/*++
/* NAME
/*	tlsmgr 8
/* SUMMARY
/*	Postfix TLS session cache and PRNG manager
/* SYNOPSIS
/*	\fBtlsmgr\fR [generic Postfix daemon options]
/* DESCRIPTION
/*	The \fBtlsmgr\fR(8) manages the Postfix TLS session caches.
/*	It stores and retrieves cache entries on request by
/*	\fBsmtpd\fR(8) and \fBsmtp\fR(8) processes, and periodically
/*	removes entries that have expired.
/*
/*	The \fBtlsmgr\fR(8) also manages the PRNG (pseudo random number
/*	generator) pool. It answers queries by the \fBsmtpd\fR(8)
/*	and \fBsmtp\fR(8)
/*	processes to seed their internal PRNG pools.
/*
/*	The \fBtlsmgr\fR(8)'s PRNG pool is initially seeded from
/*	an external source (EGD, /dev/urandom, or regular file).
/*	It is updated at configurable pseudo-random intervals with
/*	data from the external source. It is updated periodically
/*	with data from TLS session cache entries and with the time
/*	of day, and is updated with the time of day whenever a
/*	process requests \fBtlsmgr\fR(8) service.
/*
/*	The \fBtlsmgr\fR(8) saves the PRNG state to an exchange file
/*	periodically and when the process terminates, and reads
/*	the exchange file when initializing its PRNG.
/* SECURITY
/* .ad
/* .fi
/*	The \fBtlsmgr\fR(8) is not security-sensitive. The code that maintains
/*	the external and internal PRNG pools does not "trust" the
/*	data that it manipulates, and the code that maintains the
/*	TLS session cache does not touch the contents of the cached
/*	entries, except for seeding its internal PRNG pool.
/*
/*	The \fBtlsmgr\fR(8) can be run chrooted and with reduced privileges.
/*	At process startup it connects to the entropy source and
/*	exchange file, and creates or truncates the optional TLS
/*	session cache files.
/*
/*	With Postfix version 2.5 and later, the \fBtlsmgr\fR(8) no
/*	longer uses root privileges when opening cache files. These
/*	files should now be stored under the Postfix-owned
/*	\fBdata_directory\fR.  As a migration aid, an attempt to
/*	open a cache file under a non-Postfix directory is redirected
/*	to the Postfix-owned \fBdata_directory\fR, and a warning
/*	is logged.
/* DIAGNOSTICS
/*	Problems and transactions are logged to \fBsyslogd\fR(8)
/*	or \fBpostlogd\fR(8).
/* BUGS
/*	There is no automatic means to limit the number of entries in the
/*	TLS session caches and/or the size of the TLS cache files.
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
/*	Changes to \fBmain.cf\fR are not picked up automatically,
/*	because \fBtlsmgr\fR(8) is a persistent processes.  Use the
/*	command "\fBpostfix reload\fR" after a configuration change.
/*
/*	The text below provides only a parameter summary. See
/*	\fBpostconf\fR(5) for more details including examples.
/* TLS SESSION CACHE
/* .ad
/* .fi
/* .IP "\fBlmtp_tls_loglevel (0)\fR"
/*	The LMTP-specific version of the smtp_tls_loglevel
/*	configuration parameter.
/* .IP "\fBlmtp_tls_session_cache_database (empty)\fR"
/*	The LMTP-specific version of the smtp_tls_session_cache_database
/*	configuration parameter.
/* .IP "\fBlmtp_tls_session_cache_timeout (3600s)\fR"
/*	The LMTP-specific version of the smtp_tls_session_cache_timeout
/*	configuration parameter.
/* .IP "\fBsmtp_tls_loglevel (0)\fR"
/*	Enable additional Postfix SMTP client logging of TLS activity.
/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR"
/*	Name of the file containing the optional Postfix SMTP client
/*	TLS session cache.
/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR"
/*	The expiration time of Postfix SMTP client TLS session cache
/*	information.
/* .IP "\fBsmtpd_tls_loglevel (0)\fR"
/*	Enable additional Postfix SMTP server logging of TLS activity.
/* .IP "\fBsmtpd_tls_session_cache_database (empty)\fR"
/*	Name of the file containing the optional Postfix SMTP server
/*	TLS session cache.
/* .IP "\fBsmtpd_tls_session_cache_timeout (3600s)\fR"
/*	The expiration time of Postfix SMTP server TLS session cache
/*	information.
/* PSEUDO RANDOM NUMBER GENERATOR
/* .ad
/* .fi
/* .IP "\fBtls_random_source (see 'postconf -d' output)\fR"
/*	The external entropy source for the in-memory \fBtlsmgr\fR(8) pseudo
/*	random number generator (PRNG) pool.
/* .IP "\fBtls_random_bytes (32)\fR"
/*	The number of bytes that \fBtlsmgr\fR(8) reads from $tls_random_source
/*	when (re)seeding the in-memory pseudo random number generator (PRNG)
/*	pool.
/* .IP "\fBtls_random_exchange_name (see 'postconf -d' output)\fR"
/*	Name of the pseudo random number generator (PRNG) state file
/*	that is maintained by \fBtlsmgr\fR(8).
/* .IP "\fBtls_random_prng_update_period (3600s)\fR"
/*	The time between attempts by \fBtlsmgr\fR(8) to save the state of
/*	the pseudo random number generator (PRNG) to the file specified
/*	with $tls_random_exchange_name.
/* .IP "\fBtls_random_reseed_period (3600s)\fR"
/*	The maximal time between attempts by \fBtlsmgr\fR(8) to re-seed the
/*	in-memory pseudo random number generator (PRNG) pool from external
/*	sources.
/* MISCELLANEOUS CONTROLS
/* .ad
/* .fi
/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
/*	The default location of the Postfix main.cf and master.cf
/*	configuration files.
/* .IP "\fBdata_directory (see 'postconf -d' output)\fR"
/*	The directory with Postfix-writable data files (for example:
/*	caches, pseudo-random numbers).
/* .IP "\fBdaemon_timeout (18000s)\fR"
/*	How much time a Postfix daemon process may take to handle a
/*	request before it is terminated by a built-in watchdog timer.
/* .IP "\fBprocess_id (read-only)\fR"
/*	The process ID of a Postfix command or daemon process.
/* .IP "\fBprocess_name (read-only)\fR"
/*	The process name of a Postfix command or daemon process.
/* .IP "\fBsyslog_facility (mail)\fR"
/*	The syslog facility of Postfix logging.
/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
/*	A prefix that is prepended to the process name in syslog
/*	records, so that, for example, "smtpd" becomes "prefix/smtpd".
/* .PP
/*	Available in Postfix 3.3 and later:
/* .IP "\fBservice_name (read-only)\fR"
/*	The master.cf service name of a Postfix daemon process.
/* SEE ALSO
/*	smtp(8), Postfix SMTP client
/*	smtpd(8), Postfix SMTP server
/*	postconf(5), configuration parameters
/*	master(5), generic daemon options
/*	master(8), process manager
/*	postlogd(8), Postfix logging
/*	syslogd(8), system logging
/* README FILES
/* .ad
/* .fi
/*	Use "\fBpostconf readme_directory\fR" or
/*	"\fBpostconf html_directory\fR" to locate this information.
/* .na
/* .nf
/*	TLS_README, Postfix TLS configuration and operation
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* HISTORY
/*	This service was introduced with Postfix version 2.2.
/* AUTHOR(S)
/*	Lutz Jaenicke
/*	BTU Cottbus
/*	Allgemeine Elektrotechnik
/*	Universitaetsplatz 3-4
/*	D-03044 Cottbus, Germany
/*
/*	Adapted by:
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>			/* gettimeofday, not POSIX */
#include <limits.h>

