9
/* SCACHE *scache_multi_create()
11
/* This module implements an in-memory, multi-session cache.
13
/* scache_multi_create() instantiates a session cache that
14
/* stores multiple sessions.
16
/* Fatal error: memory allocation problem;
17
/* panic: internal consistency failure.
19
/* scache(3), generic session cache API
23
/* The Secure Mailer license must be distributed with this software.
26
/* IBM T.J. Watson Research
28
/* Yorktown Heights, NY 10598, USA
35
#include <stddef.h> /* offsetof() */
38
/* Utility library. */
47
/*#define msg_verbose 1*/
53
/* Application-specific. */
56
* SCACHE_MULTI is a derived type from the SCACHE super-class.
58
* Each destination has an entry in the destination hash table, and each
59
* destination->endpoint binding is kept in a circular list under its
60
* destination hash table entry.
62
* Likewise, each endpoint has an entry in the endpoint hash table, and each
63
* endpoint->session binding is kept in a circular list under its endpoint
66
* We do not attempt to limit the number of destination or endpoint entries,
67
* nor do we attempt to limit the number of sessions. Doing so would require
68
* a write-through cache. Currently, the CTABLE cache module supports only
69
* read-through caching.
71
* This is no problem because the number of cached destinations is limited by
72
* design. Sites that specify a wild-card domain pattern, and thus cache
73
* every session in recent history, may be in for a surprise.
76
SCACHE scache[1]; /* super-class */
77
HTABLE *dest_cache; /* destination->endpoint bindings */
78
HTABLE *endp_cache; /* endpoint->session bindings */
79
int sess_count; /* number of cached sessions */
83
* Storage for a destination or endpoint list head. Each list head knows its
84
* own hash table entry name, so that we can remove the list when it becomes
85
* empty. List items are stored in a circular list under the list head.
88
RING ring[1]; /* circular list linkage */
89
char *parent_key; /* parent linkage: hash table */
90
SCACHE_MULTI *cache; /* parent linkage: cache */
93
#define RING_TO_MULTI_HEAD(p) RING_TO_APPL((p), SCACHE_MULTI_HEAD, ring)
96
* Storage for a destination->endpoint binding. This is an element in a
97
* circular list, whose list head specifies the destination.
100
RING ring[1]; /* circular list linkage */
101
SCACHE_MULTI_HEAD *head; /* parent linkage: list head */
102
char *endp_label; /* endpoint name */
103
char *dest_prop; /* binding properties */
106
#define RING_TO_MULTI_DEST(p) RING_TO_APPL((p), SCACHE_MULTI_DEST, ring)
108
static void scache_multi_expire_dest(int, char *);
111
* Storage for an endpoint->session binding. This is an element in a
112
* circular list, whose list head specifies the endpoint.
115
RING ring[1]; /* circular list linkage */
116
SCACHE_MULTI_HEAD *head; /* parent linkage: list head */
117
int fd; /* cached session */
118
char *endp_prop; /* binding properties */
121
#define RING_TO_MULTI_ENDP(p) RING_TO_APPL((p), SCACHE_MULTI_ENDP, ring)
123
static void scache_multi_expire_endp(int, char *);
126
* When deleting a circular list element, are we deleting the entire
127
* circular list, or are we removing a single list element. We need this
128
* distinction to avoid a re-entrancy problem between htable_delete() and
131
#define BOTTOM_UP 1 /* one item */
132
#define TOP_DOWN 2 /* whole list */
134
/* scache_multi_drop_endp - destroy endpoint->session binding */
136
static void scache_multi_drop_endp(SCACHE_MULTI_ENDP *endp, int direction)
138
const char *myname = "scache_multi_drop_endp";
139
SCACHE_MULTI_HEAD *head;
142
msg_info("%s: endp_prop=%s fd=%d", myname,
143
endp->endp_prop, endp->fd);
148
event_cancel_timer(scache_multi_expire_endp, (char *) endp);
151
* In bottom-up mode, remove the list head from the endpoint hash when
152
* the list becomes empty. Otherwise, remove the endpoint->session
153
* binding from the list.
155
ring_detach(endp->ring);
157
head->cache->sess_count--;
158
if (direction == BOTTOM_UP && ring_pred(head->ring) == head->ring)
159
htable_delete(head->cache->endp_cache, head->parent_key, myfree);
162
* Destroy the endpoint->session binding.
164
if (endp->fd >= 0 && close(endp->fd) != 0)
165
msg_warn("%s: close(%d): %m", myname, endp->fd);
166
myfree(endp->endp_prop);
168
myfree((char *) endp);
171
/* scache_multi_expire_endp - event timer call-back */
173
static void scache_multi_expire_endp(int unused_event, char *context)
175
SCACHE_MULTI_ENDP *endp = (SCACHE_MULTI_ENDP *) context;
177
scache_multi_drop_endp(endp, BOTTOM_UP);
180
/* scache_multi_free_endp - hash table destructor call-back */
182
static void scache_multi_free_endp(char *ptr)
184
SCACHE_MULTI_HEAD *head = (SCACHE_MULTI_HEAD *) ptr;
185
SCACHE_MULTI_ENDP *endp;
189
* Delete each endpoint->session binding in the list, then delete the
190
* list head. Note: this changes the list, so we must iterate carefully.
192
while ((ring = ring_succ(head->ring)) != head->ring) {
193
endp = RING_TO_MULTI_ENDP(ring);
194
scache_multi_drop_endp(endp, TOP_DOWN);
196
myfree((char *) head);
199
/* scache_multi_save_endp - save endpoint->session binding */
201
static void scache_multi_save_endp(SCACHE *scache, int ttl,
202
const char *endp_label,
203
const char *endp_prop, int fd)
205
const char *myname = "scache_multi_save_endp";
206
SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
207
SCACHE_MULTI_HEAD *head;
208
SCACHE_MULTI_ENDP *endp;
211
msg_panic("%s: bad ttl: %d", myname, ttl);
214
* Look up or instantiate the list head with the endpoint name.
216
if ((head = (SCACHE_MULTI_HEAD *)
217
htable_find(sp->endp_cache, endp_label)) == 0) {
218
head = (SCACHE_MULTI_HEAD *) mymalloc(sizeof(*head));
219
ring_init(head->ring);
221
htable_enter(sp->endp_cache, endp_label, (char *) head)->key;
226
* Add the endpoint->session binding to the list. There can never be a
227
* duplicate, because each session must have a different file descriptor.
229
endp = (SCACHE_MULTI_ENDP *) mymalloc(sizeof(*endp));
232
endp->endp_prop = mystrdup(endp_prop);
233
ring_prepend(head->ring, endp->ring);
237
* Make sure this binding will go away eventually.
239
event_request_timer(scache_multi_expire_endp, (char *) endp, ttl);
242
msg_info("%s: endp_label=%s -> endp_prop=%s fd=%d",
243
myname, endp_label, endp_prop, fd);
246
/* scache_multi_find_endp - look up session for named endpoint */
248
static int scache_multi_find_endp(SCACHE *scache, const char *endp_label,
251
const char *myname = "scache_multi_find_endp";
252
SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
253
SCACHE_MULTI_HEAD *head;
254
SCACHE_MULTI_ENDP *endp;
259
* Look up the list head with the endpoint name.
261
if ((head = (SCACHE_MULTI_HEAD *)
262
htable_find(sp->endp_cache, endp_label)) == 0) {
264
msg_info("%s: no endpoint cache: endp_label=%s",
270
* Use the first available session. Remove the session from the cache
271
* because we're giving it to someone else.
273
if ((ring = ring_succ(head->ring)) != head->ring) {
274
endp = RING_TO_MULTI_ENDP(ring);
277
vstring_strcpy(endp_prop, endp->endp_prop);
279
msg_info("%s: found: endp_label=%s -> endp_prop=%s fd=%d",
280
myname, endp_label, endp->endp_prop, fd);
281
scache_multi_drop_endp(endp, BOTTOM_UP);
285
msg_info("%s: not found: endp_label=%s", myname, endp_label);
289
/* scache_multi_drop_dest - delete destination->endpoint binding */
291
static void scache_multi_drop_dest(SCACHE_MULTI_DEST *dest, int direction)
293
const char *myname = "scache_multi_drop_dest";
294
SCACHE_MULTI_HEAD *head;
297
msg_info("%s: dest_prop=%s endp_label=%s",
298
myname, dest->dest_prop, dest->endp_label);
303
event_cancel_timer(scache_multi_expire_dest, (char *) dest);
306
* In bottom-up mode, remove the list head from the destination hash when
307
* the list becomes empty. Otherwise, remove the destination->endpoint
308
* binding from the list.
310
ring_detach(dest->ring);
312
if (direction == BOTTOM_UP && ring_pred(head->ring) == head->ring)
313
htable_delete(head->cache->dest_cache, head->parent_key, myfree);
316
* Destroy the destination->endpoint binding.
318
myfree(dest->dest_prop);
319
myfree(dest->endp_label);
321
myfree((char *) dest);
324
/* scache_multi_expire_dest - event timer call-back */
326
static void scache_multi_expire_dest(int unused_event, char *context)
328
SCACHE_MULTI_DEST *dest = (SCACHE_MULTI_DEST *) context;
330
scache_multi_drop_dest(dest, BOTTOM_UP);
333
/* scache_multi_free_dest - hash table destructor call-back */
335
static void scache_multi_free_dest(char *ptr)
337
SCACHE_MULTI_HEAD *head = (SCACHE_MULTI_HEAD *) ptr;
338
SCACHE_MULTI_DEST *dest;
342
* Delete each destination->endpoint binding in the list, then delete the
343
* list head. Note: this changes the list, so we must iterate carefully.
345
while ((ring = ring_succ(head->ring)) != head->ring) {
346
dest = RING_TO_MULTI_DEST(ring);
347
scache_multi_drop_dest(dest, TOP_DOWN);
349
myfree((char *) head);
352
/* scache_multi_save_dest - save destination->endpoint binding */
354
static void scache_multi_save_dest(SCACHE *scache, int ttl,
355
const char *dest_label,
356
const char *dest_prop,
357
const char *endp_label)
359
const char *myname = "scache_multi_save_dest";
360
SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
361
SCACHE_MULTI_HEAD *head;
362
SCACHE_MULTI_DEST *dest;
367
msg_panic("%s: bad ttl: %d", myname, ttl);
370
* Look up or instantiate the list head with the destination name.
372
if ((head = (SCACHE_MULTI_HEAD *)
373
htable_find(sp->dest_cache, dest_label)) == 0) {
374
head = (SCACHE_MULTI_HEAD *) mymalloc(sizeof(*head));
375
ring_init(head->ring);
377
htable_enter(sp->dest_cache, dest_label, (char *) head)->key;
382
* Look up or instantiate the destination->endpoint binding. Update the
383
* expiration time if this destination->endpoint binding already exists.
385
RING_FOREACH(ring, head->ring) {
386
dest = RING_TO_MULTI_DEST(ring);
387
if (strcmp(dest->endp_label, endp_label) == 0
388
&& strcmp(dest->dest_prop, dest_prop) == 0) {
394
dest = (SCACHE_MULTI_DEST *) mymalloc(sizeof(*dest));
396
dest->endp_label = mystrdup(endp_label);
397
dest->dest_prop = mystrdup(dest_prop);
398
ring_prepend(head->ring, dest->ring);
402
* Make sure this binding will go away eventually.
404
event_request_timer(scache_multi_expire_dest, (char *) dest, ttl);
407
msg_info("%s: dest_label=%s -> dest_prop=%s endp_label=%s%s",
408
myname, dest_label, dest_prop, endp_label,
409
refresh ? " (refreshed)" : "");
412
/* scache_multi_find_dest - look up session for named destination */
414
static int scache_multi_find_dest(SCACHE *scache, const char *dest_label,
418
const char *myname = "scache_multi_find_dest";
419
SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
420
SCACHE_MULTI_HEAD *head;
421
SCACHE_MULTI_DEST *dest;
426
* Look up the list head with the destination name.
428
if ((head = (SCACHE_MULTI_HEAD *)
429
htable_find(sp->dest_cache, dest_label)) == 0) {
431
msg_info("%s: no destination cache: dest_label=%s",
437
* Search endpoints for the first available session.
439
RING_FOREACH(ring, head->ring) {
440
dest = RING_TO_MULTI_DEST(ring);
441
fd = scache_multi_find_endp(scache, dest->endp_label, endp_prop);
443
vstring_strcpy(dest_prop, dest->dest_prop);
448
msg_info("%s: not found: dest_label=%s", myname, dest_label);
452
/* scache_multi_size - size of multi-element cache object */
454
static void scache_multi_size(SCACHE *scache, SCACHE_SIZE *size)
456
SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
458
size->dest_count = sp->dest_cache->used;
459
size->endp_count = sp->endp_cache->used;
460
size->sess_count = sp->sess_count;
463
/* scache_multi_free - destroy multi-element cache object */
465
static void scache_multi_free(SCACHE *scache)
467
SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
469
htable_free(sp->dest_cache, scache_multi_free_dest);
470
htable_free(sp->endp_cache, scache_multi_free_endp);
475
/* scache_multi_create - initialize */
477
SCACHE *scache_multi_create(void)
479
SCACHE_MULTI *sp = (SCACHE_MULTI *) mymalloc(sizeof(*sp));
481
sp->scache->save_endp = scache_multi_save_endp;
482
sp->scache->find_endp = scache_multi_find_endp;
483
sp->scache->save_dest = scache_multi_save_dest;
484
sp->scache->find_dest = scache_multi_find_dest;
485
sp->scache->size = scache_multi_size;
486
sp->scache->free = scache_multi_free;
488
sp->dest_cache = htable_create(1);
489
sp->endp_cache = htable_create(1);