#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>
#include <limits.h>
#ifndef UCHAR_MAX
#define UCHAR_MAX 0xff
#endif
#ifdef USE_TLS
#include <openssl/rand.h>
#endif
#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 <mail_conf.h>
#include <mail_params.h>
#include <mail_version.h>
#include <tls_mgr.h>
#include <mail_proto.h>
#include <data_redirect.h>
#include <master_proto.h>
#include <mail_server.h>
#ifdef USE_TLS
#include <tls.h>
#include <tls_prng.h>
#include <tls_scache.h>
char *var_tls_rand_source;
int var_tls_rand_bytes;
int var_tls_reseed_period;
int var_tls_prng_exch_period;
int var_smtpd_tls_loglevel;
char *var_smtpd_tls_scache_db;
int var_smtpd_tls_scache_timeout;
int var_smtp_tls_loglevel;
char *var_smtp_tls_scache_db;
int var_smtp_tls_scache_timeout;
int var_lmtp_tls_loglevel;
char *var_lmtp_tls_scache_db;
int var_lmtp_tls_scache_timeout;
char *var_tls_rand_exch_name;
#define TLS_MGR_TIMEOUT 10
static TLS_PRNG_SRC *rand_exch;
static TLS_PRNG_SRC *rand_source_dev;
static TLS_PRNG_SRC *rand_source_egd;
static TLS_PRNG_SRC *rand_source_file;
#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)
typedef struct {
char *cache_label;
TLS_SCACHE *cache_info;
int cache_active;
char **cache_db;
int *cache_loglevel;
int *cache_timeout;
} TLSMGR_SCACHE;
TLSMGR_SCACHE cache_table[] = {
TLS_MGR_SCACHE_SMTPD, 0, 0, &var_smtpd_tls_scache_db,
&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_scache_timeout,
TLS_MGR_SCACHE_LMTP, 0, 0, &var_lmtp_tls_scache_db,
&var_lmtp_tls_loglevel, &var_lmtp_tls_scache_timeout,
0,
};
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
#define STREQ(x, y) (strcmp((x), (y)) == 0)
static void tlsmgr_prng_exch_event(int unused_event, char *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);
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);
RAND_bytes(&randbyte, 1);
next_period = (var_tls_prng_exch_period * randbyte) / UCHAR_MAX;
event_request_timer(tlsmgr_prng_exch_event, dummy, next_period);
}
static void tlsmgr_reseed_event(int unused_event, char *dummy)
{
int next_period;
unsigned char randbyte;
int must_exit = 0;
if (*var_tls_rand_source) {
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;
}
}
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;
}
}
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;
}
else {
msg_info("exiting to reopen external entropy source %s",
var_tls_rand_source);
must_exit = 1;
}
}
if (must_exit) {
if (rand_exch)
tls_prng_exch_update(rand_exch);
sleep(1);
exit(0);
}
RAND_bytes(&randbyte, 1);
next_period = (var_tls_reseed_period * randbyte) / UCHAR_MAX;
event_request_timer(tlsmgr_reseed_event, dummy, next_period);
}
static void tlsmgr_cache_run_event(int unused_event, char *ctx)
{
const char *myname = "tlsmgr_cache_run_event";
TLSMGR_SCACHE *cache = (TLSMGR_SCACHE *) ctx;
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, (char *) cache,
cache->cache_info->timeout);
}
static int tlsmgr_loop(char *unused_name, char **unused_argv)
{
struct timeval tv;
int active = 0;
TLSMGR_SCACHE *ent;
GETTIMEOFDAY(&tv);
RAND_seed(&tv, sizeof(struct timeval));
#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);
}
static int tlsmgr_request_receive(VSTREAM *client_stream, VSTRING *request)
{
int count;
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);
}
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);
}
}
else {
if (attr_scan(client_stream,
ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
ATTR_TYPE_STR, TLS_MGR_ATTR_REQ, request,
ATTR_TYPE_END) != 1) {
return (-1);
}
}
return (0);
}
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[] = {
TRIGGER_REQ_WAKEUP,
0,
};
TLSMGR_SCACHE *ent;
int status = TLS_MGR_STAT_FAIL;
if (argv[0])
msg_fatal("unexpected command-line argument: %s", argv[0]);
if (request == 0) {
request = vstring_alloc(10);
cache_type = vstring_alloc(10);
cache_id = vstring_alloc(10);
buffer = vstring_alloc(10);
}
if (tlsmgr_request_receive(client_stream, request) == 0) {
if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) {
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
ATTR_TYPE_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) {
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,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION,
LEN(buffer), STR(buffer),
ATTR_TYPE_END);
}
else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) {
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
ATTR_TYPE_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,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
ATTR_TYPE_END);
}
else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) {
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
ATTR_TYPE_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,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
ATTR_TYPE_END);
}
else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) {
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_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_AT_OFFSET(buffer, len);
status = TLS_MGR_STAT_OK;
}
}
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
ATTR_TYPE_DATA, TLS_MGR_ATTR_SEED,
LEN(buffer), STR(buffer),
ATTR_TYPE_END);
}
else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) {
int cachable = 0;
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_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;
status = TLS_MGR_STAT_OK;
}
}
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, status,
ATTR_TYPE_INT, TLS_MGR_ATTR_CACHABLE, cachable,
ATTR_TYPE_END);
}
else if (STREQ(STR(request), wakeup)) {
if (msg_verbose)
msg_info("received master trigger");
multi_server_disconnect(client_stream);
return;
}
}
else {
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_INT, MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL,
ATTR_TYPE_END);
}
vstream_fflush(client_stream);
}
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;
GETTIMEOFDAY(&tv);
tv.tv_sec ^= getpid();
RAND_seed(&tv, sizeof(struct timeval));
if (*var_tls_rand_source) {
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);
}
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);
}
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");
}
SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid);
redirect = vstring_alloc(100);
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);
}
dup_filter = htable_create(sizeof(cache_table) / sizeof(cache_table[0]));
for (ent = cache_table; ent->cache_label; ++ent) {
if (**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,
*ent->cache_loglevel >= 2,
*ent->cache_timeout);
}
}
htable_free(dup_filter, (void (*) (char *)) 0);
vstring_free(redirect);
RESTORE_SAVED_EUGID();
}
static void tlsmgr_post_init(char *unused_name, char **unused_argv)
{
TLSMGR_SCACHE *ent;
#define NULL_EVENT (0)
#define NULL_CONTEXT ((char *) 0)
var_use_limit = 0;
var_idle_limit = 0;
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);
}
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);
}
for (ent = cache_table; ent->cache_label; ++ent)
if (ent->cache_info)
tlsmgr_cache_run_event(NULL_EVENT, (char *) ent);
}
static void tlsmgr_before_exit(char *unused_service_name, char **unused_argv)
{
if (rand_exch)
tls_prng_exch_update(rand_exch);
}
MAIL_VERSION_STAMP_DECLARE;
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,
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, 0,
VAR_SMTP_TLS_SCACHTIME, DEF_SMTP_TLS_SCACHTIME, &var_smtp_tls_scache_timeout, 0, 0,
VAR_LMTP_TLS_SCACHTIME, DEF_LMTP_TLS_SCACHTIME, &var_lmtp_tls_scache_timeout, 0, 0,
0,
};
static const CONFIG_INT_TABLE int_table[] = {
VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 1, 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,
};
MAIL_VERSION_STAMP_ALLOCATE;
multi_server_main(argc, argv, tlsmgr_service,
MAIL_SERVER_TIME_TABLE, time_table,
MAIL_SERVER_INT_TABLE, int_table,
MAIL_SERVER_STR_TABLE, str_table,
MAIL_SERVER_PRE_INIT, tlsmgr_pre_init,
MAIL_SERVER_POST_INIT, tlsmgr_post_init,
MAIL_SERVER_EXIT, tlsmgr_before_exit,
MAIL_SERVER_LOOP, tlsmgr_loop,
MAIL_SERVER_SOLITARY,
0);
}
#else
static void tlsmgr_service(VSTREAM *unused_stream, char *unused_service,
char **unused_argv)
{
msg_info("TLS support is not compiled in -- exiting");
}
int main(int argc, char **argv)
{
multi_server_main(argc, argv, tlsmgr_service,
0);
}
#endif