]> granicus.if.org Git - apache/blob - modules/cache/mod_socache_dbm.c
* modules/cache/mod_socache_dbm.c
[apache] / modules / cache / mod_socache_dbm.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "httpd.h"
18 #include "http_log.h"
19 #include "http_request.h"
20 #include "http_protocol.h"
21 #include "http_config.h"
22 #include "mpm_common.h"
23
24 #include "apr.h"
25 #include "apr_strings.h"
26 #include "apr_time.h"
27 #define APR_WANT_STRFUNC
28 #include "apr_want.h"
29 #include "apr_dbm.h"
30
31 #if APR_HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34
35 #include "ap_socache.h"
36
37 #if AP_NEED_SET_MUTEX_PERMS
38 #include "unixd.h"
39 #endif
40
41 /* Use of the context structure must be thread-safe after the initial
42  * create/init; callers must hold the mutex. */
43 struct ap_socache_instance_t {
44     const char *data_file;
45     /* Pool must only be used with the mutex held. */
46     apr_pool_t *pool;
47     time_t last_expiry;
48     time_t expiry_interval;
49 };
50
51 /**
52  * Support for DBM library
53  */
54 #define SSL_DBM_FILE_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
55
56 /* ### this should use apr_dbm_usednames. */
57 #if !defined(SSL_DBM_FILE_SUFFIX_DIR) && !defined(SSL_DBM_FILE_SUFFIX_PAG)
58 #if defined(DBM_SUFFIX)
59 #define SSL_DBM_FILE_SUFFIX_DIR DBM_SUFFIX
60 #define SSL_DBM_FILE_SUFFIX_PAG DBM_SUFFIX
61 #elif defined(__FreeBSD__) || (defined(DB_LOCK) && defined(DB_SHMEM))
62 #define SSL_DBM_FILE_SUFFIX_DIR ".db"
63 #define SSL_DBM_FILE_SUFFIX_PAG ".db"
64 #else
65 #define SSL_DBM_FILE_SUFFIX_DIR ".dir"
66 #define SSL_DBM_FILE_SUFFIX_PAG ".pag"
67 #endif
68 #endif
69
70 static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s);
71
72 static void socache_dbm_remove(ap_socache_instance_t *ctx, server_rec *s, 
73                                const unsigned char *id, unsigned int idlen,
74                                apr_pool_t *p);
75
76 static const char *socache_dbm_create(ap_socache_instance_t **context, 
77                                       const char *arg, 
78                                       apr_pool_t *tmp, apr_pool_t *p)
79 {
80     ap_socache_instance_t *ctx;
81
82     *context = ctx = apr_pcalloc(p, sizeof *ctx);
83
84     ctx->data_file = ap_server_root_relative(p, arg);
85     if (!ctx->data_file) {
86         return apr_psprintf(tmp, "Invalid cache file path %s", arg);
87     }
88
89     apr_pool_create(&ctx->pool, p);
90
91     return NULL;
92 }
93
94 static apr_status_t socache_dbm_init(ap_socache_instance_t *ctx, 
95                                      const char *namespace, 
96                                      const struct ap_socache_hints *hints,
97                                      server_rec *s, apr_pool_t *p)
98 {
99     apr_dbm_t *dbm;
100     apr_status_t rv;
101
102     /* for the DBM we need the data file */
103     if (ctx->data_file == NULL) {
104         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
105                      "SSLSessionCache required");
106         return APR_EINVAL;
107     }
108
109     /* open it once to create it and to make sure it _can_ be created */
110     apr_pool_clear(ctx->pool);
111
112     if ((rv = apr_dbm_open(&dbm, ctx->data_file,
113             APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
114         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
115                      "Cannot create SSLSessionCache DBM file `%s'",
116                      ctx->data_file);
117         return rv;
118     }
119     apr_dbm_close(dbm);
120
121     ctx->expiry_interval = (hints && hints->expiry_interval 
122                             ? hints->expiry_interval : 30);
123
124 #if AP_NEED_SET_MUTEX_PERMS
125     /*
126      * We have to make sure the Apache child processes have access to
127      * the DBM file. But because there are brain-dead platforms where we
128      * cannot exactly determine the suffixes we try all possibilities.
129      */
130     if (geteuid() == 0 /* is superuser */) {
131         chown(ctx->data_file, unixd_config.user_id, -1 /* no gid change */);
132         if (chown(apr_pstrcat(p, ctx->data_file, SSL_DBM_FILE_SUFFIX_DIR, NULL),
133                   unixd_config.user_id, -1) == -1) {
134             if (chown(apr_pstrcat(p, ctx->data_file, ".db", NULL),
135                       unixd_config.user_id, -1) == -1)
136                 chown(apr_pstrcat(p, ctx->data_file, ".dir", NULL),
137                       unixd_config.user_id, -1);
138         }
139         if (chown(apr_pstrcat(p, ctx->data_file, SSL_DBM_FILE_SUFFIX_PAG, NULL),
140                   unixd_config.user_id, -1) == -1) {
141             if (chown(apr_pstrcat(p, ctx->data_file, ".db", NULL),
142                       unixd_config.user_id, -1) == -1)
143                 chown(apr_pstrcat(p, ctx->data_file, ".pag", NULL),
144                       unixd_config.user_id, -1);
145         }
146     }
147 #endif
148     socache_dbm_expire(ctx, s);
149
150     return APR_SUCCESS;
151 }
152
153 static void socache_dbm_kill(ap_socache_instance_t *ctx, server_rec *s)
154 {
155     /* the correct way */
156     unlink(apr_pstrcat(ctx->pool, ctx->data_file, SSL_DBM_FILE_SUFFIX_DIR, NULL));
157     unlink(apr_pstrcat(ctx->pool, ctx->data_file, SSL_DBM_FILE_SUFFIX_PAG, NULL));
158     /* the additional ways to be sure */
159     unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".dir", NULL));
160     unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".pag", NULL));
161     unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".db", NULL));
162     unlink(ctx->data_file);
163
164     return;
165 }
166
167 static apr_status_t socache_dbm_store(ap_socache_instance_t *ctx, server_rec *s,
168                                       const unsigned char *id, unsigned int idlen,
169                                       time_t expiry, unsigned char *ucaData, 
170                                       unsigned int nData)
171 {
172     apr_dbm_t *dbm;
173     apr_datum_t dbmkey;
174     apr_datum_t dbmval;
175     apr_status_t rv;
176
177     /* be careful: do not try to store too much bytes in a DBM file! */
178 #ifdef PAIRMAX
179     if ((idlen + nData) >= PAIRMAX) {
180         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
181                  "data size too large for DBM session cache: %d >= %d",
182                  (idlen + nData), PAIRMAX);
183         return APR_ENOSPC;
184     }
185 #else
186     if ((idlen + nData) >= 950 /* at least less than approx. 1KB */) {
187         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
188                  "data size too large for DBM session cache: %d >= %d",
189                  (idlen + nData), 950);
190         return APR_ENOSPC;
191     }
192 #endif
193
194     /* create DBM key */
195     dbmkey.dptr  = (char *)id;
196     dbmkey.dsize = idlen;
197
198     /* create DBM value */
199     dbmval.dsize = sizeof(time_t) + nData;
200     dbmval.dptr  = (char *)malloc(dbmval.dsize);
201     if (dbmval.dptr == NULL) {
202         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
203                  "malloc error creating DBM value");
204         return APR_ENOMEM;
205     }
206     memcpy((char *)dbmval.dptr, &expiry, sizeof(time_t));
207     memcpy((char *)dbmval.dptr+sizeof(time_t), ucaData, nData);
208
209     /* and store it to the DBM file */
210     apr_pool_clear(ctx->pool);
211
212     if ((rv = apr_dbm_open(&dbm, ctx->data_file,
213                            APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
214         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
215                      "Cannot open SSLSessionCache DBM file `%s' for writing "
216                      "(store)",
217                      ctx->data_file);
218         free(dbmval.dptr);
219         return rv;
220     }
221     if ((rv = apr_dbm_store(dbm, dbmkey, dbmval)) != APR_SUCCESS) {
222         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
223                      "Cannot store SSL session to DBM file `%s'",
224                      ctx->data_file);
225         apr_dbm_close(dbm);
226         free(dbmval.dptr);
227         return rv;
228     }
229     apr_dbm_close(dbm);
230
231     /* free temporary buffers */
232     free(dbmval.dptr);
233
234     /* allow the regular expiring to occur */
235     socache_dbm_expire(ctx, s);
236
237     return APR_SUCCESS;
238 }
239
240 static apr_status_t socache_dbm_retrieve(ap_socache_instance_t *ctx, server_rec *s, 
241                                          const unsigned char *id, unsigned int idlen,
242                                          unsigned char *dest, unsigned int *destlen,
243                                          apr_pool_t *p)
244 {
245     apr_dbm_t *dbm;
246     apr_datum_t dbmkey;
247     apr_datum_t dbmval;
248     unsigned int nData;
249     time_t expiry;
250     time_t now;
251     apr_status_t rc;
252
253     /* allow the regular expiring to occur */
254     socache_dbm_expire(ctx, s);
255
256     /* create DBM key and values */
257     dbmkey.dptr  = (char *)id;
258     dbmkey.dsize = idlen;
259
260     /* and fetch it from the DBM file
261      * XXX: Should we open the dbm against r->pool so the cleanup will
262      * do the apr_dbm_close? This would make the code a bit cleaner.
263      */
264     apr_pool_clear(ctx->pool);
265     if ((rc = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
266                            SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
267         ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
268                      "Cannot open SSLSessionCache DBM file `%s' for reading "
269                      "(fetch)",
270                      ctx->data_file);
271         return rc;
272     }
273     rc = apr_dbm_fetch(dbm, dbmkey, &dbmval);
274     if (rc != APR_SUCCESS) {
275         apr_dbm_close(dbm);
276         return rc;
277     }
278     if (dbmval.dptr == NULL || dbmval.dsize <= sizeof(time_t)) {
279         apr_dbm_close(dbm);
280         return APR_EGENERAL;
281     }
282
283     /* parse resulting data */
284     nData = dbmval.dsize-sizeof(time_t);
285     if (nData > *destlen) {
286         apr_dbm_close(dbm);
287         return APR_ENOSPC;
288     }    
289
290     *destlen = nData;
291     memcpy(&expiry, dbmval.dptr, sizeof(time_t));
292     memcpy(dest, (char *)dbmval.dptr + sizeof(time_t), nData);
293
294     apr_dbm_close(dbm);
295
296     /* make sure the stuff is still not expired */
297     now = time(NULL);
298     if (expiry <= now) {
299         socache_dbm_remove(ctx, s, id, idlen, p);
300         return APR_NOTFOUND;
301     }
302
303     return APR_SUCCESS;
304 }
305
306 static void socache_dbm_remove(ap_socache_instance_t *ctx, server_rec *s, 
307                                const unsigned char *id, unsigned int idlen,
308                                apr_pool_t *p)
309 {
310     apr_dbm_t *dbm;
311     apr_datum_t dbmkey;
312     apr_status_t rv;
313
314     /* create DBM key and values */
315     dbmkey.dptr  = (char *)id;
316     dbmkey.dsize = idlen;
317
318     /* and delete it from the DBM file */
319     apr_pool_clear(ctx->pool);
320
321     if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
322                            SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
323         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
324                      "Cannot open SSLSessionCache DBM file `%s' for writing "
325                      "(delete)",
326                      ctx->data_file);
327         return;
328     }
329     apr_dbm_delete(dbm, dbmkey);
330     apr_dbm_close(dbm);
331
332     return;
333 }
334
335 static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
336 {
337     apr_dbm_t *dbm;
338     apr_datum_t dbmkey;
339     apr_datum_t dbmval;
340     time_t tExpiresAt;
341     int nElements = 0;
342     int nDeleted = 0;
343     int bDelete;
344     apr_datum_t *keylist;
345     int keyidx;
346     int i;
347     time_t tNow;
348     apr_status_t rv;
349
350     /*
351      * make sure the expiration for still not-accessed session
352      * cache entries is done only from time to time
353      */
354     tNow = time(NULL);
355
356     if (tNow < ctx->last_expiry + ctx->expiry_interval) {
357         return;
358     }
359
360     ctx->last_expiry = tNow;
361
362     /*
363      * Here we have to be very carefully: Not all DBM libraries are
364      * smart enough to allow one to iterate over the elements and at the
365      * same time delete expired ones. Some of them get totally crazy
366      * while others have no problems. So we have to do it the slower but
367      * more safe way: we first iterate over all elements and remember
368      * those which have to be expired. Then in a second pass we delete
369      * all those expired elements. Additionally we reopen the DBM file
370      * to be really safe in state.
371      */
372
373 #define KEYMAX 1024
374
375     for (;;) {
376         /* allocate the key array in a memory sub pool */
377         apr_pool_clear(ctx->pool);
378
379         if ((keylist = apr_palloc(ctx->pool, sizeof(dbmkey)*KEYMAX)) == NULL) {
380             break;
381         }
382
383         /* pass 1: scan DBM database */
384         keyidx = 0;
385         if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
386                                SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
387             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
388                          "Cannot open SSLSessionCache DBM file `%s' for "
389                          "scanning",
390                          ctx->data_file);
391             break;
392         }
393         apr_dbm_firstkey(dbm, &dbmkey);
394         while (dbmkey.dptr != NULL) {
395             nElements++;
396             bDelete = FALSE;
397             apr_dbm_fetch(dbm, dbmkey, &dbmval);
398             if (dbmval.dsize <= sizeof(time_t) || dbmval.dptr == NULL)
399                 bDelete = TRUE;
400             else {
401                 memcpy(&tExpiresAt, dbmval.dptr, sizeof(time_t));
402                 if (tExpiresAt <= tNow)
403                     bDelete = TRUE;
404             }
405             if (bDelete) {
406                 if ((keylist[keyidx].dptr = apr_pmemdup(ctx->pool, dbmkey.dptr, dbmkey.dsize)) != NULL) {
407                     keylist[keyidx].dsize = dbmkey.dsize;
408                     keyidx++;
409                     if (keyidx == KEYMAX)
410                         break;
411                 }
412             }
413             apr_dbm_nextkey(dbm, &dbmkey);
414         }
415         apr_dbm_close(dbm);
416
417         /* pass 2: delete expired elements */
418         if (apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
419                          SSL_DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) {
420             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
421                          "Cannot re-open SSLSessionCache DBM file `%s' for "
422                          "expiring",
423                          ctx->data_file);
424             break;
425         }
426         for (i = 0; i < keyidx; i++) {
427             apr_dbm_delete(dbm, keylist[i]);
428             nDeleted++;
429         }
430         apr_dbm_close(dbm);
431
432         if (keyidx < KEYMAX)
433             break;
434     }
435
436     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
437                  "Inter-Process Session Cache (DBM) Expiry: "
438                  "old: %d, new: %d, removed: %d",
439                  nElements, nElements-nDeleted, nDeleted);
440 }
441
442 static void socache_dbm_status(ap_socache_instance_t *ctx, request_rec *r, 
443                                int flags)
444 {
445     apr_dbm_t *dbm;
446     apr_datum_t dbmkey;
447     apr_datum_t dbmval;
448     int nElem;
449     int nSize;
450     int nAverage;
451     apr_status_t rv;
452
453     nElem = 0;
454     nSize = 0;
455
456     apr_pool_clear(ctx->pool);
457     if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
458                            SSL_DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
459         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
460                      "Cannot open SSLSessionCache DBM file `%s' for status "
461                      "retrival",
462                      ctx->data_file);
463         return;
464     }
465     /*
466      * XXX - Check the return value of apr_dbm_firstkey, apr_dbm_fetch - TBD
467      */
468     apr_dbm_firstkey(dbm, &dbmkey);
469     for ( ; dbmkey.dptr != NULL; apr_dbm_nextkey(dbm, &dbmkey)) {
470         apr_dbm_fetch(dbm, dbmkey, &dbmval);
471         if (dbmval.dptr == NULL)
472             continue;
473         nElem += 1;
474         nSize += dbmval.dsize;
475     }
476     apr_dbm_close(dbm);
477     if (nSize > 0 && nElem > 0)
478         nAverage = nSize / nElem;
479     else
480         nAverage = 0;
481     ap_rprintf(r, "cache type: <b>DBM</b>, maximum size: <b>unlimited</b><br>");
482     ap_rprintf(r, "current sessions: <b>%d</b>, current size: <b>%d</b> bytes<br>", nElem, nSize);
483     ap_rprintf(r, "average session size: <b>%d</b> bytes<br>", nAverage);
484     return;
485 }
486
487 static const ap_socache_provider_t socache_dbm = {
488     "dbm",
489     AP_SOCACHE_FLAG_NOTMPSAFE,
490     socache_dbm_create,
491     socache_dbm_init,
492     socache_dbm_kill,
493     socache_dbm_store,
494     socache_dbm_retrieve,
495     socache_dbm_remove,
496     socache_dbm_status
497 };
498
499 static void register_hooks(apr_pool_t *p)
500 {
501     ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dbm", 
502                          AP_SOCACHE_PROVIDER_VERSION,
503                          &socache_dbm);
504 }
505
506 module AP_MODULE_DECLARE_DATA socache_dbm_module = {
507     STANDARD20_MODULE_STUFF,
508     NULL, NULL, NULL, NULL, NULL,
509     register_hooks
510 };