#ifndef UCHAR_MAX
#define UCHAR_MAX 0xff
#endif

/* OpenSSL library. */

#ifdef USE_TLS
#include <openssl/rand.h>		/* For the PRNG */
#endif

/* Utility library. */

#include <msg.h>
#include <events.h>
#include <stringops.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <vstream.h>
#include <vstring.h>
#include <vstring_vstream.h>
#include <attr.h>
#include <set_eugid.h>
#include <htable.h>
#include <warn_stat.h>

/* Global library. */

#include <mail_conf.h>
#include <mail_params.h>
#include <mail_version.h>
#include <mail_proto.h>
#include <data_redirect.h>

/* Master process interface. */

#include <master_proto.h>
#include <mail_server.h>

/* TLS library. */

#ifdef USE_TLS
#include <tls_mgr.h>
#define TLS_INTERNAL
#include <tls.h>			/* TLS_MGR_SCACHE_<type> */
#include <tls_prng.h>
#include <tls_scache.h>

/* Application-specific. */

 /*
  * Tunables.
  */
char   *var_tls_rand_source;
int     var_tls_rand_bytes;
int     var_tls_reseed_period;
int     var_tls_prng_exch_period;
char   *var_smtpd_tls_loglevel;
char   *var_smtpd_tls_scache_db;
int     var_smtpd_tls_scache_timeout;
char   *var_smtp_tls_loglevel;
char   *var_smtp_tls_scache_db;
int     var_smtp_tls_scache_timeout;
char   *var_lmtp_tls_loglevel;
char   *var_lmtp_tls_scache_db;
int     var_lmtp_tls_scache_timeout;
char   *var_tls_rand_exch_name;

 /*
  * Bound the time that we are willing to wait for an I/O operation. This
  * produces better error messages than waiting until the watchdog timer
  * kills the process.
  */
#define TLS_MGR_TIMEOUT	10

 /*
  * State for updating the PRNG exchange file.
  */
