~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to modules/ssl/ssl_scache_shmcb.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
/*                      _             _
 
18
 *  _ __ ___   ___   __| |    ___ ___| |  mod_ssl
 
19
 * | '_ ` _ \ / _ \ / _` |   / __/ __| |  Apache Interface to OpenSSL
 
20
 * | | | | | | (_) | (_| |   \__ \__ \ |
 
21
 * |_| |_| |_|\___/ \__,_|___|___/___/_|
 
22
 *                      |_____|
 
23
 *  ssl_scache_shmcb.c
 
24
 *  Session Cache via Shared Memory (Cyclic Buffer Variant)
 
25
 */
 
26
 
 
27
#include "ssl_private.h"
 
28
 
 
29
/*
 
30
 * This shared memory based SSL session cache implementation was
 
31
 * originally written by Geoff Thorpe <geoff geoffthorpe.net> for C2Net
 
32
 * Europe as a contribution to Ralf Engelschall's mod_ssl project.
 
33
 */
 
34
 
 
35
/*
 
36
 * The shared-memory segment header can be cast to and from the
 
37
 * SHMCBHeader type, all other structures need to be initialised by
 
38
 * utility functions.
 
39
 *
 
40
 * The "header" looks like this;
 
41
 *
 
42
 * data applying to the overall structure:
 
43
 * - division_offset (unsigned int):
 
44
 *   how far into the shared memory segment the first division is.
 
45
 * - division_size (unsigned int):
 
46
 *   how many bytes each division occupies.
 
47
 *   (NB: This includes the queue and the cache)
 
48
 * - division_mask (unsigned char):
 
49
 *   the "mask" in the next line. Add one to this,
 
50
 *   and that's the number of divisions.
 
51
 *
 
52
 * data applying to within each division:
 
53
 * - queue_size (unsigned int):
 
54
 *   how big each "queue" is. NB: The queue is the first block in each
 
55
 *   division and is followed immediately by the cache itself so so
 
56
 *   there's no cache_offset value.
 
57
 *
 
58
 * data applying to within each queue:
 
59
 * - index_num (unsigned char):
 
60
 *   how many indexes in each cache's queue
 
61
 * - index_offset (unsigned char):
 
62
 *   how far into the queue the first index is.
 
63
 * - index_size:
 
64
 *   how big each index is.
 
65
 *
 
66
 * data applying to within each cache:
 
67
 * - cache_data_offset (unsigned int):
 
68
 *   how far into the cache the session-data array is stored.
 
69
 * - cache_data_size (unsigned int):
 
70
 *   how big each cache's data block is.
 
71
 *
 
72
 * statistics data (this will eventually be per-division but right now
 
73
 * there's only one mutex):
 
74
 * - stores (unsigned long):
 
75
 *   how many stores have been performed in the cache.
 
76
 * - expiries (unsigned long):
 
77
 *   how many session have been expired from the cache.
 
78
 * - scrolled (unsigned long):
 
79
 *   how many sessions have been scrolled out of full cache during a
 
80
 *   "store" operation. This is different to the "removes" stats as
 
81
 *   they are requested by mod_ssl/Apache, these are done because of
 
82
 *   cache logistics. (NB: Also, this value should be deducible from
 
83
 *   the others if my code has no bugs, but I count it anyway - plus
 
84
 *   it helps debugging :-).
 
85
 * - retrieves_hit (unsigned long):
 
86
 *   how many session-retrieves have succeeded.
 
87
 * - retrieves_miss (unsigned long):
 
88
 *   how many session-retrieves have failed.
 
89
 * - removes_hit (unsigned long):
 
90
 * - removes_miss (unsigned long):
 
91
 *
 
92
 * Following immediately after the header is an array of "divisions".
 
93
 * Each division is simply a "queue" immediately followed by its
 
94
 * corresponding "cache". Each division handles some pre-defined band
 
95
 * of sessions by using the "division_mask" in the header. Eg. if
 
96
 * division_mask=0x1f then there are 32 divisions, the first of which
 
97
 * will store sessions whose least-significant 5 bits are 0, the second
 
98
 * stores session whose LS 5 bits equal 1, etc. A queue is an indexing
 
99
 * structure referring to its corresponding cache.
 
100
 *
 
101
 * A "queue" looks like this;
 
102
 *
 
103
 * - first_pos (unsigned int):
 
104
 *   the location within the array of indexes where the virtual
 
105
 *   "left-hand-edge" of the cyclic buffer is.
 
106
 * - pos_count (unsigned int):
 
107
 *   the number of indexes occupied from first_pos onwards.
 
108
 *
 
109
 * ...followed by an array of indexes, each of which can be
 
110
 * memcpy'd to and from an SHMCBIndex, and look like this;
 
111
 *
 
112
 * - expires (time_t):
 
113
 *   the time() value at which this session expires.
 
114
 * - offset (unsigned int):
 
115
 *   the offset within the cache data block where the corresponding
 
116
 *   session is stored.
 
117
 * - s_id2 (unsigned char):
 
118
 *   the second byte of the session_id, stored as an optimisation to
 
119
 *   reduce the number of d2i_SSL_SESSION calls that are made when doing
 
120
 *   a lookup.
 
121
 * - removed (unsigned char):
 
122
 *   a byte used to indicate whether a session has been "passively"
 
123
 *   removed. Ie. it is still in the cache but is to be disregarded by
 
124
 *   any "retrieve" operation.
 
125
 *
 
126
 * A "cache" looks like this;
 
127
 *
 
128
 * - first_pos (unsigned int):
 
129
 *   the location within the data block where the virtual
 
130
 *   "left-hand-edge" of the cyclic buffer is.
 
131
 * - pos_count (unsigned int):
 
132
 *   the number of bytes used in the data block from first_pos onwards.
 
133
 *
 
134
 * ...followed by the data block in which actual DER-encoded SSL
 
135
 * sessions are stored.
 
136
 */
 
137
 
 
138
/*
 
139
 * Header - can be memcpy'd to and from the front of the shared
 
140
 * memory segment. NB: The first copy (commented out) has the
 
141
 * elements in a meaningful order, but due to data-alignment
 
142
 * braindeadness, the second (uncommented) copy has the types grouped
 
143
 * so as to decrease "struct-bloat". sigh.
 
144
 */
 
145
typedef struct {
 
146
    unsigned long num_stores;
 
147
    unsigned long num_expiries;
 
148
    unsigned long num_scrolled;
 
149
    unsigned long num_retrieves_hit;
 
150
    unsigned long num_retrieves_miss;
 
151
    unsigned long num_removes_hit;
 
152
    unsigned long num_removes_miss;
 
153
    unsigned int division_offset;
 
154
    unsigned int division_size;
 
155
    unsigned int queue_size;
 
156
    unsigned int cache_data_offset;
 
157
    unsigned int cache_data_size;
 
158
    unsigned char division_mask;
 
159
    unsigned int index_num;
 
160
    unsigned int index_offset;
 
161
    unsigned int index_size;
 
162
} SHMCBHeader;
 
163
 
 
164
/*
 
165
 * Index - can be memcpy'd to and from an index inside each
 
166
 * queue's index array.
 
167
 */
 
168
typedef struct {
 
169
    time_t expires;
 
170
    unsigned int offset;
 
171
    unsigned char s_id2;
 
172
    unsigned char removed;
 
173
} SHMCBIndex;
 
174
 
 
175
/*
 
176
 * Queue - must be populated by a call to shmcb_get_division
 
177
 * and the structure's pointers are used for updating (ie.
 
178
 * the structure doesn't need any "set" to update values).
 
179
 */
 
180
typedef struct {
 
181
    SHMCBHeader *header;
 
182
    unsigned int *first_pos;
 
183
    unsigned int *pos_count;
 
184
    SHMCBIndex *indexes;
 
185
} SHMCBQueue;
 
