1 /*	$NetBSD: scache_multi.c,v 1.1.1.1 2009/06/23 10:08:48 tron 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, char *);
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, char *);
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, (char *) 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((char *) endp);
171 }
172 
173 /* scache_multi_expire_endp - event timer call-back */
174 
175 static void scache_multi_expire_endp(int unused_event, char *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(char *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((char *) 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, (char *) 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, (char *) 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, (char *) 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((char *) dest);
324 }
325 
326 /* scache_multi_expire_dest - event timer call-back */
327 
328 static void scache_multi_expire_dest(int unused_event, char *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(char *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((char *) 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, (char *) 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, (char *) 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((char *) 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