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