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