1 /* $NetBSD: tls_scache.c,v 1.1.1.1 2009/06/23 10:08:57 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* tls_scache 3 6 /* SUMMARY 7 /* TLS session cache manager 8 /* SYNOPSIS 9 /* #include <tls_scache.h> 10 /* 11 /* TLS_SCACHE *tls_scache_open(dbname, cache_label, verbose, timeout) 12 /* const char *dbname 13 /* const char *cache_label; 14 /* int verbose; 15 /* int timeout; 16 /* 17 /* void tls_scache_close(cache) 18 /* TLS_SCACHE *cache; 19 /* 20 /* int tls_scache_lookup(cache, cache_id, out_session) 21 /* TLS_SCACHE *cache; 22 /* const char *cache_id; 23 /* VSTRING *out_session; 24 /* 25 /* int tls_scache_update(cache, cache_id, session, session_len) 26 /* TLS_SCACHE *cache; 27 /* const char *cache_id; 28 /* const char *session; 29 /* ssize_t session_len; 30 /* 31 /* int tls_scache_sequence(cache, first_next, out_cache_id, 32 /* VSTRING *out_session) 33 /* TLS_SCACHE *cache; 34 /* int first_next; 35 /* char **out_cache_id; 36 /* VSTRING *out_session; 37 /* 38 /* int tls_scache_delete(cache, cache_id) 39 /* TLS_SCACHE *cache; 40 /* const char *cache_id; 41 /* DESCRIPTION 42 /* This module maintains Postfix TLS session cache files. 43 /* each session is stored under a lookup key (hostname or 44 /* session ID). 45 /* 46 /* tls_scache_open() opens the specified TLS session cache 47 /* and returns a handle that must be used for subsequent 48 /* access. 49 /* 50 /* tls_scache_close() closes the specified TLS session cache 51 /* and releases memory that was allocated by tls_scache_open(). 52 /* 53 /* tls_scache_lookup() looks up the specified session in the 54 /* specified cache, and applies session timeout restrictions. 55 /* Entries that are too old are silently deleted. 56 /* 57 /* tls_scache_update() updates the specified TLS session cache 58 /* with the specified session information. 59 /* 60 /* tls_scache_sequence() iterates over the specified TLS session 61 /* cache and either returns the first or next entry that has not 62 /* timed out, or returns no data. Entries that are too old are 63 /* silently deleted. Specify TLS_SCACHE_SEQUENCE_NOTHING as the 64 /* third and last argument to disable saving of cache entry 65 /* content or cache entry ID information. This is useful when 66 /* purging expired entries. A result value of zero means that 67 /* the end of the cache was reached. 68 /* 69 /* tls_scache_delete() removes the specified cache entry from 70 /* the specified TLS session cache. 71 /* 72 /* Arguments: 73 /* .IP dbname 74 /* The base name of the session cache file. 75 /* .IP cache_label 76 /* A string that is used in logging and error messages. 77 /* .IP verbose 78 /* Do verbose logging of cache operations? (zero == no) 79 /* .IP timeout 80 /* The time after wich a session cache entry is considered too old. 81 /* .IP first_next 82 /* One of DICT_SEQ_FUN_FIRST (first cache element) or DICT_SEQ_FUN_NEXT 83 /* (next cache element). 84 /* .IP cache_id 85 /* Session cache lookup key. 86 /* .IP session 87 /* Storage for session information. 88 /* .IP session_len 89 /* The size of the session information in bytes. 90 /* .IP out_cache_id 91 /* .IP out_session 92 /* Storage for saving the cache_id or session information of the 93 /* current cache entry. 94 /* 95 /* Specify TLS_SCACHE_DONT_NEED_CACHE_ID to avoid saving 96 /* the session cache ID of the cache entry. 97 /* 98 /* Specify TLS_SCACHE_DONT_NEED_SESSION to avoid 99 /* saving the session information in the cache entry. 100 /* DIAGNOSTICS 101 /* These routines terminate with a fatal run-time error 102 /* for unrecoverable database errors. This allows the 103 /* program to restart and reset the database to an 104 /* empty initial state. 105 /* 106 /* tls_scache_open() never returns on failure. All other 107 /* functions return non-zero on success, zero when the 108 /* operation could not be completed. 109 /* LICENSE 110 /* .ad 111 /* .fi 112 /* The Secure Mailer license must be distributed with this software. 113 /* AUTHOR(S) 114 /* Wietse Venema 115 /* IBM T.J. Watson Research 116 /* P.O. Box 704 117 /* Yorktown Heights, NY 10598, USA 118 /*--*/ 119 120 /* System library. */ 121 122 #include <sys_defs.h> 123 124 #ifdef USE_TLS 125 126 #include <string.h> 127 #include <stddef.h> 128 129 /* Utility library. */ 130 131 #include <msg.h> 132 #include <dict.h> 133 #include <stringops.h> 134 #include <mymalloc.h> 135 #include <hex_code.h> 136 #include <myflock.h> 137 #include <vstring.h> 138 139 /* Global library. */ 140 141 /* TLS library. */ 142 143 #include <tls_scache.h> 144 145 /* Application-specific. */ 146 147 /* 148 * Session cache entry format. 149 */ 150 typedef struct { 151 time_t timestamp; /* time when saved */ 152 char session[1]; /* actually a bunch of bytes */ 153 } TLS_SCACHE_ENTRY; 154 155 /* 156 * SLMs. 157 */ 158 #define STR(x) vstring_str(x) 159 #define LEN(x) VSTRING_LEN(x) 160 161 /* tls_scache_encode - encode TLS session cache entry */ 162 163 static VSTRING *tls_scache_encode(TLS_SCACHE *cp, const char *cache_id, 164 const char *session, 165 ssize_t session_len) 166 { 167 TLS_SCACHE_ENTRY *entry; 168 VSTRING *hex_data; 169 ssize_t binary_data_len; 170 171 /* 172 * Assemble the TLS session cache entry. 173 * 174 * We could eliminate some copying by using incremental encoding, but 175 * sessions are so small that it really does not matter. 176 */ 177 binary_data_len = session_len + offsetof(TLS_SCACHE_ENTRY, session); 178 entry = (TLS_SCACHE_ENTRY *) mymalloc(binary_data_len); 179 entry->timestamp = time((time_t *) 0); 180 memcpy(entry->session, session, session_len); 181 182 /* 183 * Encode the TLS session cache entry. 184 */ 185 hex_data = vstring_alloc(2 * binary_data_len + 1); 186 hex_encode(hex_data, (char *) entry, binary_data_len); 187 188 /* 189 * Logging. 190 */ 191 if (cp->verbose) 192 msg_info("write %s TLS cache entry %s: time=%ld [data %ld bytes]", 193 cp->cache_label, cache_id, (long) entry->timestamp, 194 (long) session_len); 195 196 /* 197 * Clean up. 198 */ 199 myfree((char *) entry); 200 201 return (hex_data); 202 } 203 204 /* tls_scache_decode - decode TLS session cache entry */ 205 206 static int tls_scache_decode(TLS_SCACHE *cp, const char *cache_id, 207 const char *hex_data, ssize_t hex_data_len, 208 VSTRING *out_session) 209 { 210 TLS_SCACHE_ENTRY *entry; 211 VSTRING *bin_data; 212 213 /* 214 * Sanity check. 215 */ 216 if (hex_data_len < 2 * (offsetof(TLS_SCACHE_ENTRY, session))) { 217 msg_warn("%s TLS cache: truncated entry for %s: %.100s", 218 cp->cache_label, cache_id, hex_data); 219 return (0); 220 } 221 222 /* 223 * Disassemble the TLS session cache entry. 224 * 225 * No early returns or we have a memory leak. 226 */ 227 #define FREE_AND_RETURN(ptr, x) { vstring_free(ptr); return (x); } 228 229 bin_data = vstring_alloc(hex_data_len / 2 + 1); 230 if (hex_decode(bin_data, hex_data, hex_data_len) == 0) { 231 msg_warn("%s TLS cache: malformed entry for %s: %.100s", 232 cp->cache_label, cache_id, hex_data); 233 FREE_AND_RETURN(bin_data, 0); 234 } 235 entry = (TLS_SCACHE_ENTRY *) STR(bin_data); 236 237 /* 238 * Logging. 239 */ 240 if (cp->verbose) 241 msg_info("read %s TLS cache entry %s: time=%ld [data %ld bytes]", 242 cp->cache_label, cache_id, (long) entry->timestamp, 243 (long) (LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session))); 244 245 /* 246 * Other mandatory restrictions. 247 */ 248 if (entry->timestamp + cp->timeout < time((time_t *) 0)) 249 FREE_AND_RETURN(bin_data, 0); 250 251 /* 252 * Optional output. 253 */ 254 if (out_session != 0) 255 vstring_memcpy(out_session, entry->session, 256 LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session)); 257 258 /* 259 * Clean up. 260 */ 261 FREE_AND_RETURN(bin_data, 1); 262 } 263 264 /* tls_scache_lookup - load session from cache */ 265 266 int tls_scache_lookup(TLS_SCACHE *cp, const char *cache_id, 267 VSTRING *session) 268 { 269 const char *hex_data; 270 271 /* 272 * Logging. 273 */ 274 if (cp->verbose) 275 msg_info("lookup %s session id=%s", cp->cache_label, cache_id); 276 277 /* 278 * Initialize. Don't leak data. 279 */ 280 if (session) 281 VSTRING_RESET(session); 282 283 /* 284 * Search the cache database. 285 */ 286 if ((hex_data = dict_get(cp->db, cache_id)) == 0) 287 return (0); 288 289 /* 290 * Decode entry and delete if expired or malformed. 291 */ 292 if (tls_scache_decode(cp, cache_id, hex_data, strlen(hex_data), 293 session) == 0) { 294 tls_scache_delete(cp, cache_id); 295 return (0); 296 } else { 297 return (1); 298 } 299 } 300 301 /* tls_scache_update - save session to cache */ 302 303 int tls_scache_update(TLS_SCACHE *cp, const char *cache_id, 304 const char *buf, ssize_t len) 305 { 306 VSTRING *hex_data; 307 308 /* 309 * Logging. 310 */ 311 if (cp->verbose) 312 msg_info("put %s session id=%s [data %ld bytes]", 313 cp->cache_label, cache_id, (long) len); 314 315 /* 316 * Encode the cache entry. 317 */ 318 hex_data = tls_scache_encode(cp, cache_id, buf, len); 319 320 /* 321 * Store the cache entry. 322 * 323 * XXX Berkeley DB supports huge database keys and values. SDBM seems to 324 * have a finite limit, and DBM simply can't be used at all. 325 */ 326 dict_put(cp->db, cache_id, STR(hex_data)); 327 328 /* 329 * Clean up. 330 */ 331 vstring_free(hex_data); 332 333 return (1); 334 } 335 336 /* tls_scache_sequence - get first/next TLS session cache entry */ 337 338 int tls_scache_sequence(TLS_SCACHE *cp, int first_next, 339 char **out_cache_id, 340 VSTRING *out_session) 341 { 342 const char *member; 343 const char *value; 344 char *saved_cursor; 345 int found_entry; 346 int keep_entry; 347 char *saved_member; 348 349 /* 350 * XXX Deleting entries while enumerating a map can he tricky. Some map 351 * types have a concept of cursor and support a "delete the current 352 * element" operation. Some map types without cursors don't behave well 353 * when the current first/next entry is deleted (example: with Berkeley 354 * DB < 2, the "next" operation produces garbage). To avoid trouble, we 355 * delete an expired entry after advancing the current first/next 356 * position beyond it, and ignore client requests to delete the current 357 * entry. 358 */ 359 360 /* 361 * Find the first or next database entry. Activate the passivated entry 362 * and check the time stamp. Schedule the entry for deletion if it is too 363 * old. 364 * 365 * Save the member (cache id) so that it will not be clobbered by the 366 * tls_scache_lookup() call below. 367 */ 368 found_entry = (dict_seq(cp->db, first_next, &member, &value) == 0); 369 if (found_entry) { 370 keep_entry = tls_scache_decode(cp, member, value, strlen(value), 371 out_session); 372 if (keep_entry && out_cache_id) 373 *out_cache_id = mystrdup(member); 374 saved_member = mystrdup(member); 375 } 376 377 /* 378 * Delete behind. This is a no-op if an expired cache entry was updated 379 * in the mean time. Use the saved lookup criteria so that the "delete 380 * behind" operation works as promised. 381 */ 382 if (cp->flags & TLS_SCACHE_FLAG_DEL_SAVED_CURSOR) { 383 cp->flags &= ~TLS_SCACHE_FLAG_DEL_SAVED_CURSOR; 384 saved_cursor = cp->saved_cursor; 385 cp->saved_cursor = 0; 386 tls_scache_lookup(cp, saved_cursor, (VSTRING *) 0); 387 myfree(saved_cursor); 388 } 389 390 /* 391 * Otherwise, clean up if this is not the first iteration. 392 */ 393 else { 394 if (cp->saved_cursor) 395 myfree(cp->saved_cursor); 396 cp->saved_cursor = 0; 397 } 398 399 /* 400 * Protect the current first/next entry against explicit or implied 401 * client delete requests, and schedule a bad or expired entry for 402 * deletion. Save the lookup criteria so that the "delete behind" 403 * operation will work as promised. 404 */ 405 if (found_entry) { 406 cp->saved_cursor = saved_member; 407 if (keep_entry == 0) 408 cp->flags |= TLS_SCACHE_FLAG_DEL_SAVED_CURSOR; 409 } 410 return (found_entry); 411 } 412 413 /* tls_scache_delete - delete session from cache */ 414 415 int tls_scache_delete(TLS_SCACHE *cp, const char *cache_id) 416 { 417 418 /* 419 * Logging. 420 */ 421 if (cp->verbose) 422 msg_info("delete %s session id=%s", cp->cache_label, cache_id); 423 424 /* 425 * Do it, unless we would delete the current first/next entry. Some map 426 * types don't have cursors, and some of those don't behave when the 427 * "current" entry is deleted. 428 */ 429 return ((cp->saved_cursor != 0 && strcmp(cp->saved_cursor, cache_id) == 0) 430 || dict_del(cp->db, cache_id) == 0); 431 } 432 433 /* tls_scache_open - open TLS session cache file */ 434 435 TLS_SCACHE *tls_scache_open(const char *dbname, const char *cache_label, 436 int verbose, int timeout) 437 { 438 TLS_SCACHE *cp; 439 DICT *dict; 440 441 /* 442 * Logging. 443 */ 444 if (verbose) 445 msg_info("open %s TLS cache %s", cache_label, dbname); 446 447 /* 448 * Open the dictionary with O_TRUNC, so that we never have to worry about 449 * opening a damaged file after some process terminated abnormally. 450 */ 451 #ifdef SINGLE_UPDATER 452 #define DICT_FLAGS (DICT_FLAG_DUP_REPLACE) 453 #else 454 #define DICT_FLAGS \ 455 (DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE) 456 #endif 457 458 dict = dict_open(dbname, O_RDWR | O_CREAT | O_TRUNC, DICT_FLAGS); 459 460 /* 461 * Sanity checks. 462 */ 463 if (dict->lock_fd < 0) 464 msg_fatal("dictionary %s is not a regular file", dbname); 465 #ifdef SINGLE_UPDATER 466 if (myflock(dict->lock_fd, INTERNAL_LOCK, 467 MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0) 468 msg_fatal("cannot lock dictionary %s for exclusive use: %m", dbname); 469 #endif 470 if (dict->update == 0) 471 msg_fatal("dictionary %s does not support update operations", dbname); 472 if (dict->delete == 0) 473 msg_fatal("dictionary %s does not support delete operations", dbname); 474 if (dict->sequence == 0) 475 msg_fatal("dictionary %s does not support sequence operations", dbname); 476 477 /* 478 * Create the TLS_SCACHE object. 479 */ 480 cp = (TLS_SCACHE *) mymalloc(sizeof(*cp)); 481 cp->flags = 0; 482 cp->db = dict; 483 cp->cache_label = mystrdup(cache_label); 484 cp->verbose = verbose; 485 cp->timeout = timeout; 486 cp->saved_cursor = 0; 487 488 return (cp); 489 } 490 491 /* tls_scache_close - close TLS session cache file */ 492 493 void tls_scache_close(TLS_SCACHE *cp) 494 { 495 496 /* 497 * Logging. 498 */ 499 if (cp->verbose) 500 msg_info("close %s TLS cache %s", cp->cache_label, cp->db->name); 501 502 /* 503 * Destroy the TLS_SCACHE object. 504 */ 505 dict_close(cp->db); 506 myfree(cp->cache_label); 507 if (cp->saved_cursor) 508 myfree(cp->saved_cursor); 509 myfree((char *) cp); 510 } 511 512 #endif 513