186
 
 
187
/*
 
188
 * Cache - same comment as for Queue. 'Queue's are in a 1-1
 
189
 * correspondance with 'Cache's and are usually carried round
 
190
 * in a pair, they are only seperated for clarity.
 
191
 */
 
192
typedef struct {
 
193
    SHMCBHeader *header;
 
194
    unsigned int *first_pos;
 
195
    unsigned int *pos_count;
 
196
    unsigned char *data;
 
197
} SHMCBCache;
 
198
 
 
199
/*
 
200
 * Forward function prototypes.
 
201
 */
 
202
 
 
203
/* Functions for working around data-alignment-picky systems (sparcs,
 
204
   Irix, etc). These use "memcpy" as a way of foxing these systems into
 
205
   treating the composite types as byte-arrays rather than higher-level
 
206
   primitives that it prefers to have 4-(or 8-)byte aligned. I don't
 
207
   envisage this being a performance issue as a couple of 2 or 4 byte
 
208
   memcpys can hardly make a dent on the massive memmove operations this
 
209
   cache technique avoids, nor the overheads of ASN en/decoding. */
 
210
static unsigned int shmcb_get_safe_uint(unsigned int *);
 
211
static void shmcb_set_safe_uint_ex(unsigned char *, const unsigned char *);
 
212
#define shmcb_set_safe_uint(pdest, src) \
 
213
        do { \
 
214
                unsigned int tmp_uint = src; \
 
215
                shmcb_set_safe_uint_ex((unsigned char *)pdest, \
 
216
                        (const unsigned char *)(&tmp_uint)); \
 
217
        } while(0)
 
218
#if 0 /* Unused so far */
 
219
static unsigned long shmcb_get_safe_ulong(unsigned long *);
 
220
static void shmcb_set_safe_ulong_ex(unsigned char *, const unsigned char *);
 
221
#define shmcb_set_safe_ulong(pdest, src) \
 
222
        do { \
 
223
                unsigned long tmp_ulong = src; \
 
224
                shmcb_set_safe_ulong_ex((unsigned char *)pdest, \
 
225
                        (const unsigned char *)(&tmp_ulong)); \
 
226
        } while(0)
 
227
#endif
 
228
static time_t shmcb_get_safe_time(time_t *);
 
229
static void shmcb_set_safe_time_ex(unsigned char *, const unsigned char *);
 
230
#define shmcb_set_safe_time(pdest, src) \
 
231
        do { \
 
232
                time_t tmp_time = src; \
 
233
                shmcb_set_safe_time_ex((unsigned char *)pdest, \
 
234
                        (const unsigned char *)(&tmp_time)); \
 
235
        } while(0)
 
236
 
 
237
/* This is used to persuade the compiler from using an inline memset()
 
238
 * which has no respect for alignment, since the size parameter is
 
239
 * often a compile-time constant.  GCC >= 4 will aggressively inline
 
240
 * static functions, so it's marked as explicitly not-inline. */
 
241
#if defined(__GNUC__) && __GNUC__ > 3
 
242
__attribute__((__noinline__))
 
243
#endif
 
244
static void shmcb_safe_clear(void *ptr, size_t size)
 
245
{
 
246
        memset(ptr, 0, size);
 
247
}
 
248
 
 
249
/* Underlying functions for session-caching */
 
250
static BOOL shmcb_init_memory(server_rec *, void *, unsigned int);
 
251
static BOOL shmcb_store_session(server_rec *, void *, UCHAR *, int, SSL_SESSION *, time_t);
 
252
static SSL_SESSION *shmcb_retrieve_session(server_rec *, void *, UCHAR *, int);
 
253
static BOOL shmcb_remove_session(server_rec *, void *, UCHAR *, int);
 
254
 
 
255
/* Utility functions for manipulating the structures */
 
256
static void shmcb_get_header(void *, SHMCBHeader **);
 
257
static BOOL shmcb_get_division(SHMCBHeader *, SHMCBQueue *, SHMCBCache *, unsigned int);
 
258
static SHMCBIndex *shmcb_get_index(const SHMCBQueue *, unsigned int);
 
259
static unsigned int shmcb_expire_division(server_rec *, SHMCBQueue *, SHMCBCache *);
 
260
static BOOL shmcb_insert_encoded_session(server_rec *, SHMCBQueue *, SHMCBCache *, unsigned char *, unsigned int, unsigned char *, time_t);
 
261
static SSL_SESSION *shmcb_lookup_session_id(server_rec *, SHMCBQueue *, SHMCBCache *, UCHAR *, unsigned int);
 
262
static BOOL shmcb_remove_session_id(server_rec *, SHMCBQueue *, SHMCBCache *, UCHAR *, unsigned int);
 
263
 
 
264
/*
 
265
 * Data-alignment functions (a.k.a. avoidance tactics)
 
266
 *
 
267
 * NB: On HPUX (and possibly others) there is a *very* mischievous little
 
268
 * "optimisation" in the compilers where it will convert the following;
 
269
 *      memcpy(dest_ptr, &source, sizeof(unsigned int));
 
270
 * (where dest_ptr is of type (unsigned int *) and source is (unsigned int))
 
271
 * into;
 
272
 *      *dest_ptr = source; (or *dest_ptr = *(&source), not sure).
 
273
 * Either way, it completely destroys the whole point of these _safe_
 
274
 * functions, because the assignment operation will fall victim to the
 
275
 * architecture's byte-alignment dictations, whereas the memcpy (as a
 
276
 * byte-by-byte copy) should not. sigh. So, if you're wondering about the
 
277
 * apparently unnecessary conversions to (unsigned char *) in these
 
278
 * functions, you now have an explanation. Don't just revert them back and
 
279
 * say "ooh look, it still works" - if you try it on HPUX (well, 32-bit
 
280
 * HPUX 11.00 at least) you may find it fails with a SIGBUS. :-(
 
281
 */
 
282
 
 
283
static unsigned int shmcb_get_safe_uint(unsigned int *ptr)
 
284
{
 
285
    unsigned int ret;
 
286
    shmcb_set_safe_uint_ex((unsigned char *)(&ret),
 
287
                    (const unsigned char *)ptr);
 
288
    return ret;
 
289
}
 
290
 
 
291
static void shmcb_set_safe_uint_ex(unsigned char *dest,
 
292
                                const unsigned char *src)
 
293
{
 
294
    memcpy(dest, src, sizeof(unsigned int));
 
295
}
 
296
 
 
297
#if 0 /* Unused so far */
 
298
static unsigned long shmcb_get_safe_ulong(unsigned long *ptr)
 
299
{
 
300
    unsigned long ret;
 
301
    shmcb_set_safe_ulong_ex((unsigned char *)(&ret),
 
302
                    (const unsigned char *)ptr);
 
303
    return ret;
 
304
}
 
305
 
 
306
static void shmcb_set_safe_ulong_ex(unsigned char *dest,
 
307
                                const unsigned char *src)
 
308
{
 
309
    memcpy(dest, src, sizeof(unsigned long));
 
310
}
 
311
#endif
 
312
 
 
313
static time_t shmcb_get_safe_time(time_t * ptr)
 
314
{
 
315
    time_t ret;
 
316
    shmcb_set_safe_time_ex((unsigned char *)(&ret),
 
317
                    (const unsigned char *)ptr);
 
318
    return ret;
 
319
}
 
320
 
 
321
static void shmcb_set_safe_time_ex(unsigned char *dest,
 
322
                                const unsigned char *src)
 
323
{
 
324
    memcpy(dest, src, sizeof(time_t));
 
325
}
 
326
/*
 
327
**
 
328
** High-Level "handlers" as per ssl_scache.c
 
329
**
 
330
*/
 
331
 
 
332
void ssl_scache_shmcb_init(server_rec *s, apr_pool_t *p)
 
