Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: scache_multi.c,v 1.2 2017/02/14 01:16:45 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	scache_multi 3
      6 /* SUMMARY
      7 /*	multi-session cache
      8 /* SYNOPSIS
      9 /*	#include <scache.h>
     10 /* DESCRIPTION
     11 /*	SCACHE *scache_multi_create()
     12 /* DESCRIPTION
     13 /*	This module implements an in-memory, multi-session cache.
     14 /*
     15 /*	scache_multi_create() instantiates a session cache that
     16 /*	stores multiple sessions.
     17 /* DIAGNOSTICS
     18 /*	Fatal error: memory allocation problem;
     19 /*	panic: internal consistency failure.
     20 /* SEE ALSO
     21 /*	scache(3), generic session cache API
     22 /* LICENSE
     23 /* .ad
     24 /* .fi
     25 /*	The Secure Mailer license must be distributed with this software.
     26 /* AUTHOR(S)
     27 /*	Wietse Venema
     28 /*	IBM T.J. Watson Research
     29 /*	P.O. Box 704
     30 /*	Yorktown Heights, NY 10598, USA
     31 /*--*/
     32 
     33 /* System library. */
     34 
     35 #include <sys_defs.h>
     36 #include <unistd.h>
     37 #include <stddef.h>			/* offsetof() */
     38 #include <string.h>
     39 
     40 /* Utility library. */
     41 
     42 #include <msg.h>
     43 #include <ring.h>
     44 #include <htable.h>
     45 #include <vstring.h>
     46 #include <mymalloc.h>
     47 #include <events.h>
     48 
     49 /*#define msg_verbose 1*/
     50 
     51 /* Global library. */
     52 
     53 #include <scache.h>
     54 
     55 /* Application-specific. */
     56 
     57  /*
     58   * SCACHE_MULTI is a derived type from the SCACHE super-class.
     59   *
     60   * Each destination has an entry in the destination hash table, and each
     61   * destination->endpoint binding is kept in a circular list under its
     62   * destination hash table entry.
     63   *
     64   * Likewise, each endpoint has an entry in the endpoint hash table, and each
     65   * endpoint->session binding is kept in a circular list under its endpoint
     66   * hash table entry.
     67   *
     68   * We do not attempt to limit the number of destination or endpoint entries,
     69   * nor do we attempt to limit the number of sessions. Doing so would require
     70   * a write-through cache. Currently, the CTABLE cache module supports only
     71   * read-through caching.
     72   *
     73   * This is no problem because the number of cached destinations is limited by
     74   * design. Sites that specify a wild-card domain pattern, and thus cache
     75   * every session in recent history, may be in for a surprise.
     76   */
     77 typedef struct {
     78     SCACHE  scache[1];			/* super-class */
     79     HTABLE *dest_cache;			/* destination->endpoint bindings */
     80     HTABLE *endp_cache;			/* endpoint->session bindings */
     81     int     sess_count;			/* number of cached sessions */
     82 } SCACHE_MULTI;
     83 
     84  /*
     85   * Storage for a destination or endpoint list head. Each list head knows its
     86   * own hash table entry name, so that we can remove the list when it becomes
     87   * empty. List items are stored in a circular list under the list head.
     88   */
     89 typedef struct {
     90     RING    ring[1];			/* circular list linkage */
     91     char   *parent_key;			/* parent linkage: hash table */
     92     SCACHE_MULTI *cache;		/* parent linkage: cache */
     93 } SCACHE_MULTI_HEAD;
     94 
     95 #define RING_TO_MULTI_HEAD(p) RING_TO_APPL((p), SCACHE_MULTI_HEAD, ring)
     96 
     97  /*
     98   * Storage for a destination->endpoint binding. This is an element in a
     99   * circular list, whose list head specifies the destination.
    100   */
    101 typedef struct {
    102     RING    ring[1];			/* circular list linkage */
    103     SCACHE_MULTI_HEAD *head;		/* parent linkage: list head */
    104     char   *endp_label;			/* endpoint name */
    105     char   *dest_prop;			/* binding properties */
    106 } SCACHE_MULTI_DEST;
    107 
    108 #define RING_TO_MULTI_DEST(p) RING_TO_APPL((p), SCACHE_MULTI_DEST, ring)
    109 
    110 static void scache_multi_expire_dest(int, void *);
    111 
    112  /*
    113   * Storage for an endpoint->session binding. This is an element in a
    114   * circular list, whose list head specifies the endpoint.
    115   */
    116 typedef struct {
    117     RING    ring[1];			/* circular list linkage */
    118     SCACHE_MULTI_HEAD *head;		/* parent linkage: list head */
    119     int     fd;				/* cached session */
    120     char   *endp_prop;			/* binding properties */
    121 } SCACHE_MULTI_ENDP;
    122 
    123 #define RING_TO_MULTI_ENDP(p) RING_TO_APPL((p), SCACHE_MULTI_ENDP, ring)
    124 
    125 static void scache_multi_expire_endp(int, void *);
    126 
    127  /*
    128   * When deleting a circular list element, are we deleting the entire
    129   * circular list, or are we removing a single list element. We need this
    130   * distinction to avoid a re-entrancy problem between htable_delete() and
    131   * htable_free().
    132   */
    133 #define BOTTOM_UP	1		/* one item */
    134 #define TOP_DOWN	2		/* whole list */
    135 
    136 /* scache_multi_drop_endp - destroy endpoint->session binding */
    137 
    138 static void scache_multi_drop_endp(SCACHE_MULTI_ENDP *endp, int direction)
    139 {
    140     const char *myname = "scache_multi_drop_endp";
    141     SCACHE_MULTI_HEAD *head;
    142 
    143     if (msg_verbose)
    144 	msg_info("%s: endp_prop=%s fd=%d", myname,
    145 		 endp->endp_prop, endp->fd);
    146 
    147     /*
    148      * Stop the timer.
    149      */
    150     event_cancel_timer(scache_multi_expire_endp, (void *) endp);
    151 
    152     /*
    153      * In bottom-up mode, remove the list head from the endpoint hash when
    154      * the list becomes empty. Otherwise, remove the endpoint->session
    155      * binding from the list.
    156      */
    157     ring_detach(endp->ring);
    158     head = endp->head;
    159     head->cache->sess_count--;
    160     if (direction == BOTTOM_UP && ring_pred(head->ring) == head->ring)
    161 	htable_delete(head->cache->endp_cache, head->parent_key, myfree);
    162 
    163     /*
    164      * Destroy the endpoint->session binding.
    165      */
    166     if (endp->fd >= 0 && close(endp->fd) != 0)
    167 	msg_warn("%s: close(%d): %m", myname, endp->fd);
    168     myfree(endp->endp_prop);
    169 
    170     myfree((void *) endp);
    171 }
    172 
    173 /* scache_multi_expire_endp - event timer call-back */
    174 
    175 static void scache_multi_expire_endp(int unused_event, void *context)
    176 {
    177     SCACHE_MULTI_ENDP *endp = (SCACHE_MULTI_ENDP *) context;
    178 
    179     scache_multi_drop_endp(endp, BOTTOM_UP);
    180 }
    181 
    182 /* scache_multi_free_endp - hash table destructor call-back */
    183 
    184 static void scache_multi_free_endp(void *ptr)
    185 {
    186     SCACHE_MULTI_HEAD *head = (SCACHE_MULTI_HEAD *) ptr;
    187     SCACHE_MULTI_ENDP *endp;
    188     RING   *ring;
    189 
    190     /*
    191      * Delete each endpoint->session binding in the list, then delete the
    192      * list head. Note: this changes the list, so we must iterate carefully.
    193      */
    194     while ((ring = ring_succ(head->ring)) != head->ring) {
    195 	endp = RING_TO_MULTI_ENDP(ring);
    196 	scache_multi_drop_endp(endp, TOP_DOWN);
    197     }
    198     myfree((void *) head);
    199 }
    200 
    201 /* scache_multi_save_endp - save endpoint->session binding */
    202 
    203 static void scache_multi_save_endp(SCACHE *scache, int ttl,
    204 				           const char *endp_label,
    205 				           const char *endp_prop, int fd)
    206 {
    207     const char *myname = "scache_multi_save_endp";
    208     SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
    209     SCACHE_MULTI_HEAD *head;
    210     SCACHE_MULTI_ENDP *endp;
    211 
    212     if (ttl < 0)
    213 	msg_panic("%s: bad ttl: %d", myname, ttl);
    214 
    215     /*
    216      * Look up or instantiate the list head with the endpoint name.
    217      */
    218     if ((head = (SCACHE_MULTI_HEAD *)
    219 	 htable_find(sp->endp_cache, endp_label)) == 0) {
    220 	head = (SCACHE_MULTI_HEAD *) mymalloc(sizeof(*head));
    221 	ring_init(head->ring);
    222 	head->parent_key =
    223 	    htable_enter(sp->endp_cache, endp_label, (void *) head)->key;
    224 	head->cache = sp;
    225     }
    226 
    227     /*
    228      * Add the endpoint->session binding to the list. There can never be a
    229      * duplicate, because each session must have a different file descriptor.
    230      */
    231     endp = (SCACHE_MULTI_ENDP *) mymalloc(sizeof(*endp));
    232     endp->head = head;
    233     endp->fd = fd;
    234     endp->endp_prop = mystrdup(endp_prop);
    235     ring_prepend(head->ring, endp->ring);
    236     sp->sess_count++;
    237 
    238     /*
    239      * Make sure this binding will go away eventually.
    240      */
    241     event_request_timer(scache_multi_expire_endp, (void *) endp, ttl);
    242 
    243     if (msg_verbose)
    244 	msg_info("%s: endp_label=%s -> endp_prop=%s fd=%d",
    245 		 myname, endp_label, endp_prop, fd);
    246 }
    247 
    248 /* scache_multi_find_endp - look up session for named endpoint */
    249 
    250 static int scache_multi_find_endp(SCACHE *scache, const char *endp_label,
    251 				          VSTRING *endp_prop)
    252 {
    253     const char *myname = "scache_multi_find_endp";
    254     SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
    255     SCACHE_MULTI_HEAD *head;
    256     SCACHE_MULTI_ENDP *endp;
    257     RING   *ring;
    258     int     fd;
    259 
    260     /*
    261      * Look up the list head with the endpoint name.
    262      */
    263     if ((head = (SCACHE_MULTI_HEAD *)
    264 	 htable_find(sp->endp_cache, endp_label)) == 0) {
    265 	if (msg_verbose)
    266 	    msg_info("%s: no endpoint cache: endp_label=%s",
    267 		     myname, endp_label);
    268 	return (-1);
    269     }
    270 
    271     /*
    272      * Use the first available session. Remove the session from the cache
    273      * because we're giving it to someone else.
    274      */
    275     if ((ring = ring_succ(head->ring)) != head->ring) {
    276 	endp = RING_TO_MULTI_ENDP(ring);
    277 	fd = endp->fd;
    278 	endp->fd = -1;
    279 	vstring_strcpy(endp_prop, endp->endp_prop);
    280 	if (msg_verbose)
    281 	    msg_info("%s: found: endp_label=%s -> endp_prop=%s fd=%d",
    282 		     myname, endp_label, endp->endp_prop, fd);
    283 	scache_multi_drop_endp(endp, BOTTOM_UP);
    284 	return (fd);
    285     }
    286     if (msg_verbose)
    287 	msg_info("%s: not found: endp_label=%s", myname, endp_label);
    288     return (-1);
    289 }
    290 
    291 /* scache_multi_drop_dest - delete destination->endpoint binding */
    292 
    293 static void scache_multi_drop_dest(SCACHE_MULTI_DEST *dest, int direction)
    294 {
    295     const char *myname = "scache_multi_drop_dest";
    296     SCACHE_MULTI_HEAD *head;
    297 
    298     if (msg_verbose)
    299 	msg_info("%s: dest_prop=%s endp_label=%s",
    300 		 myname, dest->dest_prop, dest->endp_label);
    301 
    302     /*
    303      * Stop the timer.
    304      */
    305     event_cancel_timer(scache_multi_expire_dest, (void *) dest);
    306 
    307     /*
    308      * In bottom-up mode, remove the list head from the destination hash when
    309      * the list becomes empty. Otherwise, remove the destination->endpoint
    310      * binding from the list.
    311      */
    312     ring_detach(dest->ring);
    313     head = dest->head;
    314     if (direction == BOTTOM_UP && ring_pred(head->ring) == head->ring)
    315 	htable_delete(head->cache->dest_cache, head->parent_key, myfree);
    316 
    317     /*
    318      * Destroy the destination->endpoint binding.
    319      */
    320     myfree(dest->dest_prop);
    321     myfree(dest->endp_label);
    322 
    323     myfree((void *) dest);
    324 }
    325 
    326 /* scache_multi_expire_dest - event timer call-back */
    327 
    328 static void scache_multi_expire_dest(int unused_event, void *context)
    329 {
    330     SCACHE_MULTI_DEST *dest = (SCACHE_MULTI_DEST *) context;
    331 
    332     scache_multi_drop_dest(dest, BOTTOM_UP);
    333 }
    334 
    335 /* scache_multi_free_dest - hash table destructor call-back */
    336 
    337 static void scache_multi_free_dest(void *ptr)
    338 {
    339     SCACHE_MULTI_HEAD *head = (SCACHE_MULTI_HEAD *) ptr;
    340     SCACHE_MULTI_DEST *dest;
    341     RING   *ring;
    342 
    343     /*
    344      * Delete each destination->endpoint binding in the list, then delete the
    345      * list head. Note: this changes the list, so we must iterate carefully.
    346      */
    347     while ((ring = ring_succ(head->ring)) != head->ring) {
    348 	dest = RING_TO_MULTI_DEST(ring);
    349 	scache_multi_drop_dest(dest, TOP_DOWN);
    350     }
    351     myfree((void *) head);
    352 }
    353 
    354 /* scache_multi_save_dest - save destination->endpoint binding */
    355 
    356 static void scache_multi_save_dest(SCACHE *scache, int ttl,
    357 				           const char *dest_label,
    358 				           const char *dest_prop,
    359 				           const char *endp_label)
    360 {
    361     const char *myname = "scache_multi_save_dest";
    362     SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
    363     SCACHE_MULTI_HEAD *head;
    364     SCACHE_MULTI_DEST *dest;
    365     RING   *ring;
    366     int     refresh = 0;
    367 
    368     if (ttl < 0)
    369 	msg_panic("%s: bad ttl: %d", myname, ttl);
    370 
    371     /*
    372      * Look up or instantiate the list head with the destination name.
    373      */
    374     if ((head = (SCACHE_MULTI_HEAD *)
    375 	 htable_find(sp->dest_cache, dest_label)) == 0) {
    376 	head = (SCACHE_MULTI_HEAD *) mymalloc(sizeof(*head));
    377 	ring_init(head->ring);
    378 	head->parent_key =
    379 	    htable_enter(sp->dest_cache, dest_label, (void *) head)->key;
    380 	head->cache = sp;
    381     }
    382 
    383     /*
    384      * Look up or instantiate the destination->endpoint binding. Update the
    385      * expiration time if this destination->endpoint binding already exists.
    386      */
    387     RING_FOREACH(ring, head->ring) {
    388 	dest = RING_TO_MULTI_DEST(ring);
    389 	if (strcmp(dest->endp_label, endp_label) == 0
    390 	    && strcmp(dest->dest_prop, dest_prop) == 0) {
    391 	    refresh = 1;
    392 	    break;
    393 	}
    394     }
    395     if (refresh == 0) {
    396 	dest = (SCACHE_MULTI_DEST *) mymalloc(sizeof(*dest));
    397 	dest->head = head;
    398 	dest->endp_label = mystrdup(endp_label);
    399 	dest->dest_prop = mystrdup(dest_prop);
    400 	ring_prepend(head->ring, dest->ring);
    401     }
    402 
    403     /*
    404      * Make sure this binding will go away eventually.
    405      */
    406     event_request_timer(scache_multi_expire_dest, (void *) dest, ttl);
    407 
    408     if (msg_verbose)
    409 	msg_info("%s: dest_label=%s -> dest_prop=%s endp_label=%s%s",
    410 		 myname, dest_label, dest_prop, endp_label,
    411 		 refresh ? " (refreshed)" : "");
    412 }
    413 
    414 /* scache_multi_find_dest - look up session for named destination */
    415 
    416 static int scache_multi_find_dest(SCACHE *scache, const char *dest_label,
    417 				          VSTRING *dest_prop,
    418 				          VSTRING *endp_prop)
    419 {
    420     const char *myname = "scache_multi_find_dest";
    421     SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
    422     SCACHE_MULTI_HEAD *head;
    423     SCACHE_MULTI_DEST *dest;
    424     RING   *ring;
    425     int     fd;
    426 
    427     /*
    428      * Look up the list head with the destination name.
    429      */
    430     if ((head = (SCACHE_MULTI_HEAD *)
    431 	 htable_find(sp->dest_cache, dest_label)) == 0) {
    432 	if (msg_verbose)
    433 	    msg_info("%s: no destination cache: dest_label=%s",
    434 		     myname, dest_label);
    435 	return (-1);
    436     }
    437 
    438     /*
    439      * Search endpoints for the first available session.
    440      */
    441     RING_FOREACH(ring, head->ring) {
    442 	dest = RING_TO_MULTI_DEST(ring);
    443 	fd = scache_multi_find_endp(scache, dest->endp_label, endp_prop);
    444 	if (fd >= 0) {
    445 	    vstring_strcpy(dest_prop, dest->dest_prop);
    446 	    return (fd);
    447 	}
    448     }
    449     if (msg_verbose)
    450 	msg_info("%s: not found: dest_label=%s", myname, dest_label);
    451     return (-1);
    452 }
    453 
    454 /* scache_multi_size - size of multi-element cache object */
    455 
    456 static void scache_multi_size(SCACHE *scache, SCACHE_SIZE *size)
    457 {
    458     SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
    459 
    460     size->dest_count = sp->dest_cache->used;
    461     size->endp_count = sp->endp_cache->used;
    462     size->sess_count = sp->sess_count;
    463 }
    464 
    465 /* scache_multi_free - destroy multi-element cache object */
    466 
    467 static void scache_multi_free(SCACHE *scache)
    468 {
    469     SCACHE_MULTI *sp = (SCACHE_MULTI *) scache;
    470 
    471     htable_free(sp->dest_cache, scache_multi_free_dest);
    472     htable_free(sp->endp_cache, scache_multi_free_endp);
    473 
    474     myfree((void *) sp);
    475 }
    476 
    477 /* scache_multi_create - initialize */
    478 
    479 SCACHE *scache_multi_create(void)
    480 {
    481     SCACHE_MULTI *sp = (SCACHE_MULTI *) mymalloc(sizeof(*sp));
    482 
    483     sp->scache->save_endp = scache_multi_save_endp;
    484     sp->scache->find_endp = scache_multi_find_endp;
    485     sp->scache->save_dest = scache_multi_save_dest;
    486     sp->scache->find_dest = scache_multi_find_dest;
    487     sp->scache->size = scache_multi_size;
    488     sp->scache->free = scache_multi_free;
    489 
    490     sp->dest_cache = htable_create(1);
    491     sp->endp_cache = htable_create(1);
    492     sp->sess_count = 0;
    493 
    494     return (sp->scache);
    495 }
    496