static TLS_PRNG_SRC *rand_exch;

 /*
  * State for seeding the internal PRNG from external source.
  */
static TLS_PRNG_SRC *rand_source_dev;
static TLS_PRNG_SRC *rand_source_egd;
static TLS_PRNG_SRC *rand_source_file;

 /*
  * The external entropy source type is encoded in the source name. The
  * obvious alternative is to have separate configuration parameters per
  * source type, so that one process can query multiple external sources.
  */
#define DEV_PREF "dev:"
#define DEV_PREF_LEN (sizeof((DEV_PREF)) - 1)
#define DEV_PATH(dev) ((dev) + DEV_PREF_LEN)

#define EGD_PREF "egd:"
#define EGD_PREF_LEN (sizeof((EGD_PREF)) - 1)
#define EGD_PATH(egd) ((egd) + EGD_PREF_LEN)

 /*
  * State for TLS session caches.
  */
typedef struct {
    char   *cache_label;		/* cache short-hand name */
    TLS_SCACHE *cache_info;		/* cache handle */
    int     cache_active;		/* cache status */
    char  **cache_db;			/* main.cf parameter value */
    const char *log_param;		/* main.cf parameter name */
    char  **log_level;			/* main.cf parameter value */
    int    *cache_timeout;		/* main.cf parameter value */
} TLSMGR_SCACHE;

static TLSMGR_SCACHE cache_table[] = {
    TLS_MGR_SCACHE_SMTPD, 0, 0, &var_smtpd_tls_scache_db,
    VAR_SMTPD_TLS_LOGLEVEL,
    &var_smtpd_tls_loglevel, &var_smtpd_tls_scache_timeout,
    TLS_MGR_SCACHE_SMTP, 0, 0, &var_smtp_tls_scache_db,
    VAR_SMTP_TLS_LOGLEVEL,
    &var_smtp_tls_loglevel, &var_smtp_tls_scache_timeout,
    TLS_MGR_SCACHE_LMTP, 0, 0, &var_lmtp_tls_scache_db,
    VAR_LMTP_TLS_LOGLEVEL,
    &var_lmtp_tls_loglevel, &var_lmtp_tls_scache_timeout,
    0,
};

#define	smtpd_cache	(cache_table[0])

 /*
  * SLMs.
  */
#define STR(x)		vstring_str(x)
#define LEN(x)		VSTRING_LEN(x)
#define STREQ(x, y)	(strcmp((x), (y)) == 0)

/* tlsmgr_prng_exch_event - update PRNG exchange file */

static void tlsmgr_prng_exch_event(int unused_event, void *dummy)
{
    const char *myname = "tlsmgr_prng_exch_event";
    unsigned char randbyte;
    int     next_period;
    struct stat st;

    if (msg_verbose)
	msg_info("%s: update PRNG exchange file", myname);

    /*
     * Sanity check. If the PRNG exchange file was removed, there is no point
     * updating it further. Restart the process and update the new file.
     */
    if (fstat(rand_exch->fd, &st) < 0)
	msg_fatal("cannot fstat() the PRNG exchange file: %m");
    if (st.st_nlink == 0) {
	msg_warn("PRNG exchange file was removed -- exiting to reopen");
	sleep(1);
	exit(0);
    }
    tls_prng_exch_update(rand_exch);

    /*
     * Make prediction difficult for outsiders and calculate the time for the
     * next execution randomly.
     */
    if (RAND_bytes(&randbyte, 1) < 0)
	randbyte = getpid() & 0xff;
    next_period = (var_tls_prng_exch_period * randbyte) / UCHAR_MAX;
    event_request_timer(tlsmgr_prng_exch_event, dummy, next_period);
}

/* tlsmgr_reseed_event - re-seed the internal PRNG pool */