333
{
 
334
    SSLModConfigRec *mc = myModConfig(s);
 
335
    void *shm_segment;
 
336
    apr_size_t shm_segsize;
 
337
    apr_status_t rv;
 
338
 
 
339
    /*
 
340
     * Create shared memory segment
 
341
     */
 
342
    if (mc->szSessionCacheDataFile == NULL) {
 
343
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
344
                     "SSLSessionCache required");
 
345
        ssl_die();
 
346
    }
 
347
 
 
348
    /* Use anonymous shm by default, fall back on name-based. */
 
349
    rv = apr_shm_create(&(mc->pSessionCacheDataMM),
 
350
                        mc->nSessionCacheDataSize,
 
351
                        NULL, mc->pPool);
 
352
 
 
353
    if (APR_STATUS_IS_ENOTIMPL(rv)) {
 
354
        /* For a name-based segment, remove it first in case of a
 
355
         * previous unclean shutdown. */
 
356
        apr_shm_remove(mc->szSessionCacheDataFile, mc->pPool);
 
357
 
 
358
        rv = apr_shm_create(&(mc->pSessionCacheDataMM),
 
359
                            mc->nSessionCacheDataSize,
 
360
                            mc->szSessionCacheDataFile,
 
361
                            mc->pPool);
 
362
    }
 
363
 
 
364
    if (rv != APR_SUCCESS) {
 
365
        char buf[100];
 
366
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
367
                     "Cannot allocate shared memory: (%d)%s", rv,
 
368
                     apr_strerror(rv, buf, sizeof(buf)));
 
369
        ssl_die();
 
370
    }
 
371
    shm_segment = apr_shm_baseaddr_get(mc->pSessionCacheDataMM);
 
372
    shm_segsize = apr_shm_size_get(mc->pSessionCacheDataMM);
 
373
 
 
374
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
375
                 "shmcb_init allocated %" APR_SIZE_T_FMT
 
376
                 " bytes of shared memory",
 
377
                 shm_segsize);
 
378
    if (!shmcb_init_memory(s, shm_segment, shm_segsize)) {
 
379
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
380
                     "Failure initialising 'shmcb' shared memory");
 
381
        ssl_die();
 
382
    }
 
383
    ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
 
384
                 "Shared memory session cache initialised");
 
385
 
 
386
    /*
 
387
     * Success ...
 
388
     */
 
389
    mc->tSessionCacheDataTable = shm_segment;
 
390
    return;
 
391
}
 
392
 
 
393
void ssl_scache_shmcb_kill(server_rec *s)
 
394
{
 
395
    SSLModConfigRec *mc = myModConfig(s);
 
396
 
 
397
    if (mc->pSessionCacheDataMM != NULL) {
 
398
        apr_shm_destroy(mc->pSessionCacheDataMM);
 
399
        mc->pSessionCacheDataMM = NULL;
 
400
    }
 
401
    return;
 
402
}
 
403
 
 
404
BOOL ssl_scache_shmcb_store(server_rec *s, UCHAR *id, int idlen,
 
405
                           time_t timeout, SSL_SESSION * pSession)
 
406
{
 
407
    SSLModConfigRec *mc = myModConfig(s);
 
408
    BOOL to_return = FALSE;
 
409
 
 
410
    ssl_mutex_on(s);
 
411
    if (!shmcb_store_session(s, mc->tSessionCacheDataTable, id, idlen,
 
412
                             pSession, timeout))
 
413
        /* in this cache engine, "stores" should never fail. */
 
414
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
415
                     "'shmcb' code was unable to store a "
 
416
                     "session in the cache.");
 
417
    else {
 
418
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
419
                     "shmcb_store successful");
 
420
        to_return = TRUE;
 
421
    }
 
422
    ssl_mutex_off(s);
 
423
    return to_return;
 
424
}
 
425
 
 
426
SSL_SESSION *ssl_scache_shmcb_retrieve(server_rec *s, UCHAR *id, int idlen)
 
427
{
 
428
    SSLModConfigRec *mc = myModConfig(s);
 
429
    SSL_SESSION *pSession;
 
430
 
 
431
    ssl_mutex_on(s);
 
432
    pSession = shmcb_retrieve_session(s, mc->tSessionCacheDataTable, id, idlen);
 
433
    ssl_mutex_off(s);
 
434
    if (pSession)
 
435
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
436
                     "shmcb_retrieve had a hit");
 
437
    else {
 
438
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
439
                     "shmcb_retrieve had a miss");
 
440
        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
 
441
                     "Client requested a 'session-resume' but "
 
442
                     "we have no such session.");
 
443
    }
 
444
    return pSession;
 
445
}
 
446
 
 
447
void ssl_scache_shmcb_remove(server_rec *s, UCHAR *id, int idlen)
 
448
{
 
449
    SSLModConfigRec *mc = myModConfig(s);
 
450
 
 
451
    ssl_mutex_on(s);
 
452
    shmcb_remove_session(s, mc->tSessionCacheDataTable, id, idlen);
 
453
    ssl_mutex_off(s);
 
454
}
 
455
 
 
456
void ssl_scache_shmcb_status(request_rec *r, int flags, apr_pool_t *p)
 
457
{
 
458
    SSLModConfigRec *mc = myModConfig(r->server);
 
459
    SHMCBHeader *header;
 
460
    SHMCBQueue queue;
 
461
    SHMCBCache cache;
 
462
    SHMCBIndex *idx;
 
463
    unsigned int loop, total, cache_total, non_empty_divisions;
 
464
    int index_pct, cache_pct;
 
465
    double expiry_total;
 
466
    time_t average_expiry, now, max_expiry, min_expiry, idxexpiry;
 
467
 
 
468
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "inside shmcb_status");
 
469
 
 
470
    /* Get the header structure. */
 
471
    shmcb_get_header(mc->tSessionCacheDataTable, &header);
 
472
    total = cache_total = non_empty_divisions = 0;
 
473
    average_expiry = max_expiry = min_expiry = 0;
 
474
    expiry_total = 0;
 
475
 
 
476
    /* It may seem strange to grab "now" at this point, but in theory
 
477
     * we should never have a negative threshold but grabbing "now" after
 
478
     * the loop (which performs expiries) could allow that chance. */
 
479
    now = time(NULL);
 
480
    for (loop = 0; loop <= header->division_mask; loop++) {
 
481
        if (shmcb_get_division(header, &queue, &cache, loop)) {
 
482
            shmcb_expire_division(r->server, &queue, &cache);
 
483
            total += shmcb_get_safe_uint(queue.pos_count);
 
484
            cache_total += shmcb_get_safe_uint(cache.pos_count);
 
485
            if (shmcb_get_safe_uint(queue.pos_count) > 0) {
 
486
                idx = shmcb_get_index(&queue,
 
487
                                     shmcb_get_safe_uint(queue.first_pos));
 
488
                non_empty_divisions++;
 
489
                idxexpiry = shmcb_get_safe_time(&(idx->expires));
 
490
                expiry_total += (double) idxexpiry;
 
491
                max_expiry = (idxexpiry > max_expiry ? idxexpiry :
 
492
                              max_expiry);
 
493
                if (min_expiry == 0)
 
494
                    min_expiry = idxexpiry;
 
495
                else
 
496
                    min_expiry = (idxexpiry < min_expiry ? idxexpiry :
 
497
                                  min_expiry);
 
498
            }
 
499
        }
 
500
    }
 
501
    index_pct = (100 * total) / (header->index_num * (header->division_mask + 1));
 
502
    cache_pct = (100 * cache_total) / (header->cache_data_size * (header->division_mask + 1));
 
503
    ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%d</b> "
 
504
               "bytes, current sessions: <b>%d</b><br>",
 
505
               mc->nSessionCacheDataSize, total);
 
506
    ap_rprintf(r, "sub-caches: <b>%d</b>, indexes per sub-cache: "
 
507
               "<b>%d</b><br>", (int) header->division_mask + 1,
 
508
               (int) header->index_num);
 
