/*++
/* 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 0 xff
#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) + EGD_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 .
*/
RAND_bytes(&randbyte, 1 );
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 overwrites 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);
RAND_bytes((unsigned char *) STR(buffer), len);
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
Messung V0.5 in Prozent C=76 H=98 G=87
¤ Dauer der Verarbeitung: 0.29 Sekunden
(vorverarbeitet am 2026-08-10)
¤
*© Formatika GbR, Deutschland