static void tlsmgr_reseed_event(int unused_event, void *dummy)
{
    int     next_period;
    unsigned char randbyte;
    int     must_exit = 0;

    /*
     * Reseed the internal PRNG from external source. Errors are recoverable.
     * We simply restart and reconnect without making a fuss. This is OK
     * because we do require that exchange file updates succeed. The exchange
     * file is the only entropy source that really matters in the long term.
     * 
     * If the administrator specifies an external randomness source that we
     * could not open upon start-up, restart to see if we can open it now
     * (and log a nagging warning if we can't).
     */
    if (*var_tls_rand_source) {

	/*
	 * Source is a random device.
	 */
	if (rand_source_dev) {
	    if (tls_prng_dev_read(rand_source_dev, var_tls_rand_bytes) <= 0) {
		msg_info("cannot read from entropy device %s: %m -- "
			 "exiting to reopen", DEV_PATH(var_tls_rand_source));
		must_exit = 1;
	    }
	}

	/*
	 * Source is an EGD compatible socket.
	 */
	else if (rand_source_egd) {
	    if (tls_prng_egd_read(rand_source_egd, var_tls_rand_bytes) <= 0) {
		msg_info("lost connection to EGD server %s -- "
		     "exiting to reconnect", EGD_PATH(var_tls_rand_source));
		must_exit = 1;
	    }
	}

	/*
	 * Source is a regular file. Read the content once and close the
	 * file.
	 */
	else if (rand_source_file) {
	    if (tls_prng_file_read(rand_source_file, var_tls_rand_bytes) <= 0)
		msg_warn("cannot read from entropy file %s: %m",
			 var_tls_rand_source);
	    tls_prng_file_close(rand_source_file);
	    rand_source_file = 0;
	    var_tls_rand_source[0] = 0;
	}

	/*
	 * Could not open the external source upon start-up. See if we can
	 * open it this time. Save PRNG state before we exit.
	 */
	else {
	    msg_info("exiting to reopen external entropy source %s",
		     var_tls_rand_source);
	    must_exit = 1;
	}
    }

    /*
     * Save PRNG state in case we must exit.
     */
    if (must_exit) {
	if (rand_exch)
	    tls_prng_exch_update(rand_exch);
	sleep(1);
	exit(0);
    }

    /*
     * Make prediction difficult for outsiders and calculate the time for the
     * next execution randomly.
     */
    RAND_bytes(&randbyte, 1);
    next_period = (var_tls_reseed_period * randbyte) / UCHAR_MAX;
    event_request_timer(tlsmgr_reseed_event, dummy, next_period);
}

/* tlsmgr_cache_run_event - start TLS session cache scan */

static void tlsmgr_cache_run_event(int unused_event, void *ctx)
{
    const char *myname = "tlsmgr_cache_run_event";
    TLSMGR_SCACHE *cache = (TLSMGR_SCACHE *) ctx;

    /*
     * This routine runs when it is time for another TLS session cache scan.
     * Make sure this routine gets called again in the future.
     * 
     * Don't start a new scan when the timer goes off while cache cleanup is
     * still in progress.
     */
    if (cache->cache_info->verbose)
	msg_info("%s: start TLS %s session cache cleanup",
		 myname, cache->cache_label);

    if (cache->cache_active == 0)
	cache->cache_active =
	    tls_scache_sequence(cache->cache_info, DICT_SEQ_FUN_FIRST,
				TLS_SCACHE_SEQUENCE_NOTHING);

    event_request_timer(tlsmgr_cache_run_event, (void *) cache,
			cache->cache_info->timeout);
}

/* tlsmgr_key - return matching or current RFC 5077 session ticket keys */

static int tlsmgr_key(VSTRING *buffer, int timeout)
{
    TLS_TICKET_KEY *key;
    TLS_TICKET_KEY tmp;
    unsigned char *name;
    time_t  now = time((time_t *) 0);

    /* In tlsmgr requests we encode null key names as empty strings. */
    name = LEN(buffer) ? (unsigned char *) STR(buffer) : 0;

    /*
     * Each key's encrypt and subsequent decrypt-only timeout is half of the
     * total session timeout.
     */
    timeout /= 2;

    /* Attempt to locate existing key */
    if ((key = tls_scache_key(name, now, timeout)) == 0) {
	if (name == 0) {
	    /* Create new encryption key */
	    if (RAND_bytes(tmp.name, TLS_TICKET_NAMELEN) <= 0
		|| RAND_bytes(tmp.bits, TLS_TICKET_KEYLEN) <= 0
		|| RAND_bytes(tmp.hmac, TLS_TICKET_MACLEN) <= 0)
		return (TLS_MGR_STAT_ERR);
	    tmp.tout = now + timeout - 1;
	    key = tls_scache_key_rotate(&tmp);
	} else {
	    /* No matching decryption key found */
	    return (TLS_MGR_STAT_ERR);
	}
    }
    /* Return value overrites name buffer */
    vstring_memcpy(buffer, (char *) key, sizeof(*key));
    return (TLS_MGR_STAT_OK);
}

/* tlsmgr_loop - TLS manager main loop */

static int tlsmgr_loop(char *unused_name, char **unused_argv)
{
    struct timeval tv;
    int     active = 0;
    TLSMGR_SCACHE *ent;

    /*
     * Update the PRNG pool with the time of day. We do it here after every
     * event (including internal timer events and external client request
     * events), instead of doing it in individual event call-back routines.
     */
    GETTIMEOFDAY(&tv);
    RAND_seed(&tv, sizeof(struct timeval));

    /*
     * This routine runs as part of the event handling loop, after the event
     * manager has delivered a timer or I/O event, or after it has waited for
     * a specified amount of time. The result value of tlsmgr_loop()
     * specifies how long the event manager should wait for the next event.
     * 
     * We use this loop to interleave TLS session cache cleanup with other
     * activity. Interleaved processing is needed when we use a client-server
     * protocol for entropy and session state exchange with smtp(8) and
     * smtpd(8) processes.
     */
#define DONT_WAIT	0
#define WAIT_FOR_EVENT	(-1)

    for (ent = cache_table; ent->cache_label; ++ent) {
	if (ent->cache_info && ent->cache_active)
	    active |= ent->cache_active =
		tls_scache_sequence(ent->cache_info, DICT_SEQ_FUN_NEXT,
				    TLS_SCACHE_SEQUENCE_NOTHING);
    }

    return (active ? DONT_WAIT : WAIT_FOR_EVENT);
}