509
    if (non_empty_divisions != 0) {
 
510
        average_expiry = (time_t)(expiry_total / (double)non_empty_divisions);
 
511
        ap_rprintf(r, "time left on oldest entries' SSL sessions: ");
 
512
        if (now < average_expiry)
 
513
            ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>",
 
514
                       (int)(average_expiry - now), (int) (min_expiry - now),
 
515
                       (int)(max_expiry - now));
 
516
        else
 
517
            ap_rprintf(r, "expiry threshold: <b>Calculation Error!</b>"
 
518
                       "<br>");
 
519
 
 
520
    }
 
521
    ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b>"
 
522
               "<br>", index_pct, cache_pct);
 
523
    ap_rprintf(r, "total sessions stored since starting: <b>%lu</b><br>",
 
524
               header->num_stores);
 
525
    ap_rprintf(r, "total sessions expired since starting: <b>%lu</b><br>",
 
526
               header->num_expiries);
 
527
    ap_rprintf(r, "total (pre-expiry) sessions scrolled out of the "
 
528
               "cache: <b>%lu</b><br>", header->num_scrolled);
 
529
    ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, "
 
530
               "<b>%lu</b> miss<br>", header->num_retrieves_hit,
 
531
               header->num_retrieves_miss);
 
532
    ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, "
 
533
               "<b>%lu</b> miss<br>", header->num_removes_hit,
 
534
               header->num_removes_miss);
 
535
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "leaving shmcb_status");
 
536
    return;
 
537
}
 
538
 
 
539
/*
 
540
**
 
541
** Memory manipulation and low-level cache operations
 
542
**
 
543
*/
 
544
 
 
545
static BOOL shmcb_init_memory(
 
546
    server_rec *s, void *shm_mem,
 
547
    unsigned int shm_mem_size)
 
548
{
 
549
    SHMCBHeader *header;
 
550
    SHMCBQueue queue;
 
551
    SHMCBCache cache;
 
552
    unsigned int temp, loop, granularity;
 
553
 
 
554
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
555
                 "entered shmcb_init_memory()");
 
556
 
 
557
    /* Calculate some sizes... */
 
558
    temp = sizeof(SHMCBHeader);
 
559
 
 
560
    /* If the segment is ridiculously too small, bail out */
 
561
    if (shm_mem_size < (2*temp)) {
 
562
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
563
                     "shared memory segment too small");
 
564
        return FALSE;
 
565
    }
 
566
 
 
567
    /* Make temp the amount of memory without the header */
 
568
    temp = shm_mem_size - temp;
 
569
 
 
570
    /* Work on the basis that you need 10 bytes index for each session
 
571
     * (approx 150 bytes), which is to divide temp by 160 - and then
 
572
     * make sure we err on having too index space to burn even when
 
573
     * the cache is full, which is a lot less stupid than having
 
574
     * having not enough index space to utilise the whole cache!. */
 
575
    temp /= 120;
 
576
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
577
                 "for %u bytes, recommending %u indexes",
 
578
                 shm_mem_size, temp);
 
579
 
 
580
    /* We should divide these indexes evenly amongst the queues. Try
 
581
     * to get it so that there are roughly half the number of divisions
 
582
     * as there are indexes in each division. */
 
583
    granularity = 256;
 
584
    while ((temp / granularity) < (2 * granularity))
 
585
        granularity /= 2;
 
586
 
 
587
    /* So we have 'granularity' divisions, set 'temp' equal to the
 
588
     * number of indexes in each division. */
 
589
    temp /= granularity;
 
590
 
 
591
    /* Too small? Bail ... */
 
592
    if (temp < 5) {
 
593
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
594
                     "shared memory segment too small");
 
595
        return FALSE;
 
596
    }
 
597
 
 
598
    /* OK, we're sorted - from here on in, the return should be TRUE */
 
599
    header = (SHMCBHeader *)shm_mem;
 
600
    header->division_mask = (unsigned char)(granularity - 1);
 
601
    header->division_offset = sizeof(SHMCBHeader);
 
602
    header->index_num = temp;
 
603
    header->index_offset = (2 * sizeof(unsigned int));
 
604
    header->index_size = sizeof(SHMCBIndex);
 
605
    header->queue_size = header->index_offset +
 
606
                         (header->index_num * header->index_size);
 
607
 
 
608
    /* Now calculate the space for each division */
 
609
    temp = shm_mem_size - header->division_offset;
 
610
    header->division_size = temp / granularity;
 
611
 
 
612
    /* Calculate the space left in each division for the cache */
 
613
    temp -= header->queue_size;
 
614
    header->cache_data_offset = (2 * sizeof(unsigned int));
 
615
    header->cache_data_size = header->division_size -
 
616
                              header->queue_size - header->cache_data_offset;
 
617
 
 
618
    /* Output trace info */
 
619
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
620
                 "shmcb_init_memory choices follow");
 
621
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
622
                 "division_mask = 0x%02X", header->division_mask);
 
623
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
624
                 "division_offset = %u", header->division_offset);
 
625
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
626
                  "division_size = %u", header->division_size);
 
627
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
628
                  "queue_size = %u", header->queue_size);
 
629
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
630
                  "index_num = %u", header->index_num);
 
631
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
632
                  "index_offset = %u", header->index_offset);
 
633
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
634
                  "index_size = %u", header->index_size);
 
635
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
636
                  "cache_data_offset = %u", header->cache_data_offset);
 
637
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
638
                  "cache_data_size = %u", header->cache_data_size);
 
639
 
 
640
    /* The header is done, make the caches empty */
 
641
    for (loop = 0; loop < granularity; loop++) {
 
642
        if (!shmcb_get_division(header, &queue, &cache, loop))
 
643
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "shmcb_init_memory, " "internal error");
 
644
        shmcb_set_safe_uint(cache.first_pos, 0);
 
645
        shmcb_set_safe_uint(cache.pos_count, 0);
 
646
        shmcb_set_safe_uint(queue.first_pos, 0);
 
647
        shmcb_set_safe_uint(queue.pos_count, 0);
 
648
    }
 
649
 
 
650
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
651
                 "leaving shmcb_init_memory()");
 
652
    return TRUE;
 
653
}
 
654
 
 
655
static BOOL shmcb_store_session(
 
656
    server_rec *s, void *shm_segment, UCHAR *id,
 
657
    int idlen, SSL_SESSION * pSession,
 
658
    time_t timeout)
 
659
{
 
660
    SHMCBHeader *header;
 
661
    SHMCBQueue queue;
 
662
    SHMCBCache cache;
 
663
    unsigned char masked_index;
 
664
    unsigned char encoded[SSL_SESSION_MAX_DER];
 
665
    unsigned char *ptr_encoded;
 
666
    unsigned int len_encoded;
 
667
    time_t expiry_time;
 
668
    unsigned char *session_id = SSL_SESSION_get_session_id(pSession);
 
669
 
 
670
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
671
                 "inside shmcb_store_session");
 
672
 
 
673
    /* Get the header structure, which division this session will fall into etc. */
 
674
    shmcb_get_header(shm_segment, &header);
 
675
    masked_index = session_id[0] & header->division_mask;
 
676
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
677
                 "session_id[0]=%u, masked index=%u",
 
678
                 session_id[0], masked_index);
 
679
    if (!shmcb_get_division(header, &queue, &cache, (unsigned int)masked_index)) {
 
680
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
681
                     "shmcb_store_session internal error");
 
682
        return FALSE;
 
683
    }
 
684
 
 
685
    /* Serialise the session, work out how much we're dealing
 
686
     * with. NB: This check could be removed if we're not paranoid
 
687
     * or we find some assurance that it will never be necessary. */
 
688
    len_encoded = i2d_SSL_SESSION(pSession, NULL);
 
689
    if (len_encoded > SSL_SESSION_MAX_DER) {
 
690
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
691
                     "session is too big (%u bytes)", len_encoded);
 
692
        return FALSE;
 
693
    }
 
694
    ptr_encoded = encoded;
 
695
    len_encoded = i2d_SSL_SESSION(pSession, &ptr_encoded);
 
