1 /*                      _             _
2 **  _ __ ___   ___   __| |    ___ ___| |  mod_ssl
3 ** | '_ ` _ \ / _ \ / _` |   / __/ __| |  Apache Interface to OpenSSL
4 ** | | | | | | (_) | (_| |   \__ \__ \ |  www.modssl.org
5 ** |_| |_| |_|\___/ \__,_|___|___/___/_|  ftp.modssl.org
6 **                      |_____|
7 **  ssl_scache_dbm.c
8 **  Session Cache via DBM
9 */
10 
11 /* ====================================================================
12  * Copyright (c) 1998-2003 Ralf S. Engelschall. All rights reserved.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  *
18  * 1. Redistributions of source code must retain the above copyright
19  *    notice, this list of conditions and the following disclaimer.
20  *
21  * 2. Redistributions in binary form must reproduce the above copyright
22  *    notice, this list of conditions and the following
23  *    disclaimer in the documentation and/or other materials
24  *    provided with the distribution.
25  *
26  * 3. All advertising materials mentioning features or use of this
27  *    software must display the following acknowledgment:
28  *    "This product includes software developed by
29  *     Ralf S. Engelschall <rse@engelschall.com> for use in the
30  *     mod_ssl project (http://www.modssl.org/)."
31  *
32  * 4. The names "mod_ssl" must not be used to endorse or promote
33  *    products derived from this software without prior written
34  *    permission. For written permission, please contact
35  *    rse@engelschall.com.
36  *
37  * 5. Products derived from this software may not be called "mod_ssl"
38  *    nor may "mod_ssl" appear in their names without prior
39  *    written permission of Ralf S. Engelschall.
40  *
41  * 6. Redistributions of any form whatsoever must retain the following
42  *    acknowledgment:
43  *    "This product includes software developed by
44  *     Ralf S. Engelschall <rse@engelschall.com> for use in the
45  *     mod_ssl project (http://www.modssl.org/)."
46  *
47  * THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``AS IS'' AND ANY
48  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
50  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL RALF S. ENGELSCHALL OR
51  * HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
52  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
53  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
54  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
55  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
56  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
57  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
58  * OF THE POSSIBILITY OF SUCH DAMAGE.
59  * ====================================================================
60  */
61 
62 #include "mod_ssl.h"
63 
ssl_scache_dbm_init(server_rec * s,pool * p)64 void ssl_scache_dbm_init(server_rec *s, pool *p)
65 {
66     SSLModConfigRec *mc = myModConfig();
67     DBM *dbm;
68 
69     /* for the DBM we need the data file */
70     if (mc->szSessionCacheDataFile == NULL) {
71         ssl_log(s, SSL_LOG_ERROR, "SSLSessionCache required");
72         ssl_die();
73     }
74 
75     /* open it once to create it and to make sure it _can_ be created */
76     ssl_mutex_on(s);
77     if ((dbm = ssl_dbm_open(mc->szSessionCacheDataFile,
78                             O_RDWR|O_CREAT, SSL_DBM_FILE_MODE)) == NULL) {
79         ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
80                 "Cannot create SSLSessionCache DBM file `%s'",
81                 mc->szSessionCacheDataFile);
82         ssl_mutex_off(s);
83         return;
84     }
85     ssl_dbm_close(dbm);
86 
87     /*
88      * We have to make sure the Apache child processes have access to
89      * the DBM file. But because there are brain-dead platforms where we
90      * cannot exactly determine the suffixes we try all possibilities.
91      */
92     if (geteuid() == 0 /* is superuser */) {
93         chown(mc->szSessionCacheDataFile, ap_user_id, -1 /* no gid change */);
94         if (chown(ap_pstrcat(p, mc->szSessionCacheDataFile, SSL_DBM_FILE_SUFFIX_DIR, NULL),
95                   ap_user_id, -1) == -1) {
96             if (chown(ap_pstrcat(p, mc->szSessionCacheDataFile, ".db", NULL),
97                       ap_user_id, -1) == -1)
98                 chown(ap_pstrcat(p, mc->szSessionCacheDataFile, ".dir", NULL),
99                       ap_user_id, -1);
100         }
101         if (chown(ap_pstrcat(p, mc->szSessionCacheDataFile, SSL_DBM_FILE_SUFFIX_PAG, NULL),
102                   ap_user_id, -1) == -1) {
103             if (chown(ap_pstrcat(p, mc->szSessionCacheDataFile, ".db", NULL),
104                       ap_user_id, -1) == -1)
105                 chown(ap_pstrcat(p, mc->szSessionCacheDataFile, ".pag", NULL),
106                       ap_user_id, -1);
107         }
108     }
109     ssl_mutex_off(s);
110     ssl_scache_dbm_expire(s);
111     return;
112 }
113 
ssl_scache_dbm_kill(server_rec * s)114 void ssl_scache_dbm_kill(server_rec *s)
115 {
116     SSLModConfigRec *mc = myModConfig();
117     pool *p;
118 
119     if ((p = ap_make_sub_pool(NULL)) != NULL) {
120         /* the correct way */
121         ap_server_strip_chroot(mc->szSessionCacheDataFile, 0);
122         unlink(ap_pstrcat(p, mc->szSessionCacheDataFile, SSL_DBM_FILE_SUFFIX_DIR, NULL));
123         unlink(ap_pstrcat(p, mc->szSessionCacheDataFile, SSL_DBM_FILE_SUFFIX_PAG, NULL));
124         /* the additional ways to be sure */
125         unlink(ap_pstrcat(p, mc->szSessionCacheDataFile, ".dir", NULL));
126         unlink(ap_pstrcat(p, mc->szSessionCacheDataFile, ".pag", NULL));
127         unlink(ap_pstrcat(p, mc->szSessionCacheDataFile, ".db", NULL));
128         unlink(mc->szSessionCacheDataFile);
129         ap_destroy_pool(p);
130     }
131     return;
132 }
133 
ssl_scache_dbm_store(server_rec * s,UCHAR * id,int idlen,time_t expiry,SSL_SESSION * sess)134 BOOL ssl_scache_dbm_store(server_rec *s, UCHAR *id, int idlen, time_t expiry, SSL_SESSION *sess)
135 {
136     SSLModConfigRec *mc = myModConfig();
137     DBM *dbm;
138     datum dbmkey;
139     datum dbmval;
140     UCHAR ucaData[SSL_SESSION_MAX_DER];
141     int nData;
142     UCHAR *ucp;
143 
144     /* streamline session data */
145     if ((nData = i2d_SSL_SESSION(sess, NULL)) > sizeof(ucaData))
146         return FALSE;
147     ucp = ucaData;
148     i2d_SSL_SESSION(sess, &ucp);
149 
150     /* be careful: do not try to store too much bytes in a DBM file! */
151     if ((idlen + nData) >= 950 /* at least less than approx. 1KB */)
152         return FALSE;
153 
154     /* create DBM key */
155     dbmkey.dptr  = (char *)id;
156     dbmkey.dsize = idlen;
157 
158     /* create DBM value */
159     dbmval.dsize = sizeof(time_t) + nData;
160     dbmval.dptr  = (char *)malloc(dbmval.dsize);
161     if (dbmval.dptr == NULL)
162         return FALSE;
163     memcpy((char *)dbmval.dptr, &expiry, sizeof(time_t));
164     memcpy((char *)dbmval.dptr+sizeof(time_t), ucaData, nData);
165 
166     /* and store it to the DBM file */
167     ssl_mutex_on(s);
168     ap_server_strip_chroot(mc->szSessionCacheDataFile, 0);
169     if ((dbm = ssl_dbm_open(mc->szSessionCacheDataFile,
170                             O_RDWR, SSL_DBM_FILE_MODE)) == NULL) {
171         ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
172                 "Cannot open SSLSessionCache DBM file `%s' for writing (store)",
173                 mc->szSessionCacheDataFile);
174         ssl_mutex_off(s);
175         free(dbmval.dptr);
176         return FALSE;
177     }
178     if (ssl_dbm_store(dbm, dbmkey, dbmval, DBM_INSERT) < 0) {
179         ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
180                 "Cannot store SSL session to DBM file `%s'",
181                 mc->szSessionCacheDataFile);
182         ssl_dbm_close(dbm);
183         ssl_mutex_off(s);
184         free(dbmval.dptr);
185         return FALSE;
186     }
187     ssl_dbm_close(dbm);
188     ssl_mutex_off(s);
189 
190     /* free temporary buffers */
191     free(dbmval.dptr);
192 
193     /* allow the regular expiring to occur */
194     ssl_scache_dbm_expire(s);
195 
196     return TRUE;
197 }
198 
ssl_scache_dbm_retrieve(server_rec * s,UCHAR * id,int idlen)199 SSL_SESSION *ssl_scache_dbm_retrieve(server_rec *s, UCHAR *id, int idlen)
200 {
201     SSLModConfigRec *mc = myModConfig();
202     DBM *dbm;
203     datum dbmkey;
204     datum dbmval;
205     SSL_SESSION *sess = NULL;
206     UCHAR *ucpData;
207     int nData;
208     time_t expiry;
209     time_t now;
210 
211     /* allow the regular expiring to occur */
212     ssl_scache_dbm_expire(s);
213 
214     /* create DBM key and values */
215     dbmkey.dptr  = (char *)id;
216     dbmkey.dsize = idlen;
217 
218     /* and fetch it from the DBM file */
219     ssl_mutex_on(s);
220     ap_server_strip_chroot(mc->szSessionCacheDataFile, 0);
221     if ((dbm = ssl_dbm_open(mc->szSessionCacheDataFile,
222                             O_RDONLY, SSL_DBM_FILE_MODE)) == NULL) {
223         ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
224                 "Cannot open SSLSessionCache DBM file `%s' for reading (fetch)",
225                 mc->szSessionCacheDataFile);
226         ssl_mutex_off(s);
227         return NULL;
228     }
229     dbmval = ssl_dbm_fetch(dbm, dbmkey);
230     ssl_mutex_off(s);
231 
232     /* immediately return if not found */
233     if (dbmval.dptr == NULL || dbmval.dsize <= sizeof(time_t)) {
234 	ssl_dbm_close(dbm);
235         return NULL;
236     }
237 
238     /* parse resulting data */
239     nData = dbmval.dsize-sizeof(time_t);
240     ucpData = (UCHAR *)malloc(nData);
241     if (ucpData == NULL) {
242 	ssl_dbm_close(dbm);
243         return NULL;
244     }
245     memcpy(ucpData, (char *)dbmval.dptr+sizeof(time_t), nData);
246     memcpy(&expiry, dbmval.dptr, sizeof(time_t));
247 
248     ssl_dbm_close(dbm);
249 
250     /* make sure the stuff is still not expired */
251     now = time(NULL);
252     if (expiry <= now) {
253         ssl_scache_dbm_remove(s, id, idlen);
254         return NULL;
255     }
256 
257     /* unstreamed SSL_SESSION */
258     sess = d2i_SSL_SESSION(NULL, (void *)(&ucpData), nData);
259 
260     return sess;
261 }
262 
ssl_scache_dbm_remove(server_rec * s,UCHAR * id,int idlen)263 void ssl_scache_dbm_remove(server_rec *s, UCHAR *id, int idlen)
264 {
265     SSLModConfigRec *mc = myModConfig();
266     DBM *dbm;
267     datum dbmkey;
268 
269     /* create DBM key and values */
270     dbmkey.dptr  = (char *)id;
271     dbmkey.dsize = idlen;
272 
273     /* and delete it from the DBM file */
274     ssl_mutex_on(s);
275     ap_server_strip_chroot(mc->szSessionCacheDataFile, 0);
276     if ((dbm = ssl_dbm_open(mc->szSessionCacheDataFile,
277                             O_RDWR, SSL_DBM_FILE_MODE)) == NULL) {
278         ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
279                 "Cannot open SSLSessionCache DBM file `%s' for writing (delete)",
280                 mc->szSessionCacheDataFile);
281         ssl_mutex_off(s);
282         return;
283     }
284     ssl_dbm_delete(dbm, dbmkey);
285     ssl_dbm_close(dbm);
286     ssl_mutex_off(s);
287 
288     return;
289 }
290 
ssl_scache_dbm_expire(server_rec * s)291 void ssl_scache_dbm_expire(server_rec *s)
292 {
293     SSLModConfigRec *mc = myModConfig();
294     SSLSrvConfigRec *sc = mySrvConfig(s);
295     static time_t tLast = 0;
296     DBM *dbm;
297     datum dbmkey;
298     datum dbmval;
299     pool *p;
300     time_t tExpiresAt;
301     int nElements = 0;
302     int nDeleted = 0;
303     int bDelete;
304     datum *keylist;
305     int keyidx;
306     int i;
307     time_t tNow;
308 
309     /*
310      * make sure the expiration for still not-accessed session
311      * cache entries is done only from time to time
312      */
313     tNow = time(NULL);
314     if (tNow < tLast+sc->nSessionCacheTimeout)
315         return;
316     tLast = tNow;
317 
318     /*
319      * Here we have to be very carefully: Not all DBM libraries are
320      * smart enough to allow one to iterate over the elements and at the
321      * same time delete expired ones. Some of them get totally crazy
322      * while others have no problems. So we have to do it the slower but
323      * more safe way: we first iterate over all elements and remember
324      * those which have to be expired. Then in a second pass we delete
325      * all those expired elements. Additionally we reopen the DBM file
326      * to be really safe in state.
327      */
328 
329 #define KEYMAX 1024
330 
331     ssl_mutex_on(s);
332     for (;;) {
333         /* allocate the key array in a memory sub pool */
334         if ((p = ap_make_sub_pool(NULL)) == NULL)
335             break;
336         if ((keylist = ap_palloc(p, sizeof(dbmkey)*KEYMAX)) == NULL) {
337             ap_destroy_pool(p);
338             break;
339         }
340 
341         /* pass 1: scan DBM database */
342         keyidx = 0;
343 	ap_server_strip_chroot(mc->szSessionCacheDataFile, 0);
344         if ((dbm = ssl_dbm_open(mc->szSessionCacheDataFile,
345                                 O_RDWR, SSL_DBM_FILE_MODE)) == NULL) {
346             ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
347                     "Cannot open SSLSessionCache DBM file `%s' for scanning",
348                     mc->szSessionCacheDataFile);
349             ap_destroy_pool(p);
350             break;
351         }
352         dbmkey = ssl_dbm_firstkey(dbm);
353         while (dbmkey.dptr != NULL) {
354             nElements++;
355             bDelete = FALSE;
356             dbmval = ssl_dbm_fetch(dbm, dbmkey);
357             if (dbmval.dsize <= sizeof(time_t) || dbmval.dptr == NULL)
358                 bDelete = TRUE;
359             else {
360                 memcpy(&tExpiresAt, dbmval.dptr, sizeof(time_t));
361                 if (tExpiresAt <= tNow)
362                     bDelete = TRUE;
363             }
364             if (bDelete) {
365                 if ((keylist[keyidx].dptr = ap_palloc(p, dbmkey.dsize)) != NULL) {
366                     memcpy(keylist[keyidx].dptr, dbmkey.dptr, dbmkey.dsize);
367                     keylist[keyidx].dsize = dbmkey.dsize;
368                     keyidx++;
369                     if (keyidx == KEYMAX)
370                         break;
371                 }
372             }
373             dbmkey = ssl_dbm_nextkey(dbm);
374         }
375         ssl_dbm_close(dbm);
376 
377         /* pass 2: delete expired elements */
378         if ((dbm = ssl_dbm_open(mc->szSessionCacheDataFile,
379                                 O_RDWR, SSL_DBM_FILE_MODE)) == NULL) {
380             ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
381                     "Cannot re-open SSLSessionCache DBM file `%s' for expiring",
382                     mc->szSessionCacheDataFile);
383             ap_destroy_pool(p);
384             break;
385         }
386         for (i = 0; i < keyidx; i++) {
387             ssl_dbm_delete(dbm, keylist[i]);
388             nDeleted++;
389         }
390         ssl_dbm_close(dbm);
391 
392         /* destroy temporary pool */
393         ap_destroy_pool(p);
394 
395         if (keyidx < KEYMAX)
396             break;
397     }
398     ssl_mutex_off(s);
399 
400     ssl_log(s, SSL_LOG_TRACE, "Inter-Process Session Cache (DBM) Expiry: "
401             "old: %d, new: %d, removed: %d", nElements, nElements-nDeleted, nDeleted);
402     return;
403 }
404 
ssl_scache_dbm_status(server_rec * s,pool * p,void (* func)(char *,void *),void * arg)405 void ssl_scache_dbm_status(server_rec *s, pool *p, void (*func)(char *, void *), void *arg)
406 {
407     SSLModConfigRec *mc = myModConfig();
408     DBM *dbm;
409     datum dbmkey;
410     datum dbmval;
411     int nElem;
412     int nSize;
413     int nAverage;
414 
415     nElem = 0;
416     nSize = 0;
417     ssl_mutex_on(s);
418     ap_server_strip_chroot(mc->szSessionCacheDataFile, 0);
419     if ((dbm = ssl_dbm_open(mc->szSessionCacheDataFile,
420                             O_RDONLY, SSL_DBM_FILE_MODE)) == NULL) {
421         ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
422                 "Cannot open SSLSessionCache DBM file `%s' for status retrival",
423                 mc->szSessionCacheDataFile);
424         ssl_mutex_off(s);
425         return;
426     }
427     dbmkey = ssl_dbm_firstkey(dbm);
428     for ( ; dbmkey.dptr != NULL; dbmkey = ssl_dbm_nextkey(dbm)) {
429         dbmval = ssl_dbm_fetch(dbm, dbmkey);
430         if (dbmval.dptr == NULL)
431             continue;
432         nElem += 1;
433         nSize += dbmval.dsize;
434     }
435     ssl_dbm_close(dbm);
436     ssl_mutex_off(s);
437     if (nSize > 0 && nElem > 0)
438         nAverage = nSize / nElem;
439     else
440         nAverage = 0;
441     func(ap_psprintf(p, "cache type: <b>DBM</b>, maximum size: <b>unlimited</b><br>"), arg);
442     func(ap_psprintf(p, "current sessions: <b>%d</b>, current size: <b>%d</b> bytes<br>", nElem, nSize), arg);
443     func(ap_psprintf(p, "average session size: <b>%d</b> bytes<br>", nAverage), arg);
444     return;
445 }
446 
447