/* tlsmgr_request_receive - receive request */

static int tlsmgr_request_receive(VSTREAM *client_stream, VSTRING *request)
{
    int     count;

    /*
     * Kluge: choose the protocol depending on the request size.
     */
    if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) {
	msg_warn("timeout while waiting for data from %s",
		 VSTREAM_PATH(client_stream));
	return (-1);
    }
    if ((count = peekfd(vstream_fileno(client_stream))) < 0) {
	msg_warn("cannot examine read buffer of %s: %m",
		 VSTREAM_PATH(client_stream));
	return (-1);
    }

    /*
     * Short request: master trigger. Use the string+null protocol.
     */
    if (count <= 2) {
	if (vstring_get_null(request, client_stream) == VSTREAM_EOF) {
	    msg_warn("end-of-input while reading request from %s: %m",
		     VSTREAM_PATH(client_stream));
	    return (-1);
	}
    }

    /*
     * Long request: real tlsmgr client. Use the attribute list protocol.
     */
    else {
	if (attr_scan(client_stream,
		      ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
		      RECV_ATTR_STR(TLS_MGR_ATTR_REQ, request),
		      ATTR_TYPE_END) != 1) {
	    return (-1);
	}
    }
    return (0);
}

/* tlsmgr_service - respond to external request */

static void tlsmgr_service(VSTREAM *client_stream, char *unused_service,
			           char **argv)
{
    static VSTRING *request = 0;
    static VSTRING *cache_type = 0;
    static VSTRING *cache_id = 0;
    static VSTRING *buffer = 0;
    int     len;
    static char wakeup[] = {		/* master wakeup request */
	TRIGGER_REQ_WAKEUP,
	0,
    };
    TLSMGR_SCACHE *ent;
    int     status = TLS_MGR_STAT_FAIL;

    /*
     * Sanity check. This service takes no command-line arguments.
     */
    if (argv[0])
	msg_fatal("unexpected command-line argument: %s", argv[0]);

    /*
     * Initialize. We're select threaded, so we can use static buffers.
     */
    if (request == 0) {
	request = vstring_alloc(10);
	cache_type = vstring_alloc(10);
	cache_id = vstring_alloc(10);
	buffer = vstring_alloc(10);
    }

    /*
     * This routine runs whenever a client connects to the socket dedicated
     * to the tlsmgr service (including wake up events sent by the master).
     * All connection-management stuff is handled by the common code in
     * multi_server.c.
     */
    if (tlsmgr_request_receive(client_stream, request) == 0) {

	/*
	 * Load session from cache.
	 */
	if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
			  RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
			  ATTR_TYPE_END) == 2) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    msg_warn("bogus cache type \"%s\" in \"%s\" request",
			     STR(cache_type), TLS_MGR_REQ_LOOKUP);
		    VSTRING_RESET(buffer);
		} else if (ent->cache_info == 0) {

		    /*
		     * Cache type valid, but not enabled
		     */
		    VSTRING_RESET(buffer);
		} else {
		    status = tls_scache_lookup(ent->cache_info,
					       STR(cache_id), buffer) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION,
				      LEN(buffer), STR(buffer)),
		       ATTR_TYPE_END);
	}

	/*
	 * Save session to cache.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
			  RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
			  RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buffer),
			  ATTR_TYPE_END) == 3) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    msg_warn("bogus cache type \"%s\" in \"%s\" request",
			     STR(cache_type), TLS_MGR_REQ_UPDATE);
		} else if (ent->cache_info != 0) {
		    status =
			tls_scache_update(ent->cache_info, STR(cache_id),
					  STR(buffer), LEN(buffer)) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       ATTR_TYPE_END);
	}

	/*
	 * Delete session from cache.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
			  RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
			  ATTR_TYPE_END) == 2) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    msg_warn("bogus cache type \"%s\" in \"%s\" request",
			     STR(cache_type), TLS_MGR_REQ_DELETE);
		} else if (ent->cache_info != 0) {
		    status = tls_scache_delete(ent->cache_info,
					       STR(cache_id)) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       ATTR_TYPE_END);
	}

	/*
	 * RFC 5077 TLS session ticket keys
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, buffer),
			  ATTR_TYPE_END) == 1) {
		if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) {
		    msg_warn("invalid session ticket key name length: %ld",
			     (long) LEN(buffer));
		    VSTRING_RESET(buffer);
		} else if (*smtpd_cache.cache_timeout <= 0) {
		    status = TLS_MGR_STAT_ERR;
		    VSTRING_RESET(buffer);
		} else {
		    status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout);
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       SEND_ATTR_DATA(TLS_MGR_ATTR_KEYBUF,
				      LEN(buffer), STR(buffer)),
		       ATTR_TYPE_END);
	}

	/*
	 * Entropy request.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_INT(TLS_MGR_ATTR_SIZE, &len),
			  ATTR_TYPE_END) == 1) {
		VSTRING_RESET(buffer);
		if (len <= 0 || len > 255) {
		    msg_warn("bogus seed length \"%d\" in \"%s\" request",
			     len, TLS_MGR_REQ_SEED);
		} else {
		    VSTRING_SPACE(buffer, len);
		    if (RAND_bytes((unsigned char *) STR(buffer), len) < 0) {
			status = TLS_MGR_STAT_ERR;
		    } else {
			vstring_set_payload_size(buffer, len);
			status = TLS_MGR_STAT_OK;
		    }
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       SEND_ATTR_DATA(TLS_MGR_ATTR_SEED,
				      LEN(buffer), STR(buffer)),
		       ATTR_TYPE_END);
	}

	/*
	 * Caching policy request.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) {
	    int     cachable = 0;
	    int     timeout = 0;

	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
			  ATTR_TYPE_END) == 1) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    msg_warn("bogus cache type \"%s\" in \"%s\" request",
			     STR(cache_type), TLS_MGR_REQ_POLICY);
		} else {
		    cachable = (ent->cache_info != 0) ? 1 : 0;
		    timeout = *ent->cache_timeout;
		    status = TLS_MGR_STAT_OK;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
		       SEND_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable),
		       SEND_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout),
		       ATTR_TYPE_END);
	}

	/*
	 * Master trigger. Normally, these triggers arrive only after some
	 * other process requested the tlsmgr's service. The purpose is to
	 * restart the tlsmgr after it aborted due to a fatal run-time error,
	 * so that it can continue its housekeeping even while nothing is
	 * using TLS.
	 * 
	 * XXX Which begs the question, if TLS isn't used often, do we need a
	 * tlsmgr background process? It could terminate when the session
	 * caches are empty.
	 */
	else if (STREQ(STR(request), wakeup)) {
	    if (msg_verbose)
		msg_info("received master trigger");
	    multi_server_disconnect(client_stream);
	    return;				/* NOT: vstream_fflush */
	}
    }

    /*
     * Workaround: some OS lies under load. It tells the Postfix event
     * handler that a server socket is readable, then it tells peekfd() that
     * the socket has unread data, and then it tells vstring_get_null() that
     * there is none, causing Postfix to spam the log with warning messages.
     * Close the stream to stop such nonsense; the client can reconnect if it
     * still wants to talk to us.
     * 
     * XXX Why is this problem not reported for the other five
     * multi_server-based Postfix services?
     */
    else if (vstream_ferror(client_stream) || vstream_feof(client_stream)) {
	multi_server_disconnect(client_stream);
	return;
	/* Note: client_stream is now a dangling pointer. */
    }

    /*
     * Protocol error.
     */
    else {
	attr_print(client_stream, ATTR_FLAG_NONE,
		   SEND_ATTR_INT(MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL),
		   ATTR_TYPE_END);
    }
    vstream_fflush(client_stream);
}