696
    expiry_time = timeout;
 
697
    if (!shmcb_insert_encoded_session(s, &queue, &cache, encoded,
 
698
                                     len_encoded, session_id,
 
699
                                     expiry_time)) {
 
700
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
701
                     "can't store a session!");
 
702
        return FALSE;
 
703
    }
 
704
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
705
                 "leaving shmcb_store successfully");
 
706
    header->num_stores++;
 
707
    return TRUE;
 
708
}
 
709
 
 
710
static SSL_SESSION *shmcb_retrieve_session(
 
711
    server_rec *s, void *shm_segment,
 
712
    UCHAR *id, int idlen)
 
713
{
 
714
    SHMCBHeader *header;
 
715
    SHMCBQueue queue;
 
716
    SHMCBCache cache;
 
717
    unsigned char masked_index;
 
718
    SSL_SESSION *pSession;
 
719
 
 
720
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
721
                 "inside shmcb_retrieve_session");
 
722
    if (idlen < 2) {
 
723
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
 
724
                "(%u bytes)", idlen);
 
725
        return FALSE;
 
726
    }
 
727
 
 
728
    /* Get the header structure, which division this session lookup
 
729
     * will come from etc. */
 
730
    shmcb_get_header(shm_segment, &header);
 
731
    masked_index = id[0] & header->division_mask;
 
732
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
733
                 "id[0]=%u, masked index=%u", id[0], masked_index);
 
734
    if (!shmcb_get_division(header, &queue, &cache, (unsigned int) masked_index)) {
 
735
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
736
                     "shmcb_retrieve_session internal error");
 
737
        header->num_retrieves_miss++;
 
738
        return FALSE;
 
739
    }
 
740
 
 
741
    /* Get the session corresponding to the session_id or NULL if it
 
742
     * doesn't exist (or is flagged as "removed"). */
 
743
    pSession = shmcb_lookup_session_id(s, &queue, &cache, id, idlen);
 
744
    if (pSession)
 
745
        header->num_retrieves_hit++;
 
746
    else
 
747
        header->num_retrieves_miss++;
 
748
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
749
                 "leaving shmcb_retrieve_session");
 
750
    return pSession;
 
751
}
 
752
 
 
753
static BOOL shmcb_remove_session(
 
754
    server_rec *s, void *shm_segment,
 
755
    UCHAR *id, int idlen)
 
756
{
 
757
    SHMCBHeader *header;
 
758
    SHMCBQueue queue;
 
759
    SHMCBCache cache;
 
760
    unsigned char masked_index;
 
761
    BOOL res;
 
762
 
 
763
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
764
                 "inside shmcb_remove_session");
 
765
    if (id == NULL) {
 
766
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "remove called with NULL session_id!");
 
767
        return FALSE;
 
768
    }
 
769
 
 
770
    /* Get the header structure, which division this session remove
 
771
     * will happen in etc. */
 
772
    shmcb_get_header(shm_segment, &header);
 
773
    masked_index = id[0] & header->division_mask;
 
774
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
775
                 "id[0]=%u, masked index=%u", id[0], masked_index);
 
776
    if (!shmcb_get_division(header, &queue, &cache, (unsigned int)masked_index)) {
 
777
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "shmcb_remove_session, internal error");
 
778
        header->num_removes_miss++;
 
779
        return FALSE;
 
780
    }
 
781
    res = shmcb_remove_session_id(s, &queue, &cache, id, idlen);
 
782
    if (res)
 
783
        header->num_removes_hit++;
 
784
    else
 
785
        header->num_removes_miss++;
 
786
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
787
                 "leaving shmcb_remove_session");
 
788
    return res;
 
789
}
 
790
 
 
791
 
 
792
/*
 
793
**
 
794
** Weirdo cyclic buffer functions
 
795
**
 
796
*/
 
797
 
 
798
/* This gets used in the cyclic "index array" (in the 'Queue's) and
 
799
 * in the cyclic 'Cache's too ... you provide the "width" of the
 
800
 * cyclic store, the starting position and how far to move (with
 
801
 * wrapping if necessary). Basically it's addition modulo buf_size. */
 
802
static unsigned int shmcb_cyclic_increment(
 
803
    unsigned int buf_size,
 
804
    unsigned int start_pos,
 
805
    unsigned int to_add)
 
806
{
 
807
    start_pos += to_add;
 
808
    while (start_pos >= buf_size)
 
809
        start_pos -= buf_size;
 
810
    return start_pos;
 
811
}
 
812
 
 
813
/* Given two positions in a cyclic buffer, calculate the "distance".
 
814
 * This is to cover the case ("non-trivial") where the 'next' offset
 
815
 * is to the left of the 'start' offset. NB: This calculates the
 
816
 * space inclusive of one end-point but not the other. There is an
 
817
 * ambiguous case (which is why we use the <start_pos,offset>
 
818
 * coordinate system rather than <start_pos,end_pos> one) when 'start'
 
819
 * is the same as 'next'. It could indicate the buffer is full or it
 
820
 * can indicate the buffer is empty ... I choose the latter as it's
 
821
 * easier and usually necessary to check if the buffer is full anyway
 
822
 * before doing incremental logic (which is this useful for), but we
 
823
 * definitely need the empty case handled - in fact it's our starting
 
824
 * state!! */
 
825
static unsigned int shmcb_cyclic_space(
 
826
    unsigned int buf_size,
 
827
    unsigned int start_offset,
 
828
    unsigned int next_offset)
 
829
{
 
830
    /* Is it the trivial case? */
 
831
    if (start_offset <= next_offset)
 
832
        return (next_offset - start_offset);              /* yes */
 
833
    else
 
834
        return ((buf_size - start_offset) + next_offset); /* no */
 
835
}
 
836
 
 
837
/* A "normal-to-cyclic" memcpy ... this takes a linear block of
 
838
 * memory and copies it onto a cyclic buffer. The purpose and
 
839
 * function of this is pretty obvious, you need to cover the case
 
840
 * that the destination (cyclic) buffer has to wrap round. */
 
841
static void shmcb_cyclic_ntoc_memcpy(
 
842
    unsigned int buf_size,
 
843
    unsigned char *data,
 
844
    unsigned int dest_offset,
 
845
    unsigned char *src, unsigned int src_len)
 
846
{
 
847
    /* Cover the case that src_len > buf_size */
 
848
    if (src_len > buf_size)
 
849
        src_len = buf_size;
 
850
 
 
851
    /* Can it be copied all in one go? */
 
852
    if (dest_offset + src_len < buf_size)
 
853
        /* yes */
 
854
        memcpy(data + dest_offset, src, src_len);
 
855
    else {
 
856
        /* no */
 
857
        memcpy(data + dest_offset, src, buf_size - dest_offset);
 
858
        memcpy(data, src + buf_size - dest_offset,
 
859
               src_len + dest_offset - buf_size);
 
860
    }
 
861
    return;
 
862
}
 
863
 
 
864
/* A "cyclic-to-normal" memcpy ... given the last function, this
 
865
 * one's purpose is clear, it copies out of a cyclic buffer handling
 
866
 * wrapping. */
 
867
static void shmcb_cyclic_cton_memcpy(
 
868
    unsigned int buf_size,
 
869
    unsigned char *dest,
 
870
    unsigned char *data,
 
871
    unsigned int src_offset,
 
872
    unsigned int src_len)
 
873
{
 
874
    /* Cover the case that src_len > buf_size */
 
875
    if (src_len > buf_size)
 
876
        src_len = buf_size;
 
877
 
 
878
    /* Can it be copied all in one go? */
 
879
    if (src_offset + src_len < buf_size)
 
880
        /* yes */
 
881
        memcpy(dest, data + src_offset, src_len);
 
882
    else {
 
883
        /* no */
 
884
        memcpy(dest, data + src_offset, buf_size - src_offset);
 
885
        memcpy(dest + buf_size - src_offset, data,
 
886
               src_len + src_offset - buf_size);
 
887
    }
 
888
    return;
 
889
}
 
