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