diff options
Diffstat (limited to 'src/global/scache_multi.c')
-rw-r--r-- | src/global/scache_multi.c | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/src/global/scache_multi.c b/src/global/scache_multi.c new file mode 100644 index 0000000..7d8373d --- /dev/null +++ b/src/global/scache_multi.c @@ -0,0 +1,493 @@ +/*++ +/* NAME +/* scache_multi 3 +/* SUMMARY +/* multi-session cache +/* SYNOPSIS +/* #include <scache.h> +/* DESCRIPTION +/* SCACHE *scache_multi_create() +/* DESCRIPTION +/* This module implements an in-memory, multi-session cache. +/* +/* scache_multi_create() instantiates a session cache that +/* stores multiple sessions. +/* DIAGNOSTICS +/* Fatal error: memory allocation problem; +/* panic: internal consistency failure. +/* SEE ALSO +/* scache(3), generic session cache API +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <stddef.h> /* offsetof() */ +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <ring.h> +#include <htable.h> +#include <vstring.h> +#include <mymalloc.h> +#include <events.h> + +/*#define msg_verbose 1*/ + +/* Global library. */ + +#include <scache.h> + +/* Application-specific. */ + + /* + * SCACHE_MULTI is a derived type from the SCACHE super-class. + * + * Each destination has an entry in the destination hash table, and each + * destination->endpoint binding is kept in a circular list under its + * destination hash table entry. + * + * Likewise, each endpoint has an entry in the endpoint hash table, and each + * endpoint->session binding is kept in a circular list under its endpoint + * hash table entry. + * + * We do not attempt to limit the number of destination or endpoint entries, + * nor do we attempt to limit the number of sessions. Doing so would require + * a write-through cache. Currently, the CTABLE cache module supports only + * read-through caching. + * + * This is no problem because the number of cached destinations is limited by + * design. Sites that specify a wild-card domain pattern, and thus cache + * every session in recent history, may be in for a surprise. + */ +typedef struct { + SCACHE scache[1]; /* super-class */ + HTABLE *dest_cache; /* destination->endpoint bindings */ + HTABLE *endp_cache; /* endpoint->session bindings */ + int sess_count; /* number of cached sessions */ +} SCACHE_MULTI; + + /* + * Storage for a destination or endpoint list head. Each list head knows its + * own hash table entry name, so that we can remove the list when it becomes + * empty. List items are stored in a circular list under the list head. + */ +typedef struct { + RING ring[1]; /* circular list linkage */ + char *parent_key; /* parent linkage: hash table */ + SCACHE_MULTI *cache; /* parent linkage: cache */ +} SCACHE_MULTI_HEAD; + +#define RING_TO_MULTI_HEAD(p) RING_TO_APPL((p), SCACHE_MULTI_HEAD, ring) + + /* + * Storage for a destination->endpoint binding. This is an element in a + * circular list, whose list head specifies the destination. + */ +typedef struct { + RING ring[1]; /* circular list linkage */ + SCACHE_MULTI_HEAD *head; /* parent linkage: list head */ + char *endp_label; /* endpoint name */ + char *dest_prop; /* binding properties */ +} SCACHE_MULTI_DEST; + +#define RING_TO_MULTI_DEST(p) RING_TO_APPL((p), SCACHE_MULTI_DEST, ring) + +static void scache_multi_expire_dest(int, void *); + + /* + * Storage for an endpoint->session binding. This is an element in a + * circular list, whose list head specifies the endpoint. + */ +typedef struct { + RING ring[1]; /* circular list linkage */ + SCACHE_MULTI_HEAD *head; /* parent linkage: list head */ + int fd; /* cached session */ + char *endp_prop; /* binding properties */ +} SCACHE_MULTI_ENDP; + +#define RING_TO_MULTI_ENDP(p) RING_TO_APPL((p), SCACHE_MULTI_ENDP, ring) + +static void scache_multi_expire_endp(int, void *); + + /* + * When deleting a circular list element, are we deleting the entire + * circular list, or are we removing a single list element. We need this + * distinction to avoid a re-entrancy problem between htable_delete() and + * htable_free(). + */ +#define BOTTOM_UP 1 /* one item */ +#define TOP_DOWN 2 /* whole list */ + +/* scache_multi_drop_endp - destroy endpoint->session binding */ + +static void scache_multi_drop_endp(SCACHE_MULTI_ENDP *endp, int direction) +{ + const char *myname = "scache_multi_drop_endp"; + SCACHE_MULTI_HEAD *head; + + if (msg_verbose) + msg_info("%s: endp_prop=%s fd=%d", myname, + endp->endp_prop, endp->fd); + + /* + * Stop the timer. + */ + event_cancel_timer(scache_multi_expire_endp, (void *) endp); + + /* + * In bottom-up mode, remove the list head from the endpoint hash when + * the list becomes empty. Otherwise, remove the endpoint->session + * binding from the list. + */ + ring_detach(endp->ring); + head = endp->head; + head->cache->sess_count--; + if (direction == BOTTOM_UP && ring_pred(head->ring) == head->ring) + htable_delete(head->cache->endp_cache, head->parent_key, myfree); + + /* + * Destroy the endpoint->session binding. + */ + if (endp->fd >= 0 && close(endp->fd) != 0) + msg_warn("%s: close(%d): %m", myname, endp->fd); + myfree(endp->endp_prop); + + myfree((void *) endp); +} + +/* scache_multi_expire_endp - event timer call-back */ + +static void scache_multi_expire_endp(int unused_event, void *context) +{ + SCACHE_MULTI_ENDP *endp = (SCACHE_MULTI_ENDP *) context; + + scache_multi_drop_endp(endp, BOTTOM_UP); +} + +/* scache_multi_free_endp - hash table destructor call-back */ + +static void scache_multi_free_endp(void *ptr) +{ + SCACHE_MULTI_HEAD *head = (SCACHE_MULTI_HEAD *) ptr; + SCACHE_MULTI_ENDP *endp; + RING *ring; + + /* + * Delete each endpoint->session binding in the list, then delete the + * list head. Note: this changes the list, so we must iterate carefully. + */ + while ((ring = ring_succ(head->ring)) != head->ring) { + endp = RING_TO_MULTI_ENDP(ring); + scache_multi_drop_endp(endp, TOP_DOWN); + } + myfree((void *) head); +} + +/* scache_multi_save_endp - save endpoint->session binding */ + +static void scache_multi_save_endp(SCACHE *scache, int ttl, + const char *endp_label, + const char *endp_prop, int fd) +{ + const char *myname = "scache_multi_save_endp"; + SCACHE_MULTI *sp = (SCACHE_MULTI *) scache; + SCACHE_MULTI_HEAD *head; + SCACHE_MULTI_ENDP *endp; + + if (ttl < 0) + msg_panic("%s: bad ttl: %d", myname, ttl); + + /* + * Look up or instantiate the list head with the endpoint name. + */ + if ((head = (SCACHE_MULTI_HEAD *) + htable_find(sp->endp_cache, endp_label)) == 0) { + head = (SCACHE_MULTI_HEAD *) mymalloc(sizeof(*head)); + ring_init(head->ring); + head->parent_key = + htable_enter(sp->endp_cache, endp_label, (void *) head)->key; + head->cache = sp; + } + + /* + * Add the endpoint->session binding to the list. There can never be a + * duplicate, because each session must have a different file descriptor. + */ + endp = (SCACHE_MULTI_ENDP *) mymalloc(sizeof(*endp)); + endp->head = head; + endp->fd = fd; + endp->endp_prop = mystrdup(endp_prop); + ring_prepend(head->ring, endp->ring); + sp->sess_count++; + + /* + * Make sure this binding will go away eventually. + */ + event_request_timer(scache_multi_expire_endp, (void *) endp, ttl); + + if (msg_verbose) + msg_info("%s: endp_label=%s -> endp_prop=%s fd=%d", + myname, endp_label, endp_prop, fd); +} + +/* scache_multi_find_endp - look up session for named endpoint */ + +static int scache_multi_find_endp(SCACHE *scache, const char *endp_label, + VSTRING *endp_prop) +{ + const char *myname = "scache_multi_find_endp"; + SCACHE_MULTI *sp = (SCACHE_MULTI *) scache; + SCACHE_MULTI_HEAD *head; + SCACHE_MULTI_ENDP *endp; + RING *ring; + int fd; + + /* + * Look up the list head with the endpoint name. + */ + if ((head = (SCACHE_MULTI_HEAD *) + htable_find(sp->endp_cache, endp_label)) == 0) { + if (msg_verbose) + msg_info("%s: no endpoint cache: endp_label=%s", + myname, endp_label); + return (-1); + } + + /* + * Use the first available session. Remove the session from the cache + * because we're giving it to someone else. + */ + if ((ring = ring_succ(head->ring)) != head->ring) { + endp = RING_TO_MULTI_ENDP(ring); + fd = endp->fd; + endp->fd = -1; + vstring_strcpy(endp_prop, endp->endp_prop); + if (msg_verbose) + msg_info("%s: found: endp_label=%s -> endp_prop=%s fd=%d", + myname, endp_label, endp->endp_prop, fd); + scache_multi_drop_endp(endp, BOTTOM_UP); + return (fd); + } + if (msg_verbose) + msg_info("%s: not found: endp_label=%s", myname, endp_label); + return (-1); +} + +/* scache_multi_drop_dest - delete destination->endpoint binding */ + +static void scache_multi_drop_dest(SCACHE_MULTI_DEST *dest, int direction) +{ + const char *myname = "scache_multi_drop_dest"; + SCACHE_MULTI_HEAD *head; + + if (msg_verbose) + msg_info("%s: dest_prop=%s endp_label=%s", + myname, dest->dest_prop, dest->endp_label); + + /* + * Stop the timer. + */ + event_cancel_timer(scache_multi_expire_dest, (void *) dest); + + /* + * In bottom-up mode, remove the list head from the destination hash when + * the list becomes empty. Otherwise, remove the destination->endpoint + * binding from the list. + */ + ring_detach(dest->ring); + head = dest->head; + if (direction == BOTTOM_UP && ring_pred(head->ring) == head->ring) + htable_delete(head->cache->dest_cache, head->parent_key, myfree); + + /* + * Destroy the destination->endpoint binding. + */ + myfree(dest->dest_prop); + myfree(dest->endp_label); + + myfree((void *) dest); +} + +/* scache_multi_expire_dest - event timer call-back */ + +static void scache_multi_expire_dest(int unused_event, void *context) +{ + SCACHE_MULTI_DEST *dest = (SCACHE_MULTI_DEST *) context; + + scache_multi_drop_dest(dest, BOTTOM_UP); +} + +/* scache_multi_free_dest - hash table destructor call-back */ + +static void scache_multi_free_dest(void *ptr) +{ + SCACHE_MULTI_HEAD *head = (SCACHE_MULTI_HEAD *) ptr; + SCACHE_MULTI_DEST *dest; + RING *ring; + + /* + * Delete each destination->endpoint binding in the list, then delete the + * list head. Note: this changes the list, so we must iterate carefully. + */ + while ((ring = ring_succ(head->ring)) != head->ring) { + dest = RING_TO_MULTI_DEST(ring); + scache_multi_drop_dest(dest, TOP_DOWN); + } + myfree((void *) head); +} + +/* scache_multi_save_dest - save destination->endpoint binding */ + +static void scache_multi_save_dest(SCACHE *scache, int ttl, + const char *dest_label, + const char *dest_prop, + const char *endp_label) +{ + const char *myname = "scache_multi_save_dest"; + SCACHE_MULTI *sp = (SCACHE_MULTI *) scache; + SCACHE_MULTI_HEAD *head; + SCACHE_MULTI_DEST *dest; + RING *ring; + int refresh = 0; + + if (ttl < 0) + msg_panic("%s: bad ttl: %d", myname, ttl); + + /* + * Look up or instantiate the list head with the destination name. + */ + if ((head = (SCACHE_MULTI_HEAD *) + htable_find(sp->dest_cache, dest_label)) == 0) { + head = (SCACHE_MULTI_HEAD *) mymalloc(sizeof(*head)); + ring_init(head->ring); + head->parent_key = + htable_enter(sp->dest_cache, dest_label, (void *) head)->key; + head->cache = sp; + } + + /* + * Look up or instantiate the destination->endpoint binding. Update the + * expiration time if this destination->endpoint binding already exists. + */ + RING_FOREACH(ring, head->ring) { + dest = RING_TO_MULTI_DEST(ring); + if (strcmp(dest->endp_label, endp_label) == 0 + && strcmp(dest->dest_prop, dest_prop) == 0) { + refresh = 1; + break; + } + } + if (refresh == 0) { + dest = (SCACHE_MULTI_DEST *) mymalloc(sizeof(*dest)); + dest->head = head; + dest->endp_label = mystrdup(endp_label); + dest->dest_prop = mystrdup(dest_prop); + ring_prepend(head->ring, dest->ring); + } + + /* + * Make sure this binding will go away eventually. + */ + event_request_timer(scache_multi_expire_dest, (void *) dest, ttl); + + if (msg_verbose) + msg_info("%s: dest_label=%s -> dest_prop=%s endp_label=%s%s", + myname, dest_label, dest_prop, endp_label, + refresh ? " (refreshed)" : ""); +} + +/* scache_multi_find_dest - look up session for named destination */ + +static int scache_multi_find_dest(SCACHE *scache, const char *dest_label, + VSTRING *dest_prop, + VSTRING *endp_prop) +{ + const char *myname = "scache_multi_find_dest"; + SCACHE_MULTI *sp = (SCACHE_MULTI *) scache; + SCACHE_MULTI_HEAD *head; + SCACHE_MULTI_DEST *dest; + RING *ring; + int fd; + + /* + * Look up the list head with the destination name. + */ + if ((head = (SCACHE_MULTI_HEAD *) + htable_find(sp->dest_cache, dest_label)) == 0) { + if (msg_verbose) + msg_info("%s: no destination cache: dest_label=%s", + myname, dest_label); + return (-1); + } + + /* + * Search endpoints for the first available session. + */ + RING_FOREACH(ring, head->ring) { + dest = RING_TO_MULTI_DEST(ring); + fd = scache_multi_find_endp(scache, dest->endp_label, endp_prop); + if (fd >= 0) { + vstring_strcpy(dest_prop, dest->dest_prop); + return (fd); + } + } + if (msg_verbose) + msg_info("%s: not found: dest_label=%s", myname, dest_label); + return (-1); +} + +/* scache_multi_size - size of multi-element cache object */ + +static void scache_multi_size(SCACHE *scache, SCACHE_SIZE *size) +{ + SCACHE_MULTI *sp = (SCACHE_MULTI *) scache; + + size->dest_count = sp->dest_cache->used; + size->endp_count = sp->endp_cache->used; + size->sess_count = sp->sess_count; +} + +/* scache_multi_free - destroy multi-element cache object */ + +static void scache_multi_free(SCACHE *scache) +{ + SCACHE_MULTI *sp = (SCACHE_MULTI *) scache; + + htable_free(sp->dest_cache, scache_multi_free_dest); + htable_free(sp->endp_cache, scache_multi_free_endp); + + myfree((void *) sp); +} + +/* scache_multi_create - initialize */ + +SCACHE *scache_multi_create(void) +{ + SCACHE_MULTI *sp = (SCACHE_MULTI *) mymalloc(sizeof(*sp)); + + sp->scache->save_endp = scache_multi_save_endp; + sp->scache->find_endp = scache_multi_find_endp; + sp->scache->save_dest = scache_multi_save_dest; + sp->scache->find_dest = scache_multi_find_dest; + sp->scache->size = scache_multi_size; + sp->scache->free = scache_multi_free; + + sp->dest_cache = htable_create(1); + sp->endp_cache = htable_create(1); + sp->sess_count = 0; + + return (sp->scache); +} |