890
 
 
891
/* Here's the cool hack that makes it all work ... by simply
 
892
 * making the first collection of bytes *be* our header structure
 
893
 * (casting it into the C structure), we have the perfect way to
 
894
 * maintain state in a shared-memory session cache from one call
 
895
 * (and process) to the next, use the shared memory itself! The
 
896
 * original mod_ssl shared-memory session cache uses variables
 
897
 * inside the context, but we simply use that for storing the
 
898
 * pointer to the shared memory itself. And don't forget, after
 
899
 * Apache's initialisation, this "header" is constant/read-only
 
900
 * so we can read it outside any locking.
 
901
 * <grin> - sometimes I just *love* coding y'know?!  */
 
902
static void shmcb_get_header(void *shm_mem, SHMCBHeader **header)
 
903
{
 
904
    *header = (SHMCBHeader *)shm_mem;
 
905
    return;
 
906
}
 
907
 
 
908
/* This is what populates our "interesting" structures. Given a
 
909
 * pointer to the header, and an index into the appropriate
 
910
 * division (this must have already been masked using the
 
911
 * division_mask by the caller!), we can populate the provided
 
912
 * SHMCBQueue and SHMCBCache structures with values and
 
913
 * pointers to the underlying shared memory. Upon returning
 
914
 * (if not FALSE), the caller can meddle with the pointer
 
915
 * values and they will map into the shared-memory directly,
 
916
 * as such there's no need to "free" or "set" the Queue or
 
917
 * Cache values, they were themselves references to the *real*
 
918
 * data. */
 
919
static BOOL shmcb_get_division(
 
920
    SHMCBHeader *header, SHMCBQueue *queue,
 
921
    SHMCBCache *cache, unsigned int idx)
 
922
{
 
923
    unsigned char *pQueue;
 
924
    unsigned char *pCache;
 
925
 
 
926
    /* bounds check */
 
927
    if (idx > (unsigned int) header->division_mask)
 
928
        return FALSE;
 
929
 
 
930
    /* Locate the blocks of memory storing the corresponding data */
 
931
    pQueue = ((unsigned char *) header) + header->division_offset +
 
932
        (idx * header->division_size);
 
933
    pCache = pQueue + header->queue_size;
 
934
 
 
935
    /* Populate the structures with appropriate pointers */
 
936
    queue->first_pos = (unsigned int *) pQueue;
 
937
 
 
938
    /* Our structures stay packed, no matter what the system's
 
939
     * data-alignment regime is. */
 
940
    queue->pos_count = (unsigned int *) (pQueue + sizeof(unsigned int));
 
941
    queue->indexes = (SHMCBIndex *) (pQueue + (2 * sizeof(unsigned int)));
 
942
    cache->first_pos = (unsigned int *) pCache;
 
943
    cache->pos_count = (unsigned int *) (pCache + sizeof(unsigned int));
 
944
    cache->data = (unsigned char *) (pCache + (2 * sizeof(unsigned int)));
 
945
    queue->header = cache->header = header;
 
946
 
 
947
    return TRUE;
 
948
}
 
949
 
 
950
/* This returns a pointer to the piece of shared memory containing
 
951
 * a specified 'Index'. SHMCBIndex, like SHMCBHeader, is a fixed
 
952
 * width non-referencing structure of primitive types that can be
 
953
 * cast onto the corresponding block of shared memory. Thus, by
 
954
 * returning a cast pointer to that section of shared memory, the
 
955
 * caller can read and write values to and from the "structure" and
 
956
 * they are actually reading and writing the underlying shared
 
957
 * memory. */
 
958
static SHMCBIndex *shmcb_get_index(
 
959
    const SHMCBQueue *queue, unsigned int idx)
 
960
{
 
961
    /* bounds check */
 
962
    if (idx > queue->header->index_num)
 
963
        return NULL;
 
964
 
 
965
    /* Return a pointer to the index. NB: I am being horribly pendantic
 
966
     * here so as to avoid any potential data-alignment assumptions being
 
967
     * placed on the pointer arithmetic by the compiler (sigh). */
 
968
    return (SHMCBIndex *)(((unsigned char *) queue->indexes) +
 
969
                          (idx * sizeof(SHMCBIndex)));
 
970
}
 
971
 
 
972
/* This functions rolls expired cache (and index) entries off the front
 
973
 * of the cyclic buffers in a division. The function returns the number
 
974
 * of expired sessions. */
 
975
static unsigned int shmcb_expire_division(
 
976
    server_rec *s, SHMCBQueue *queue, SHMCBCache *cache)
 
977
{
 
978
    SHMCBIndex *idx;
 
979
    time_t now;
 
980
    unsigned int loop, index_num, pos_count, new_pos;
 
981
    SHMCBHeader *header;
 
982
 
 
983
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
984
                 "entering shmcb_expire_division");
 
985
 
 
986
    /* We must calculate num and space ourselves based on expiry times. */
 
987
    now = time(NULL);
 
988
    loop = 0;
 
989
    new_pos = shmcb_get_safe_uint(queue->first_pos);
 
990
 
 
991
    /* Cache useful values */
 
992
    header = queue->header;
 
993
    index_num = header->index_num;
 
994
    pos_count = shmcb_get_safe_uint(queue->pos_count);
 
995
    while (loop < pos_count) {
 
996
        idx = shmcb_get_index(queue, new_pos);
 
997
        if (shmcb_get_safe_time(&(idx->expires)) > now)
 
998
            /* it hasn't expired yet, we're done iterating */
 
999
            break;
 
1000
        /* This one should be expired too. Shift to the next entry. */
 
1001
        loop++;
 
1002
        new_pos = shmcb_cyclic_increment(index_num, new_pos, 1);
 
1003
    }
 
1004
 
 
1005
    /* Find the new_offset and make the expiries happen. */
 
1006
    if (loop > 0) {
 
1007
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1008
                     "will be expiring %u sessions", loop);
 
1009
        /* We calculate the new_offset by "peeking" (or in the
 
1010
         * case it's the last entry, "sneaking" ;-). */
 
1011
        if (loop == pos_count) {
 
1012
            /* We are expiring everything! This is easy to do... */
 
1013
            shmcb_set_safe_uint(queue->pos_count, 0);
 
1014
            shmcb_set_safe_uint(cache->pos_count, 0);
 
1015
        }
 
1016
        else {
 
1017
            /* The Queue is easy to adjust */
 
1018
            shmcb_set_safe_uint(queue->pos_count,
 
1019
                               shmcb_get_safe_uint(queue->pos_count) - loop);
 
1020
            shmcb_set_safe_uint(queue->first_pos, new_pos);
 
1021
            /* peek to the start of the next session */
 
1022
            idx = shmcb_get_index(queue, new_pos);
 
1023
            /* We can use shmcb_cyclic_space because we've guaranteed
 
1024
             * we don't fit the ambiguous full/empty case. */
 
1025
            shmcb_set_safe_uint(cache->pos_count,
 
1026
                               shmcb_get_safe_uint(cache->pos_count) -
 
1027
                               shmcb_cyclic_space(header->cache_data_size,
 
1028
                                                  shmcb_get_safe_uint(cache->first_pos),
 
1029
                                                  shmcb_get_safe_uint(&(idx->offset))));
 
1030
            shmcb_set_safe_uint(cache->first_pos, shmcb_get_safe_uint(&(idx->offset)));
 
1031
        }
 
1032
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1033
                     "we now have %u sessions",
 
1034
                     shmcb_get_safe_uint(queue->pos_count));
 
1035
    }
 
1036
    header->num_expiries += loop;
 
1037
    return loop;
 
1038
}
 