/* tlsmgr_pre_init - pre-jail initialization */

static void tlsmgr_pre_init(char *unused_name, char **unused_argv)
{
    char   *path;
    struct timeval tv;
    TLSMGR_SCACHE *ent;
    VSTRING *redirect;
    HTABLE *dup_filter;
    const char *dup_label;

    /*
     * If nothing else works then at least this will get us a few bits of
     * entropy.
     * 
     * XXX This is our first call into the OpenSSL library. We should find out
     * if this can be moved to the post-jail initialization phase, without
     * breaking compatibility with existing installations.
     */
    GETTIMEOFDAY(&tv);
    tv.tv_sec ^= getpid();
    RAND_seed(&tv, sizeof(struct timeval));

    /*
     * Open the external entropy source. We will not be able to open it again
     * after we are sent to chroot jail, so we keep it open. Errors are not
     * fatal. The exchange file (see below) is the only entropy source that
     * really matters in the long run.
     * 
     * Security note: we open the entropy source while privileged, but we don't
     * access the source until after we release privileges. This way, none of
     * the OpenSSL code gets to execute while we are privileged.
     */
    if (*var_tls_rand_source) {

	/*
	 * Source is a random device.
	 */
	if (!strncmp(var_tls_rand_source, DEV_PREF, DEV_PREF_LEN)) {
	    path = DEV_PATH(var_tls_rand_source);
	    rand_source_dev = tls_prng_dev_open(path, TLS_MGR_TIMEOUT);
	    if (rand_source_dev == 0)
		msg_warn("cannot open entropy device %s: %m", path);
	}

	/*
	 * Source is an EGD compatible socket.
	 */
	else if (!strncmp(var_tls_rand_source, EGD_PREF, EGD_PREF_LEN)) {
	    path = EGD_PATH(var_tls_rand_source);
	    rand_source_egd = tls_prng_egd_open(path, TLS_MGR_TIMEOUT);
	    if (rand_source_egd == 0)
		msg_warn("cannot connect to EGD server %s: %m", path);
	}

	/*
	 * Source is regular file. We read this only once.
	 */
	else {
	    rand_source_file =
		tls_prng_file_open(var_tls_rand_source, TLS_MGR_TIMEOUT);
	}
    } else {
	msg_warn("no entropy source specified with parameter %s",
		 VAR_TLS_RAND_SOURCE);
	msg_warn("encryption keys etc. may be predictable");
    }

    /*
     * Security: don't create root-owned files that contain untrusted data.
     * And don't create Postfix-owned files in root-owned directories,
     * either. We want a correct relationship between (file/directory)
     * ownership and (file/directory) content.
     */
    SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid);
    redirect = vstring_alloc(100);

    /*
     * Open the PRNG exchange file before going to jail, but don't use root
     * privileges. Start the exchange file read/update pseudo thread after
     * dropping privileges.
     */
    if (*var_tls_rand_exch_name) {
	rand_exch =
	    tls_prng_exch_open(data_redirect_file(redirect,
						  var_tls_rand_exch_name));
	if (rand_exch == 0)
	    msg_fatal("cannot open PRNG exchange file %s: %m",
		      var_tls_rand_exch_name);
    }

    /*
     * Open the session cache files and discard old information before going
     * to jail, but don't use root privilege. Start the cache maintenance
     * pseudo threads after dropping privileges.
     */
    dup_filter = htable_create(sizeof(cache_table) / sizeof(cache_table[0]));
    for (ent = cache_table; ent->cache_label; ++ent) {
	/* Sanitize session timeout */
	if (*ent->cache_timeout > 0) {
	    if (*ent->cache_timeout < TLS_SESSION_LIFEMIN)
		*ent->cache_timeout = TLS_SESSION_LIFEMIN;
	} else {
	    *ent->cache_timeout = 0;
	}
	/* External cache database disabled if timeout is non-positive */
	if (*ent->cache_timeout > 0 && **ent->cache_db) {
	    if ((dup_label = htable_find(dup_filter, *ent->cache_db)) != 0)
		msg_fatal("do not use the same TLS cache file %s for %s and %s",
			  *ent->cache_db, dup_label, ent->cache_label);
	    htable_enter(dup_filter, *ent->cache_db, ent->cache_label);
	    ent->cache_info =
		tls_scache_open(data_redirect_map(redirect, *ent->cache_db),
				ent->cache_label,
				tls_log_mask(ent->log_param,
					   *ent->log_level) & TLS_LOG_CACHE,
				*ent->cache_timeout);
	}
    }
    htable_free(dup_filter, (void (*) (void *)) 0);

    /*
     * Clean up and restore privilege.
     */
    vstring_free(redirect);
    RESTORE_SAVED_EUGID();
}