1039
 
 
1040
/* Inserts a new encoded session into a queue/cache pair - expiring
 
1041
 * (early or otherwise) any leading sessions as necessary to ensure
 
1042
 * there is room. An error return (FALSE) should only happen in the
 
1043
 * event of surreal values being passed on, or ridiculously small
 
1044
 * cache sizes. NB: For tracing purposes, this function is also given
 
1045
 * the server_rec to allow "ssl_log()". */
 
1046
static BOOL shmcb_insert_encoded_session(
 
1047
    server_rec *s, SHMCBQueue * queue,
 
1048
    SHMCBCache * cache,
 
1049
    unsigned char *encoded,
 
1050
    unsigned int encoded_len,
 
1051
    unsigned char *session_id,
 
1052
    time_t expiry_time)
 
1053
{
 
1054
    SHMCBHeader *header;
 
1055
    SHMCBIndex *idx = NULL;
 
1056
    unsigned int gap, new_pos, loop, new_offset;
 
1057
    int need;
 
1058
 
 
1059
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1060
                 "entering shmcb_insert_encoded_session, "
 
1061
                 "*queue->pos_count = %u",
 
1062
                 shmcb_get_safe_uint(queue->pos_count));
 
1063
 
 
1064
    /* If there's entries to expire, ditch them first thing. */
 
1065
    shmcb_expire_division(s, queue, cache);
 
1066
    header = cache->header;
 
1067
    gap = header->cache_data_size - shmcb_get_safe_uint(cache->pos_count);
 
1068
    if (gap < encoded_len) {
 
1069
        new_pos = shmcb_get_safe_uint(queue->first_pos);
 
1070
        loop = 0;
 
1071
        need = (int) encoded_len - (int) gap;
 
1072
        while ((need > 0) && (loop + 1 < shmcb_get_safe_uint(queue->pos_count))) {
 
1073
            new_pos = shmcb_cyclic_increment(header->index_num, new_pos, 1);
 
1074
            loop += 1;
 
1075
            idx = shmcb_get_index(queue, new_pos);
 
1076
            need = (int) encoded_len - (int) gap -
 
1077
                shmcb_cyclic_space(header->cache_data_size,
 
1078
                                   shmcb_get_safe_uint(cache->first_pos),
 
1079
                                   shmcb_get_safe_uint(&(idx->offset)));
 
1080
        }
 
1081
        if (loop > 0) {
 
1082
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1083
                         "about to scroll %u sessions from %u",
 
1084
                         loop, shmcb_get_safe_uint(queue->pos_count));
 
1085
            /* We are removing "loop" items from the cache. */
 
1086
            shmcb_set_safe_uint(cache->pos_count,
 
1087
                                shmcb_get_safe_uint(cache->pos_count) -
 
1088
                                shmcb_cyclic_space(header->cache_data_size,
 
1089
                                                   shmcb_get_safe_uint(cache->first_pos),
 
1090
                                                   shmcb_get_safe_uint(&(idx->offset))));
 
1091
            shmcb_set_safe_uint(cache->first_pos, shmcb_get_safe_uint(&(idx->offset)));
 
1092
            shmcb_set_safe_uint(queue->pos_count, shmcb_get_safe_uint(queue->pos_count) - loop);
 
1093
            shmcb_set_safe_uint(queue->first_pos, new_pos);
 
1094
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1095
                         "now only have %u sessions",
 
1096
                         shmcb_get_safe_uint(queue->pos_count));
 
1097
            /* Update the stats!!! */
 
1098
            header->num_scrolled += loop;
 
1099
        }
 
1100
    }
 
1101
 
 
1102
    /* probably unecessary checks, but I'll leave them until this code
 
1103
     * is verified. */
 
1104
    if (shmcb_get_safe_uint(cache->pos_count) + encoded_len >
 
1105
        header->cache_data_size) {
 
1106
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
1107
                     "shmcb_insert_encoded_session internal error");
 
1108
        return FALSE;
 
1109
    }
 
1110
    if (shmcb_get_safe_uint(queue->pos_count) == header->index_num) {
 
1111
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
1112
                     "shmcb_insert_encoded_session internal error");
 
1113
        return FALSE;
 
1114
    }
 
1115
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1116
                 "we have %u bytes and %u indexes free - enough",
 
1117
                 header->cache_data_size -
 
1118
                 shmcb_get_safe_uint(cache->pos_count), header->index_num -
 
1119
                 shmcb_get_safe_uint(queue->pos_count));
 
1120
 
 
1121
 
 
1122
    /* HERE WE ASSUME THAT THE NEW SESSION SHOULD GO ON THE END! I'M NOT
 
1123
     * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE.
 
1124
     *
 
1125
     * We either fix that, or find out at a "higher" (read "mod_ssl")
 
1126
     * level whether it is possible to have distinct session caches for
 
1127
     * any attempted tomfoolery to do with different session timeouts.
 
1128
     * Knowing in advance that we can have a cache-wide constant timeout
 
1129
     * would make this stuff *MUCH* more efficient. Mind you, it's very
 
1130
     * efficient right now because I'm ignoring this problem!!!
 
1131
     */
 
1132
 
 
1133
    /* Increment to the first unused byte */
 
1134
    new_offset = shmcb_cyclic_increment(header->cache_data_size,
 
1135
                                        shmcb_get_safe_uint(cache->first_pos),
 
1136
                                        shmcb_get_safe_uint(cache->pos_count));
 
1137
    /* Copy the DER-encoded session into place */
 
1138
    shmcb_cyclic_ntoc_memcpy(header->cache_data_size, cache->data,
 
1139
                            new_offset, encoded, encoded_len);
 
1140
    /* Get the new index that this session is stored in. */
 
1141
    new_pos = shmcb_cyclic_increment(header->index_num,
 
1142
                                     shmcb_get_safe_uint(queue->first_pos),
 
1143
                                     shmcb_get_safe_uint(queue->pos_count));
 
1144
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1145
                 "storing in index %u, at offset %u",
 
1146
                 new_pos, new_offset);
 
1147
    idx = shmcb_get_index(queue, new_pos);
 
1148
    if (idx == NULL) {
 
1149
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
1150
                     "shmcb_insert_encoded_session internal error");
 
1151
        return FALSE;
 
1152
    }
 
1153
    shmcb_safe_clear(idx, sizeof(SHMCBIndex));
 
1154
    shmcb_set_safe_time(&(idx->expires), expiry_time);
 
1155
    shmcb_set_safe_uint(&(idx->offset), new_offset);
 
1156
 
 
1157
    /* idx->removed = (unsigned char)0; */ /* Not needed given the memset above. */
 
1158
    idx->s_id2 = session_id[1];
 
1159
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1160
                 "session_id[0]=%u, idx->s_id2=%u",
 
1161
                 session_id[0], session_id[1]);
 
1162
 
 
1163
    /* All that remains is to adjust the cache's and queue's "pos_count"s. */
 
1164
    shmcb_set_safe_uint(cache->pos_count,
 
1165
                       shmcb_get_safe_uint(cache->pos_count) + encoded_len);
 
1166
    shmcb_set_safe_uint(queue->pos_count,
 
1167
                       shmcb_get_safe_uint(queue->pos_count) + 1);
 
1168
 
 
1169
    /* And just for good debugging measure ... */
 
1170
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1171
                 "leaving now with %u bytes in the cache and %u indexes",
 
1172
                 shmcb_get_safe_uint(cache->pos_count),
 
1173
                 shmcb_get_safe_uint(queue->pos_count));
 
1174
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1175
                 "leaving shmcb_insert_encoded_session");
 
1176
    return TRUE;
 
1177
}
 
1178
 
 
1179
/* Performs a lookup into a queue/cache pair for a
 
1180
 * session_id. If found, the session is deserialised
 
1181
 * and returned, otherwise NULL. */
 
1182
static SSL_SESSION *shmcb_lookup_session_id(
 
1183
    server_rec *s, SHMCBQueue *queue,
 
1184
    SHMCBCache *cache, UCHAR *id,
 
1185
    unsigned int idlen)
 