/* tlsmgr_post_init - post-jail initialization */

static void tlsmgr_post_init(char *unused_name, char **unused_argv)
{
    TLSMGR_SCACHE *ent;

#define NULL_EVENT	(0)
#define NULL_CONTEXT	((char *) 0)

    /*
     * This routine runs after the skeleton code has entered the chroot jail,
     * but before any client requests are serviced. Prevent automatic process
     * suicide after a limited number of client requests or after a limited
     * amount of idle time.
     */
    var_use_limit = 0;
    var_idle_limit = 0;

    /*
     * Start the internal PRNG re-seeding pseudo thread first.
     */
    if (*var_tls_rand_source) {
	if (var_tls_reseed_period > INT_MAX / UCHAR_MAX)
	    var_tls_reseed_period = INT_MAX / UCHAR_MAX;
	tlsmgr_reseed_event(NULL_EVENT, NULL_CONTEXT);
    }

    /*
     * Start the exchange file read/update pseudo thread.
     */
    if (*var_tls_rand_exch_name) {
	if (var_tls_prng_exch_period > INT_MAX / UCHAR_MAX)
	    var_tls_prng_exch_period = INT_MAX / UCHAR_MAX;
	tlsmgr_prng_exch_event(NULL_EVENT, NULL_CONTEXT);
    }

    /*
     * Start the cache maintenance pseudo threads last. Strictly speaking
     * there is nothing to clean up after we truncate the database to zero
     * length, but early cleanup makes verbose logging more informative (we
     * get positive confirmation that the cleanup threads are running).
     */
    for (ent = cache_table; ent->cache_label; ++ent)
	if (ent->cache_info)
	    tlsmgr_cache_run_event(NULL_EVENT, (void *) ent);
}

/* tlsmgr_post_accept - announce our protocol */

static void tlsmgr_post_accept(VSTREAM *stream, char *unused_name,
			           char **unused_argv, HTABLE *unused_table)
{

    /*
     * Announce the protocol.
     */
    attr_print(stream, ATTR_FLAG_NONE,
	       SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSMGR),
	       ATTR_TYPE_END);
    (void) vstream_fflush(stream);
}


/* tlsmgr_before_exit - save PRNG state before exit */

static void tlsmgr_before_exit(char *unused_service_name, char **unused_argv)
{

    /*
     * Save state before we exit after "postfix reload".
     */
    if (rand_exch)
	tls_prng_exch_update(rand_exch);
}

MAIL_VERSION_STAMP_DECLARE;

/* main - the main program */

int     main(int argc, char **argv)
{
    static const CONFIG_STR_TABLE str_table[] = {
	VAR_TLS_RAND_SOURCE, DEF_TLS_RAND_SOURCE, &var_tls_rand_source, 0, 0,
	VAR_TLS_RAND_EXCH_NAME, DEF_TLS_RAND_EXCH_NAME, &var_tls_rand_exch_name, 0, 0,
	VAR_SMTPD_TLS_SCACHE_DB, DEF_SMTPD_TLS_SCACHE_DB, &var_smtpd_tls_scache_db, 0, 0,
	VAR_SMTP_TLS_SCACHE_DB, DEF_SMTP_TLS_SCACHE_DB, &var_smtp_tls_scache_db, 0, 0,
	VAR_LMTP_TLS_SCACHE_DB, DEF_LMTP_TLS_SCACHE_DB, &var_lmtp_tls_scache_db, 0, 0,
	VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0,
	VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
	VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_lmtp_tls_loglevel, 0, 0,
	0,
    };
    static const CONFIG_TIME_TABLE time_table[] = {
	VAR_TLS_RESEED_PERIOD, DEF_TLS_RESEED_PERIOD, &var_tls_reseed_period, 1, 0,
	VAR_TLS_PRNG_UPD_PERIOD, DEF_TLS_PRNG_UPD_PERIOD, &var_tls_prng_exch_period, 1, 0,
	VAR_SMTPD_TLS_SCACHTIME, DEF_SMTPD_TLS_SCACHTIME, &var_smtpd_tls_scache_timeout, 0, MAX_SMTPD_TLS_SCACHETIME,
	VAR_SMTP_TLS_SCACHTIME, DEF_SMTP_TLS_SCACHTIME, &var_smtp_tls_scache_timeout, 0, MAX_SMTP_TLS_SCACHETIME,
	VAR_LMTP_TLS_SCACHTIME, DEF_LMTP_TLS_SCACHTIME, &var_lmtp_tls_scache_timeout, 0, MAX_LMTP_TLS_SCACHETIME,
	0,
    };
    static const CONFIG_INT_TABLE int_table[] = {
	VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 1, 0,
	0,
    };

    /*
     * Fingerprint executables and core dumps.
     */
    MAIL_VERSION_STAMP_ALLOCATE;

    /*
     * Use the multi service skeleton, and require that no-one else is
     * monitoring our service port while this process runs.
     */
    multi_server_main(argc, argv, tlsmgr_service,
		      CA_MAIL_SERVER_TIME_TABLE(time_table),
		      CA_MAIL_SERVER_INT_TABLE(int_table),
		      CA_MAIL_SERVER_STR_TABLE(str_table),
		      CA_MAIL_SERVER_PRE_INIT(tlsmgr_pre_init),
		      CA_MAIL_SERVER_POST_INIT(tlsmgr_post_init),
		      CA_MAIL_SERVER_POST_ACCEPT(tlsmgr_post_accept),
		      CA_MAIL_SERVER_EXIT(tlsmgr_before_exit),
		      CA_MAIL_SERVER_LOOP(tlsmgr_loop),
		      CA_MAIL_SERVER_SOLITARY,
		      0);
}

#else

/* tlsmgr_service - respond to external trigger(s), non-TLS version */

static void tlsmgr_service(VSTREAM *unused_stream, char *unused_service,
			           char **unused_argv)
{
    msg_info("TLS support is not compiled in -- exiting");
}

/* main - the main program, non-TLS version */

int     main(int argc, char **argv)
{

    /*
     * 200411 We can't simply use msg_fatal() here, because the logging
     * hasn't been initialized. The text would disappear because stderr is
     * redirected to /dev/null.
     * 
     * We invoke multi_server_main() to complete program initialization
     * (including logging) and then invoke the tlsmgr_service() routine to
     * log the message that says why this program will not run.
     */
    multi_server_main(argc, argv, tlsmgr_service,
		      0);
}

#endif