1186
{
 
1187
    unsigned char tempasn[SSL_SESSION_MAX_DER];
 
1188
    SHMCBIndex *idx;
 
1189
    SHMCBHeader *header;
 
1190
    SSL_SESSION *pSession = NULL;
 
1191
    unsigned int curr_pos, loop, count;
 
1192
    MODSSL_D2I_SSL_SESSION_CONST unsigned char *ptr;
 
1193
    time_t now;
 
1194
 
 
1195
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1196
                 "entering shmcb_lookup_session_id");
 
1197
 
 
1198
    /* If there are entries to expire, ditch them first thing. */
 
1199
    shmcb_expire_division(s, queue, cache);
 
1200
    now = time(NULL);
 
1201
    curr_pos = shmcb_get_safe_uint(queue->first_pos);
 
1202
    count = shmcb_get_safe_uint(queue->pos_count);
 
1203
    header = queue->header;
 
1204
    for (loop = 0; loop < count; loop++) {
 
1205
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1206
                     "loop=%u, count=%u, curr_pos=%u",
 
1207
                     loop, count, curr_pos);
 
1208
        idx = shmcb_get_index(queue, curr_pos);
 
1209
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1210
                     "idx->s_id2=%u, id[1]=%u, offset=%u",
 
1211
                idx->s_id2, id[1], shmcb_get_safe_uint(&(idx->offset)));
 
1212
        /* Only look into the session further if;
 
1213
         * (a) the second byte of the session_id matches,
 
1214
         * (b) the "removed" flag isn't set,
 
1215
         * (c) the session hasn't expired yet.
 
1216
         * We do (c) like this so that it saves us having to
 
1217
         * do natural expiries ... naturally expired sessions
 
1218
         * scroll off the front anyway when the cache is full and
 
1219
         * "rotating", the only real issue that remains is the
 
1220
         * removal or disabling of forcibly killed sessions. */
 
1221
        if ((idx->s_id2 == id[1]) && !idx->removed &&
 
1222
            (shmcb_get_safe_time(&(idx->expires)) > now)) {
 
1223
            unsigned int session_id_length;
 
1224
            unsigned char *session_id;
 
1225
 
 
1226
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1227
                         "at index %u, found possible session match",
 
1228
                         curr_pos);
 
1229
            shmcb_cyclic_cton_memcpy(header->cache_data_size,
 
1230
                                     tempasn, cache->data,
 
1231
                                     shmcb_get_safe_uint(&(idx->offset)),
 
1232
                                     SSL_SESSION_MAX_DER);
 
1233
            ptr = tempasn;
 
1234
            pSession = d2i_SSL_SESSION(NULL, &ptr, SSL_SESSION_MAX_DER);
 
1235
            session_id_length = SSL_SESSION_get_session_id_length(pSession);
 
1236
            session_id = SSL_SESSION_get_session_id(pSession);
 
1237
 
 
1238
            if (pSession == NULL) {
 
1239
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
1240
                             "scach2_lookup_session_id internal error");
 
1241
                return NULL;
 
1242
            }
 
1243
            if ((session_id_length == idlen) &&
 
1244
                (memcmp(session_id, id, idlen) == 0)) {
 
1245
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1246
                             "a match!");
 
1247
                return pSession;
 
1248
            }
 
1249
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1250
                         "not a match");
 
1251
            SSL_SESSION_free(pSession);
 
1252
            pSession = NULL;
 
1253
        }
 
1254
        curr_pos = shmcb_cyclic_increment(header->index_num, curr_pos, 1);
 
1255
    }
 
1256
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1257
                 "no matching sessions were found");
 
1258
    return NULL;
 
1259
}
 
1260
 
 
1261
static BOOL shmcb_remove_session_id(
 
1262
    server_rec *s, SHMCBQueue *queue,
 
1263
    SHMCBCache *cache, UCHAR *id, unsigned int idlen)
 
1264
{
 
1265
    unsigned char tempasn[SSL_SESSION_MAX_DER];
 
1266
    SSL_SESSION *pSession = NULL;
 
1267
    SHMCBIndex *idx;
 
1268
    SHMCBHeader *header;
 
1269
    unsigned int curr_pos, loop, count;
 
1270
    MODSSL_D2I_SSL_SESSION_CONST unsigned char *ptr;
 
1271
    BOOL to_return = FALSE;
 
1272
 
 
1273
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1274
                 "entering shmcb_remove_session_id");
 
1275
 
 
1276
    /* If there's entries to expire, ditch them first thing. */
 
1277
    /* shmcb_expire_division(s, queue, cache); */
 
1278
 
 
1279
    /* Regarding the above ... hmmm ... I know my expiry code is slightly
 
1280
     * "faster" than all this remove stuff ... but if the higher level
 
1281
     * code calls a "remove" operation (and this *only* seems to happen
 
1282
     * when it has spotted an expired session before we had a chance to)
 
1283
     * then it should get credit for a remove (stats-wise). Also, in the
 
1284
     * off-chance that the server *requests* a renegotiate and wants to
 
1285
     * wipe the session clean we should give that priority over our own
 
1286
     * routine expiry handling. So I've moved the expiry check to *after*
 
1287
     * this general remove stuff. */
 
1288
    curr_pos = shmcb_get_safe_uint(queue->first_pos);
 
1289
    count = shmcb_get_safe_uint(queue->pos_count);
 
1290
    header = cache->header;
 
1291
    for (loop = 0; loop < count; loop++) {
 
1292
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1293
                     "loop=%u, count=%u, curr_pos=%u",
 
1294
                loop, count, curr_pos);
 
1295
        idx = shmcb_get_index(queue, curr_pos);
 
1296
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1297
                     "idx->s_id2=%u, id[1]=%u", idx->s_id2,
 
1298
                id[1]);
 
1299
        /* Only look into the session further if the second byte of the
 
1300
         * session_id matches. */
 
1301
        if (idx->s_id2 == id[1]) {
 
1302
            unsigned int session_id_length;
 
1303
            unsigned char *session_id;
 
1304
 
 
1305
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1306
                         "at index %u, found possible "
 
1307
                         "session match", curr_pos);
 
1308
            shmcb_cyclic_cton_memcpy(header->cache_data_size,
 
1309
                                     tempasn, cache->data,
 
1310
                                     shmcb_get_safe_uint(&(idx->offset)),
 
1311
                                     SSL_SESSION_MAX_DER);
 
1312
            ptr = tempasn;
 
1313
            pSession = d2i_SSL_SESSION(NULL, &ptr, SSL_SESSION_MAX_DER);
 
1314
            if (pSession == NULL) {
 
1315
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
 
1316
                             "shmcb_remove_session_id, internal error");
 
1317
                goto end;
 
1318
            }
 
1319
            session_id_length = SSL_SESSION_get_session_id_length(pSession);
 
1320
            session_id = SSL_SESSION_get_session_id(pSession);
 
1321
 
 
1322
            if ((session_id_length == idlen)
 
1323
                 && (memcmp(id, session_id, idlen) == 0)) {
 
1324
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1325
                            "a match!");
 
1326
                /* Scrub out this session "quietly" */
 
1327
                idx->removed = (unsigned char) 1;
 
1328
                SSL_SESSION_free(pSession);
 
1329
                to_return = TRUE;
 
1330
                goto end;
 
1331
            }
 
1332
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1333
                         "not a match");
 
1334
            SSL_SESSION_free(pSession);
 
1335
            pSession = NULL;
 
1336
        }
 
1337
        curr_pos = shmcb_cyclic_increment(header->index_num, curr_pos, 1);
 
1338
    }
 
1339
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1340
                 "no matching sessions were found");
 
1341
 
 
1342
    /* If there's entries to expire, ditch them now. */
 
1343
    shmcb_expire_division(s, queue, cache);
 
1344
end:
 
1345
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
 
1346
                 "leaving shmcb_remove_session_id");
 
1347
    return to_return;
 
1348
}