#include <sys_defs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <iostuff.h>
#include <mymalloc.h>
#include <vstring.h>
#include <vstream.h>
#include <dict.h>
#include <myflock.h>
#include <stringops.h>
#include <msg.h>
#include <connect.h>
#include "mail_params.h"
#include "pfixtls.h"
#define STR vstring_str
const tls_info_t tls_info_zero = {
0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0
};
#ifdef USE_SSL
#include <openssl/lhash.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#ifdef __APPLE__
#include <Security/SecKeychain.h>
typedef struct
{
int len;
char key[ FILENAME_MAX ];
int reserved;
} CallbackUserData;
static CallbackUserData *sUserData = NULL;
#endif
static const char hexcodes[] = "0123456789ABCDEF";
#define ID_MAXLENGTH 64
static char server_session_id_context[] = "Postfix/TLS";
static int TLScontext_index = -1;
static int TLSpeername_index = -1;
static int do_dump = 0;
static DH *dh_512 = NULL, *dh_1024 = NULL;
static SSL_CTX *ctx = NULL;
static int rand_exch_fd = -1;
static DICT *scache_db = NULL;
const long scache_db_version = 0x00000003L;
const long openssl_version = OPENSSL_VERSION_NUMBER;
int pfixtls_serverengine = 0;
static int pfixtls_serveractive = 0;
int pfixtls_clientengine = 0;
static int pfixtls_clientactive = 0;
#define CCERT_BUFSIZ 256
typedef struct {
SSL *con;
BIO *internal_bio;
BIO *network_bio;
char peer_subject[CCERT_BUFSIZ];
char peer_issuer[CCERT_BUFSIZ];
char peer_CN[CCERT_BUFSIZ];
char issuer_CN[CCERT_BUFSIZ];
unsigned char md[EVP_MAX_MD_SIZE];
char fingerprint[EVP_MAX_MD_SIZE * 3];
char peername_save[129];
int enforce_verify_errors;
int enforce_CN;
int hostname_matched;
} TLScontext_t;
typedef struct {
int pid;
struct timeval tv;
} randseed_t;
static randseed_t randseed;
static unsigned char dh512_p[] = {
0x88, 0x3F, 0x00, 0xAF, 0xFC, 0x0C, 0x8A, 0xB8, 0x35, 0xCD, 0xE5, 0xC2,
0x0F, 0x55, 0xDF, 0x06, 0x3F, 0x16, 0x07, 0xBF, 0xCE, 0x13, 0x35, 0xE4,
0x1C, 0x1E, 0x03, 0xF3, 0xAB, 0x17, 0xF6, 0x63, 0x50, 0x63, 0x67, 0x3E,
0x10, 0xD7, 0x3E, 0xB4, 0xEB, 0x46, 0x8C, 0x40, 0x50, 0xE6, 0x91, 0xA5,
0x6E, 0x01, 0x45, 0xDE, 0xC9, 0xB1, 0x1F, 0x64, 0x54, 0xFA, 0xD9, 0xAB,
0x4F, 0x70, 0xBA, 0x5B,
};
static unsigned char dh512_g[] = {
0x02,
};
static unsigned char dh1024_p[] = {
0xB0, 0xFE, 0xB4, 0xCF, 0xD4, 0x55, 0x07, 0xE7, 0xCC, 0x88, 0x59, 0x0D,
0x17, 0x26, 0xC5, 0x0C, 0xA5, 0x4A, 0x92, 0x23, 0x81, 0x78, 0xDA, 0x88,
0xAA, 0x4C, 0x13, 0x06, 0xBF, 0x5D, 0x2F, 0x9E, 0xBC, 0x96, 0xB8, 0x51,
0x00, 0x9D, 0x0C, 0x0D, 0x75, 0xAD, 0xFD, 0x3B, 0xB1, 0x7E, 0x71, 0x4F,
0x3F, 0x91, 0x54, 0x14, 0x44, 0xB8, 0x30, 0x25, 0x1C, 0xEB, 0xDF, 0x72,
0x9C, 0x4C, 0xF1, 0x89, 0x0D, 0x68, 0x3F, 0x94, 0x8E, 0xA4, 0xFB, 0x76,
0x89, 0x18, 0xB2, 0x91, 0x16, 0x90, 0x01, 0x99, 0x66, 0x8C, 0x53, 0x81,
0x4E, 0x27, 0x3D, 0x99, 0xE7, 0x5A, 0x7A, 0xAF, 0xD5, 0xEC, 0xE2, 0x7E,
0xFA, 0xED, 0x01, 0x18, 0xC2, 0x78, 0x25, 0x59, 0x06, 0x5C, 0x39, 0xF6,
0xCD, 0x49, 0x54, 0xAF, 0xC1, 0xB1, 0xEA, 0x4A, 0xF9, 0x53, 0xD0, 0xDF,
0x6D, 0xAF, 0xD4, 0x93, 0xE7, 0xBA, 0xAE, 0x9B,
};
static unsigned char dh1024_g[] = {
0x02,
};
const size_t BIO_bufsiz = 8192;
static int network_biopair_interop(int fd, int timeout, BIO *network_bio)
{
int want_write;
int num_write;
int write_pos;
int from_bio;
int want_read;
int num_read;
int to_bio;
#define NETLAYER_BUFFERSIZE 8192
char buffer[8192];
while ((want_write = BIO_ctrl_pending(network_bio)) > 0) {
if (want_write > NETLAYER_BUFFERSIZE)
want_write = NETLAYER_BUFFERSIZE;
from_bio = BIO_read(network_bio, buffer, want_write);
write_pos = 0;
do {
if (timeout > 0 && write_wait(fd, timeout) < 0)
return (-1);
num_write = write(fd, buffer + write_pos, from_bio - write_pos);
if (num_write <= 0) {
if ((num_write < 0) && (timeout > 0) && (errno == EAGAIN)) {
msg_warn("write() returns EAGAIN on a writable file descriptor!");
msg_warn("pausing to avoid going into a tight select/write loop!");
sleep(1);
} else {
msg_warn("Write failed in network_biopair_interop with errno=%d: num_write=%d, provided=%d", errno, num_write, from_bio - write_pos);
return (-1);
}
} else
write_pos += num_write;
} while (write_pos < from_bio);
}
while ((want_read = BIO_ctrl_get_read_request(network_bio)) > 0) {
if (want_read > NETLAYER_BUFFERSIZE)
want_read = NETLAYER_BUFFERSIZE;
if (timeout > 0 && read_wait(fd, timeout) < 0)
return (-1);
num_read = read(fd, buffer, want_read);
if (num_read <= 0) {
if ((num_write < 0) && (timeout > 0) && (errno == EAGAIN)) {
msg_warn("read() returns EAGAIN on a readable file descriptor!");
msg_warn("pausing to avoid going into a tight select/write loop!");
sleep(1);
} else {
msg_warn("Read failed in network_biopair_interop with errno=%d: num_read=%d, want_read=%d", errno, num_read, want_read);
return (-1);
}
} else {
to_bio = BIO_write(network_bio, buffer, num_read);
if (to_bio != num_read)
msg_fatal("to_bio != num_read");
}
}
return (0);
}
static void pfixtls_print_errors(void);
static int do_tls_operation(int fd, int timeout, TLScontext_t *TLScontext,
int (*hsfunc)(SSL *),
int (*rfunc)(SSL *, void *, int),
int (*wfunc)(SSL *, const void *, int),
char *buf, int num)
{
int status;
int err;
int retval = 0;
int biop_retval;
int done = 0;
while (!done) {
if (hsfunc)
status = hsfunc(TLScontext->con);
else if (rfunc)
status = rfunc(TLScontext->con, buf, num);
else
status = wfunc(TLScontext->con, (const char *)buf, num);
err = SSL_get_error(TLScontext->con, status);
#if (OPENSSL_VERSION_NUMBER <= 0x0090581fL)
if (err == SSL_ERROR_SSL) {
if (ERR_peek_error() == 0x0407006AL) {
pfixtls_print_errors();
msg_info("OpenSSL <= 0.9.5a workaround called: certificate errors ignored");
err = SSL_get_error(TLScontext->con, status);
}
}
#endif
switch (err) {
case SSL_ERROR_NONE:
retval = status;
done = 1;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
biop_retval = network_biopair_interop(fd, timeout,
TLScontext->network_bio);
if (biop_retval < 0)
return (-1);
break;
case SSL_ERROR_ZERO_RETURN:
case SSL_ERROR_SYSCALL:
case SSL_ERROR_SSL:
default:
retval = status;
done = 1;
;
}
};
return retval;
}
int pfixtls_timed_read(int fd, void *buf, unsigned buf_len, int timeout,
void *context)
{
int i;
int ret;
char mybuf[40];
char *mybuf2;
TLScontext_t *TLScontext;
TLScontext = (TLScontext_t *)context;
if (!TLScontext)
msg_fatal("Called tls_timed_read() without TLS-context");
ret = do_tls_operation(fd, timeout, TLScontext, NULL, SSL_read, NULL,
(char *)buf, buf_len);
if ((pfixtls_serveractive && var_smtpd_tls_loglevel >= 4) ||
(pfixtls_clientactive && var_smtp_tls_loglevel >= 4)) {
mybuf2 = (char *) buf;
if (ret > 0) {
i = 0;
while ((i < 39) && (i < ret) && (mybuf2[i] != 0)) {
mybuf[i] = mybuf2[i];
i++;
}
mybuf[i] = '\0';
msg_info("Read %d chars: %s", ret, mybuf);
}
}
return (ret);
}
int pfixtls_timed_write(int fd, void *buf, unsigned len, int timeout,
void *context)
{
int i;
char mybuf[40];
char *mybuf2;
TLScontext_t *TLScontext;
TLScontext = (TLScontext_t *)context;
if (!TLScontext)
msg_fatal("Called tls_timed_write() without TLS-context");
if ((pfixtls_serveractive && var_smtpd_tls_loglevel >= 4) ||
(pfixtls_clientactive && var_smtp_tls_loglevel >= 4)) {
mybuf2 = (char *) buf;
if (len > 0) {
i = 0;
while ((i < 39) && (i < len) && (mybuf2[i] != 0)) {
mybuf[i] = mybuf2[i];
i++;
}
mybuf[i] = '\0';
msg_info("Write %d chars: %s", len, mybuf);
}
}
return (do_tls_operation(fd, timeout, TLScontext, NULL, NULL, SSL_write,
buf, len));
}
static void pfixtls_stir_seed(void)
{
GETTIMEOFDAY(&randseed.tv);
RAND_seed(&randseed, sizeof(randseed_t));
}
static void pfixtls_print_errors(void)
{
unsigned long l;
char buf[256];
const char *file;
const char *data;
int line;
int flags;
unsigned long es;
es = CRYPTO_thread_id();
while ((l = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {
if (flags & ERR_TXT_STRING)
msg_info("%lu:%s:%s:%d:%s:", es, ERR_error_string(l, buf),
file, line, data);
else
msg_info("%lu:%s:%s:%d:", es, ERR_error_string(l, buf),
file, line);
}
}
#ifdef __APPLE__
int apple_password_callback ( char *inBuf, int inSize, int in_rwflag, void *inUserData )
{
OSStatus status = noErr;
SecKeychainItemRef keyChainRef = NULL;
void *pwdBuf = NULL;
UInt32 pwdLen = 0;
char *service = "certificateManager";
CallbackUserData *cbUserData = (CallbackUserData *)inUserData;
if ( (cbUserData == NULL) || strlen( cbUserData->key ) == 0 ||
(cbUserData->len >= FILENAME_MAX) || (cbUserData->len == 0) || !inBuf )
{
if (var_smtpd_tls_loglevel >= 3)
msg_info("Error: Invalid arguments in callback" );
return( 0 );
}
status = SecKeychainSetPreferenceDomain( kSecPreferencesDomainSystem );
if ( status != noErr )
{
if (var_smtpd_tls_loglevel >= 3)
msg_info("Error: SecKeychainSetPreferenceDomain returned status: %d", status );
return( 0 );
}
status = SecKeychainFindGenericPassword( NULL, strlen( service ), service,
cbUserData->len, cbUserData->key,
&pwdLen, &pwdBuf,
&keyChainRef );
if ( (status == noErr) && (keyChainRef != NULL) )
{
if ( pwdLen > inSize )
{
if (var_smtpd_tls_loglevel >= 3)
msg_info("Error: Invalid buffer size callback (size:%d, len:%d)", inSize, pwdLen );
SecKeychainItemFreeContent( NULL, pwdBuf );
return( 0 );
}
memcpy( inBuf, (const void *)pwdBuf, pwdLen );
if ( inSize > 0 )
{
inBuf[ pwdLen ] = 0;
inBuf[ inSize - 1 ] = 0;
}
SecKeychainItemFreeContent( NULL, pwdBuf );
return( strlen(inBuf ) );
}
else if (status == errSecNotAvailable)
{
if (var_smtpd_tls_loglevel >= 3)
msg_info("Error: SecKeychainSetPreferenceDomain: No keychain is available" );
}
else if ( status == errSecItemNotFound )
{
if (var_smtpd_tls_loglevel >= 3)
msg_info("Error: SecKeychainSetPreferenceDomain: The requested key could not be found in the system keychain");
}
else if (status != noErr)
{
if (var_smtpd_tls_loglevel >= 3)
msg_info("Error: SecKeychainFindGenericPassword returned status %d", status);
}
return( 0 );
}
#endif
static int set_cert_stuff(SSL_CTX * ctx, char *cert_file, char *key_file)
{
#ifdef __APPLE__
if ( sUserData == NULL )
{
sUserData = mymalloc( sizeof(CallbackUserData) );
if ( sUserData != NULL )
{
memset( sUserData, 0, sizeof(CallbackUserData) );
}
}
#endif
if (cert_file != NULL) {
if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) {
msg_info("unable to get certificate from '%s'", cert_file);
pfixtls_print_errors();
return (0);
}
if (key_file == NULL)
key_file = cert_file;
#ifdef __APPLE__
if ( strlen( key_file ) < FILENAME_MAX )
{
char tmp[ FILENAME_MAX ];
strlcpy( tmp, key_file, FILENAME_MAX );
char *p = tmp;
char *q = tmp;
while ( *p != '\0' )
{
if ( *p == '/' )
{
q = p;
}
p++;
}
if ( (*q != '\0') && (*q == '/') && (*p+1 != '\0') )
{
p = ++q;
int len = strlen( p );
if ( sUserData != NULL )
{
if ( strncmp( p+(len-4), ".key", 4 ) == 0 )
{
len = len - 4;
strncpy( sUserData->key, p, len );
sUserData->key[ len ] = '\0';
sUserData->len = len;
}
else
{
strcpy( sUserData->key, p );
sUserData->len = strlen( p );
}
SSL_CTX_set_default_passwd_cb_userdata( ctx, (void *)sUserData );
SSL_CTX_set_default_passwd_cb( ctx, &apple_password_callback );
}
else
{
msg_info("Could not allocate data for callback: %s", key_file);
}
}
else
{
msg_info("Could not set custom callback: %s", key_file);
}
}
else
{
msg_info("Key file path too big for custom callback: %s", key_file );
}
#endif
if (SSL_CTX_use_PrivateKey_file(ctx, key_file,
SSL_FILETYPE_PEM) <= 0) {
msg_info("unable to get private key from '%s'", key_file);
pfixtls_print_errors();
return (0);
}
if (!SSL_CTX_check_private_key(ctx)) {
msg_info("Private key does not match the certificate public key");
return (0);
}
}
return (1);
}
static RSA *tmp_rsa_cb(SSL * s, int export, int keylength)
{
static RSA *rsa_tmp = NULL;
if (rsa_tmp == NULL) {
rsa_tmp = RSA_generate_key(keylength, RSA_F4, NULL, NULL);
}
return (rsa_tmp);
}
static DH *get_dh512(void)
{
DH *dh;
if (dh_512 == NULL) {
if ((dh = DH_new()) == NULL) return(NULL);
dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL);
dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL);
if ((dh->p == NULL) || (dh->g == NULL))
return(NULL);
else
dh_512 = dh;
}
return (dh_512);
}
static DH *get_dh1024(void)
{
DH *dh;
if (dh_1024 == NULL) {
if ((dh = DH_new()) == NULL) return(NULL);
dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
if ((dh->p == NULL) || (dh->g == NULL))
return(NULL);
else
dh_1024 = dh;
}
return (dh_1024);
}
static DH *tmp_dh_cb(SSL *s, int export, int keylength)
{
DH *dh_tmp = NULL;
if (export) {
if (keylength == 512)
dh_tmp = get_dh512();
else if (keylength == 1024)
dh_tmp = get_dh1024();
else
dh_tmp = get_dh1024();
}
else {
dh_tmp = get_dh1024();
}
return (dh_tmp);
}
static int match_hostname(const char *buf, TLScontext_t *TLScontext)
{
char *hostname_lowercase;
char *peername_left;
int hostname_matched = 0;
int buf_len;
buf_len = strlen(buf);
if (!(hostname_lowercase = (char *)mymalloc(buf_len + 1)))
return 0;
memcpy(hostname_lowercase, buf, buf_len + 1);
hostname_lowercase = lowercase(hostname_lowercase);
if (!strcmp(TLScontext->peername_save, hostname_lowercase)) {
hostname_matched = 1;
} else {
if ((buf_len > 2) &&
(hostname_lowercase[0] == '*') && (hostname_lowercase[1] == '.')) {
peername_left = strchr(TLScontext->peername_save, '.');
if (peername_left) {
if (!strcmp(peername_left + 1, hostname_lowercase + 2))
hostname_matched = 1;
}
}
}
myfree(hostname_lowercase);
return hostname_matched;
}
static int verify_callback(int ok, X509_STORE_CTX * ctx)
{
char buf[256];
char *peername_left;
X509 *err_cert;
int err;
int depth;
int verify_depth;
SSL *con;
TLScontext_t *TLScontext;
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
TLScontext = SSL_get_ex_data(con, TLScontext_index);
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
if (((pfixtls_serverengine) && (var_smtpd_tls_loglevel >= 2)) ||
((pfixtls_clientengine) && (var_smtp_tls_loglevel >= 2)))
msg_info("Peer cert verify depth=%d %s", depth, buf);
verify_depth = SSL_get_verify_depth(con);
if (ok && (verify_depth >= 0) && (depth > verify_depth)) {
ok = 0;
err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
X509_STORE_CTX_set_error(ctx, err);
}
if (!ok) {
msg_info("verify error:num=%d:%s", err,
X509_verify_cert_error_string(err));
}
if (ok && (depth == 0) && pfixtls_clientengine) {
int i, r;
int hostname_matched;
int dNSName_found;
STACK_OF(GENERAL_NAME) *gens;
hostname_matched = dNSName_found = 0;
gens = X509_get_ext_d2i(err_cert, NID_subject_alt_name, 0, 0);
if (gens) {
for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i) {
const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
if (gn->type == GEN_DNS) {
dNSName_found++;
if ((hostname_matched =
match_hostname((char *)gn->d.ia5->data, TLScontext)))
break;
}
}
sk_GENERAL_NAME_free(gens);
}
if (dNSName_found) {
if (!hostname_matched)
msg_info("Peer verification: %d dNSNames in certificate found, but no one does match %s", dNSName_found, TLScontext->peername_save);
} else {
buf[0] = '\0';
if (!X509_NAME_get_text_by_NID(X509_get_subject_name(err_cert),
NID_commonName, buf, 256)) {
msg_info("Could not parse server's subject CN");
pfixtls_print_errors();
}
else {
hostname_matched = match_hostname(buf, TLScontext);
if (!hostname_matched)
msg_info("Peer verification: CommonName in certificate does not match: %s != %s", buf, TLScontext->peername_save);
}
}
if (!hostname_matched) {
if (TLScontext->enforce_verify_errors && TLScontext->enforce_CN) {
err = X509_V_ERR_CERT_REJECTED;
X509_STORE_CTX_set_error(ctx, err);
msg_info("Verify failure: Hostname mismatch");
ok = 0;
}
}
else
TLScontext->hostname_matched = 1;
}
switch (ctx->error) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256);
msg_info("issuer= %s", buf);
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
msg_info("cert not yet valid");
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
msg_info("cert has expired");
break;
}
if (((pfixtls_serverengine) && (var_smtpd_tls_loglevel >= 2)) ||
((pfixtls_clientengine) && (var_smtp_tls_loglevel >= 2)))
msg_info("verify return:%d", ok);
if (TLScontext->enforce_verify_errors)
return (ok);
else
return (1);
}
static void apps_ssl_info_callback(SSL * s, int where, int ret)
{
char *str;
int w;
w = where & ~SSL_ST_MASK;
if (w & SSL_ST_CONNECT)
str = "SSL_connect";
else if (w & SSL_ST_ACCEPT)
str = "SSL_accept";
else
str = "undefined";
if (where & SSL_CB_LOOP) {
msg_info("%s:%s", str, SSL_state_string_long(s));
} else if (where & SSL_CB_ALERT) {
str = (where & SSL_CB_READ) ? "read" : "write";
if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY)
msg_info("SSL3 alert %s:%s:%s", str,
SSL_alert_type_string_long(ret),
SSL_alert_desc_string_long(ret));
} else if (where & SSL_CB_EXIT) {
if (ret == 0)
msg_info("%s:failed in %s",
str, SSL_state_string_long(s));
else if (ret < 0) {
msg_info("%s:error in %s",
str, SSL_state_string_long(s));
}
}
}
#define TRUNCATE
#define DUMP_WIDTH 16
static int pfixtls_dump(const char *s, int len)
{
int ret = 0;
char buf[160 + 1];
char *ss;
int i;
int j;
int rows;
int trunc;
unsigned char ch;
trunc = 0;
#ifdef TRUNCATE
for (; (len > 0) && ((s[len - 1] == ' ') || (s[len - 1] == '\0')); len--)
trunc++;
#endif
rows = (len / DUMP_WIDTH);
if ((rows * DUMP_WIDTH) < len)
rows++;
for (i = 0; i < rows; i++) {
buf[0] = '\0';
ss = buf;
sprintf(ss, "%04x ", i * DUMP_WIDTH);
ss += strlen(ss);
for (j = 0; j < DUMP_WIDTH; j++) {
if (((i * DUMP_WIDTH) + j) >= len) {
strcpy(ss, " ");
} else {
ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j))
& 0xff;
sprintf(ss, "%02x%c", ch, j == 7 ? '|' : ' ');
ss += 3;
}
}
ss += strlen(ss);
*ss++ = ' ';
for (j = 0; j < DUMP_WIDTH; j++) {
if (((i * DUMP_WIDTH) + j) >= len)
break;
ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j)) & 0xff;
*ss++ = (((ch >= ' ') && (ch <= '~')) ? ch : '.');
if (j == 7) *ss++ = ' ';
}
*ss = 0;
msg_info("%s", buf);
ret += strlen(buf);
}
#ifdef TRUNCATE
if (trunc > 0) {
sprintf(buf, "%04x - <SPACES/NULS>\n", len + trunc);
msg_info("%s", buf);
ret += strlen(buf);
}
#endif
return (ret);
}
static long bio_dump_cb(BIO * bio, int cmd, const char *argp, int argi,
long argl, long ret)
{
if (!do_dump)
return (ret);
if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
msg_info("read from %08X [%08lX] (%d bytes => %ld (0x%X))",
(unsigned int)bio, (unsigned long)argp, argi,
ret, (unsigned int)ret);
pfixtls_dump(argp, (int) ret);
return (ret);
} else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
msg_info("write to %08X [%08lX] (%d bytes => %ld (0x%X))",
(unsigned int)bio, (unsigned long)argp, argi,
ret, (unsigned int)ret);
pfixtls_dump(argp, (int) ret);
}
return (ret);
}
static SSL_SESSION *get_session_cb(SSL *ssl, unsigned char *SessionID,
int length, int *copy)
{
SSL_SESSION *session;
char idstring[2 * ID_MAXLENGTH + 1];
int n;
int uselength;
int hex_length;
const char *session_hex;
pfixtls_scache_info_t scache_info;
unsigned char nibble, *data, *sess_data;
if (length > ID_MAXLENGTH)
uselength = ID_MAXLENGTH;
else
uselength = length;
for(n=0 ; n < uselength ; n++)
sprintf(idstring + 2 * n, "%02x", SessionID[n]);
if (var_smtpd_tls_loglevel >= 3)
msg_info("Trying to reload Session from disc: %s", idstring);
session = NULL;
session_hex = dict_get(scache_db, idstring);
if (session_hex) {
hex_length = strlen(session_hex);
data = (unsigned char *)mymalloc(hex_length / 2);
if (!data) {
msg_info("could not allocate memory for session reload");
return(NULL);
}
memset(data, 0, hex_length / 2);
for (n = 0; n < hex_length; n++) {
if ((session_hex[n] >= '0') && (session_hex[n] <= '9'))
nibble = session_hex[n] - '0';
else
nibble = session_hex[n] - 'A' + 10;
if (n % 2)
data[n / 2] |= nibble;
else
data[n / 2] |= (nibble << 4);
}
memcpy(&scache_info, data, sizeof(pfixtls_scache_info_t));
if ((scache_info.scache_db_version != scache_db_version) ||
(scache_info.openssl_version != openssl_version) ||
(scache_info.timestamp + var_smtpd_tls_scache_timeout < time(NULL)))
dict_del(scache_db, idstring);
else {
sess_data = data + sizeof(pfixtls_scache_info_t);
session = d2i_SSL_SESSION(NULL, &sess_data,
hex_length / 2 - sizeof(pfixtls_scache_info_t));
if (!session)
pfixtls_print_errors();
}
myfree((char *)data);
}
if (session && (var_smtpd_tls_loglevel >= 3))
msg_info("Successfully reloaded session from disc");
return (session);
}
static SSL_SESSION *load_clnt_session(const char *hostname,
int enforce_peername)
{
SSL_SESSION *session = NULL;
char idstring[ID_MAXLENGTH + 1];
int n;
int uselength;
int length;
int hex_length;
const char *session_hex;
pfixtls_scache_info_t scache_info;
unsigned char nibble, *data, *sess_data;
length = strlen(hostname);
if (length > ID_MAXLENGTH)
uselength = ID_MAXLENGTH;
else
uselength = length;
for(n=0 ; n < uselength ; n++)
idstring[n] = tolower(hostname[n]);
idstring[uselength] = '\0';
if (var_smtp_tls_loglevel >= 3)
msg_info("Trying to reload Session from disc: %s", idstring);
session_hex = dict_get(scache_db, idstring);
if (session_hex) {
hex_length = strlen(session_hex);
data = (unsigned char *)mymalloc(hex_length / 2);
if (!data) {
msg_info("could not allocate memory for session reload");
return(NULL);
}
memset(data, 0, hex_length / 2);
for (n = 0; n < hex_length; n++) {
if ((session_hex[n] >= '0') && (session_hex[n] <= '9'))
nibble = session_hex[n] - '0';
else
nibble = session_hex[n] - 'A' + 10;
if (n % 2)
data[n / 2] |= nibble;
else
data[n / 2] |= (nibble << 4);
}
memcpy(&scache_info, data, sizeof(pfixtls_scache_info_t));
if ((scache_info.scache_db_version != scache_db_version) ||
(scache_info.openssl_version != openssl_version) ||
(scache_info.timestamp + var_smtpd_tls_scache_timeout < time(NULL)))
dict_del(scache_db, idstring);
else if (enforce_peername && (!scache_info.enforce_peername))
dict_del(scache_db, idstring);
else {
sess_data = data + sizeof(pfixtls_scache_info_t);
session = d2i_SSL_SESSION(NULL, &sess_data,
hex_length / 2 - sizeof(time_t));
strncpy(SSL_SESSION_get_ex_data(session, TLSpeername_index),
idstring, ID_MAXLENGTH + 1);
if (!session)
pfixtls_print_errors();
}
myfree((char *)data);
}
if (session && (var_smtp_tls_loglevel >= 3))
msg_info("Successfully reloaded session from disc");
return (session);
}
static void create_client_lookup_id(char *idstring, char *hostname)
{
int n, len, uselength;
len = strlen(hostname);
if (len > ID_MAXLENGTH)
uselength = ID_MAXLENGTH;
else
uselength = len;
for (n = 0 ; n < uselength ; n++)
idstring[n] = tolower(hostname[n]);
idstring[uselength] = '\0';
}
static void create_server_lookup_id(char *idstring, SSL_SESSION *session)
{
int n, uselength;
if (session->session_id_length > ID_MAXLENGTH)
uselength = ID_MAXLENGTH;
else
uselength = session->session_id_length;
for(n = 0; n < uselength ; n++)
sprintf(idstring + 2 * n, "%02x", session->session_id[n]);
}
static void remove_session_cb(SSL_CTX *ctx, SSL_SESSION *session)
{
char idstring[2 * ID_MAXLENGTH + 1];
char *hostname;
if (pfixtls_clientengine) {
hostname = SSL_SESSION_get_ex_data(session, TLSpeername_index);
create_client_lookup_id(idstring, hostname);
if (var_smtp_tls_loglevel >= 3)
msg_info("Trying to remove session from disc: %s", idstring);
}
else {
create_server_lookup_id(idstring, session);
if (var_smtpd_tls_loglevel >= 3)
msg_info("Trying to remove session from disc: %s", idstring);
}
if (scache_db)
dict_del(scache_db, idstring);
}
static int new_peername_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
int idx, long argl, void *argp)
{
char *peername;
peername = (char *)mymalloc(ID_MAXLENGTH + 1);
if (!peername)
return 0;
peername[0] = '\0';
return CRYPTO_set_ex_data(ad, idx, peername);
}
static void free_peername_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
int idx, long argl, void *argp)
{
myfree(CRYPTO_get_ex_data(ad, idx));
}
static int dup_peername_func(CRYPTO_EX_DATA *to, CRYPTO_EX_DATA *from,
void *from_d, int idx, long argl, void *argp)
{
char *peername_old, *peername_new;
peername_old = CRYPTO_get_ex_data(from, idx);
peername_new = CRYPTO_get_ex_data(to, idx);
if (!peername_old || !peername_new)
return 0;
memcpy(peername_new, peername_old, ID_MAXLENGTH + 1);
return 1;
}
static int new_session_cb(SSL *ssl, SSL_SESSION *session)
{
char idstring[2 * ID_MAXLENGTH + 1];
int n;
int dsize;
int len;
unsigned char *data, *sess_data;
pfixtls_scache_info_t scache_info;
char *hexdata, *hostname;
TLScontext_t *TLScontext;
if (pfixtls_clientengine) {
TLScontext = SSL_get_ex_data(ssl, TLScontext_index);
hostname = TLScontext->peername_save;
create_client_lookup_id(idstring, hostname);
strncpy(SSL_SESSION_get_ex_data(session, TLSpeername_index),
hostname, ID_MAXLENGTH + 1);
scache_info.enforce_peername =
(TLScontext->enforce_verify_errors && TLScontext->enforce_CN);
if (var_smtp_tls_loglevel >= 3)
msg_info("Trying to save session for hostID to disc: %s", idstring);
#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L)
session->verify_result = SSL_get_verify_result(TLScontext->con);
#endif
}
else {
create_server_lookup_id(idstring, session);
if (var_smtpd_tls_loglevel >= 3)
msg_info("Trying to save Session to disc: %s", idstring);
}
dsize = i2d_SSL_SESSION(session, NULL);
if (dsize < 0) {
msg_info("Could not access session");
return 0;
}
data = (unsigned char *)mymalloc(dsize + sizeof(pfixtls_scache_info_t));
if (!data) {
msg_info("could not allocate memory for SSL session");
return 0;
}
scache_info.scache_db_version = scache_db_version;
scache_info.openssl_version = openssl_version;
scache_info.timestamp = time(NULL);
memcpy(data, &scache_info, sizeof(pfixtls_scache_info_t));
sess_data = data + sizeof(pfixtls_scache_info_t);
len = i2d_SSL_SESSION(session, &sess_data);
len += sizeof(pfixtls_scache_info_t);
hexdata = (char *)mymalloc(2 * len + 1);
if (!hexdata) {
msg_info("could not allocate memory for SSL session (HEX)");
myfree((char *)data);
return 0;
}
for (n = 0; n < len; n++) {
hexdata[n * 2] = hexcodes[(data[n] & 0xf0) >> 4];
hexdata[(n * 2) + 1] = hexcodes[(data[n] & 0x0f)];
}
hexdata[len * 2] = '\0';
if (strlen(idstring) + strlen(hexdata) < 8000)
dict_put(scache_db, idstring, hexdata);
myfree(hexdata);
myfree((char *)data);
return (1);
}
static void pfixtls_exchange_seed(void)
{
unsigned char buffer[1024];
if (rand_exch_fd == -1)
return;
if (myflock(rand_exch_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) != 0)
msg_info("Could not lock random exchange file: %s",
strerror(errno));
lseek(rand_exch_fd, 0, SEEK_SET);
if (read(rand_exch_fd, buffer, 1024) < 0)
msg_fatal("reading exchange file failed");
RAND_seed(buffer, 1024);
RAND_bytes(buffer, 1024);
lseek(rand_exch_fd, 0, SEEK_SET);
if (write(rand_exch_fd, buffer, 1024) != 1024)
msg_fatal("Writing exchange file failed");
if (myflock(rand_exch_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) != 0)
msg_fatal("Could not unlock random exchange file: %s",
strerror(errno));
}
int pfixtls_init_serverengine(int verifydepth, int askcert)
{
int off = 0;
int verify_flags = SSL_VERIFY_NONE;
int rand_bytes;
int rand_source_dev_fd;
int rand_source_socket_fd;
unsigned char buffer[255];
char *CApath;
char *CAfile;
char *s_cert_file;
char *s_key_file;
char *s_dcert_file;
char *s_dkey_file;
FILE *paramfile;
if (pfixtls_serverengine)
return (0);
if (var_smtpd_tls_loglevel >= 2)
msg_info("starting TLS engine");
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
needs_openssl_095_or_later();
#endif
randseed.pid = getpid();
GETTIMEOFDAY(&randseed.tv);
RAND_seed(&randseed, sizeof(randseed_t));
if (*var_tls_daemon_rand_source) {
if (!strncmp(var_tls_daemon_rand_source, "dev:", 4)) {
rand_source_dev_fd = open(var_tls_daemon_rand_source + 4, 0, 0);
if (rand_source_dev_fd == -1)
msg_info("Could not open entropy device %s",
var_tls_daemon_rand_source);
else {
if (var_tls_daemon_rand_bytes > 255)
var_tls_daemon_rand_bytes = 255;
read(rand_source_dev_fd, buffer, var_tls_daemon_rand_bytes);
RAND_seed(buffer, var_tls_daemon_rand_bytes);
close(rand_source_dev_fd);
}
} else if (!strncmp(var_tls_daemon_rand_source, "egd:", 4)) {
rand_source_socket_fd = unix_connect(var_tls_daemon_rand_source +4,
BLOCKING, 10);
if (rand_source_socket_fd == -1)
msg_info("Could not connect to %s", var_tls_daemon_rand_source);
else {
if (var_tls_daemon_rand_bytes > 255)
var_tls_daemon_rand_bytes = 255;
buffer[0] = 1;
buffer[1] = var_tls_daemon_rand_bytes;
if (write(rand_source_socket_fd, buffer, 2) != 2)
msg_info("Could not talk to %s",
var_tls_daemon_rand_source);
else if (read(rand_source_socket_fd, buffer, 1) != 1)
msg_info("Could not read info from %s",
var_tls_daemon_rand_source);
else {
rand_bytes = buffer[0];
read(rand_source_socket_fd, buffer, rand_bytes);
RAND_seed(buffer, rand_bytes);
}
close(rand_source_socket_fd);
}
} else {
RAND_load_file(var_tls_daemon_rand_source,
var_tls_daemon_rand_bytes);
}
}
if (*var_tls_rand_exch_name) {
rand_exch_fd = open(var_tls_rand_exch_name, O_RDWR | O_CREAT, 0600);
if (rand_exch_fd != -1)
pfixtls_exchange_seed();
}
randseed.pid = getpid();
GETTIMEOFDAY(&randseed.tv);
RAND_seed(&randseed, sizeof(randseed_t));
ctx = SSL_CTX_new(SSLv23_server_method());
if (ctx == NULL) {
pfixtls_print_errors();
return (-1);
};
off |= SSL_OP_ALL;
SSL_CTX_set_options(ctx, off);
if (var_smtpd_tls_loglevel >= 2)
SSL_CTX_set_info_callback(ctx, apps_ssl_info_callback);
if (strlen(var_smtpd_tls_cipherlist) != 0)
if (SSL_CTX_set_cipher_list(ctx, var_smtpd_tls_cipherlist) == 0) {
pfixtls_print_errors();
return (-1);
}
if (strlen(var_smtpd_tls_CAfile) == 0)
CAfile = NULL;
else
CAfile = var_smtpd_tls_CAfile;
if (strlen(var_smtpd_tls_CApath) == 0)
CApath = NULL;
else
CApath = var_smtpd_tls_CApath;
if (CAfile || CApath) {
if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath)) {
msg_info("TLS engine: cannot load CA data");
pfixtls_print_errors();
return (-1);
}
if (!SSL_CTX_set_default_verify_paths(ctx)) {
msg_info("TLS engine: cannot set verify paths");
pfixtls_print_errors();
return (-1);
}
}
if (strlen(var_smtpd_tls_cert_file) == 0)
s_cert_file = NULL;
else
s_cert_file = var_smtpd_tls_cert_file;
if (strlen(var_smtpd_tls_key_file) == 0)
s_key_file = NULL;
else
s_key_file = var_smtpd_tls_key_file;
if (strlen(var_smtpd_tls_dcert_file) == 0)
s_dcert_file = NULL;
else
s_dcert_file = var_smtpd_tls_dcert_file;
if (strlen(var_smtpd_tls_dkey_file) == 0)
s_dkey_file = NULL;
else
s_dkey_file = var_smtpd_tls_dkey_file;
if (s_cert_file) {
if (!set_cert_stuff(ctx, s_cert_file, s_key_file)) {
msg_info("TLS engine: cannot load RSA cert/key data");
pfixtls_print_errors();
return (-1);
}
}
if (s_dcert_file) {
if (!set_cert_stuff(ctx, s_dcert_file, s_dkey_file)) {
msg_info("TLS engine: cannot load DSA cert/key data");
pfixtls_print_errors();
return (-1);
}
}
if (!s_cert_file && !s_dcert_file) {
msg_info("TLS engine: do need at least RSA _or_ DSA cert/key data");
return (-1);
}
SSL_CTX_set_tmp_rsa_callback(ctx, tmp_rsa_cb);
SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_cb);
if (strlen(var_smtpd_tls_dh1024_param_file) != 0) {
if ((paramfile = fopen(var_smtpd_tls_dh1024_param_file, "r")) != NULL) {
dh_1024 = PEM_read_DHparams(paramfile, NULL, NULL, NULL);
if (dh_1024 == NULL) {
msg_info("TLS engine: cannot load 1024bit DH parameters");
pfixtls_print_errors();
}
}
else {
msg_info("TLS engine: cannot load 1024bit DH parameters: %s: %s",
var_smtpd_tls_dh1024_param_file, strerror(errno));
}
}
if (strlen(var_smtpd_tls_dh512_param_file) != 0) {
if ((paramfile = fopen(var_smtpd_tls_dh512_param_file, "r")) != NULL) {
dh_512 = PEM_read_DHparams(paramfile, NULL, NULL, NULL);
if (dh_512 == NULL) {
msg_info("TLS engine: cannot load 512bit DH parameters");
pfixtls_print_errors();
}
}
else {
msg_info("TLS engine: cannot load 512bit DH parameters: %s: %s",
var_smtpd_tls_dh512_param_file, strerror(errno));
}
}
if (askcert)
verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
SSL_CTX_set_verify(ctx, verify_flags, verify_callback);
SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CAfile));
SSL_CTX_sess_set_cache_size(ctx, 1);
SSL_CTX_set_timeout(ctx, var_smtpd_tls_scache_timeout);
SSL_CTX_set_session_id_context(ctx, (void*)&server_session_id_context,
sizeof(server_session_id_context));
if (*var_smtpd_tls_scache_db) {
if (strncmp(var_smtpd_tls_scache_db, "sdbm:", 5))
msg_warn("Only sdbm: type allowed for %s",
var_smtpd_tls_scache_db);
else
scache_db = dict_open(var_smtpd_tls_scache_db, O_RDWR,
DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE);
if (scache_db) {
SSL_CTX_set_session_cache_mode(ctx,
SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_AUTO_CLEAR);
SSL_CTX_sess_set_get_cb(ctx, get_session_cb);
SSL_CTX_sess_set_new_cb(ctx, new_session_cb);
SSL_CTX_sess_set_remove_cb(ctx, remove_session_cb);
}
else
msg_warn("Could not open session cache %s",
var_smtpd_tls_scache_db);
}
TLScontext_index = SSL_get_ex_new_index(0, "TLScontext ex_data index",
NULL, NULL, NULL);
pfixtls_serverengine = 1;
return (0);
}
int pfixtls_start_servertls(VSTREAM *stream, int timeout,
const char *peername, const char *peeraddr,
tls_info_t *tls_info, int requirecert)
{
int sts;
int j;
int verify_flags;
unsigned int n;
TLScontext_t *TLScontext;
SSL_SESSION *session;
SSL_CIPHER *cipher;
X509 *peer;
if (!pfixtls_serverengine) {
msg_info("tls_engine not running");
return (-1);
}
if (var_smtpd_tls_loglevel >= 1)
msg_info("setting up TLS connection from %s[%s]", peername, peeraddr);
TLScontext = (TLScontext_t *)mymalloc(sizeof(TLScontext_t));
if (!TLScontext) {
msg_fatal("Could not allocate 'TLScontext' with mymalloc");
}
if ((TLScontext->con = (SSL *) SSL_new(ctx)) == NULL) {
msg_info("Could not allocate 'TLScontext->con' with SSL_new()");
pfixtls_print_errors();
myfree((char *)TLScontext);
return (-1);
}
if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
msg_info("Could not set application data for 'TLScontext->con'");
pfixtls_print_errors();
SSL_free(TLScontext->con);
myfree((char *)TLScontext);
return (-1);
}
if (requirecert) {
verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
verify_flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
TLScontext->enforce_verify_errors = 1;
SSL_set_verify(TLScontext->con, verify_flags, verify_callback);
}
else {
TLScontext->enforce_verify_errors = 0;
}
TLScontext->enforce_CN = 0;
if (!BIO_new_bio_pair(&TLScontext->internal_bio, BIO_bufsiz,
&TLScontext->network_bio, BIO_bufsiz)) {
msg_info("Could not obtain BIO_pair");
pfixtls_print_errors();
SSL_free(TLScontext->con);
myfree((char *)TLScontext);
return (-1);
}
pfixtls_stir_seed();
pfixtls_exchange_seed();
SSL_set_accept_state(TLScontext->con);
SSL_set_bio(TLScontext->con, TLScontext->internal_bio,
TLScontext->internal_bio);
if (var_smtpd_tls_loglevel >= 3)
BIO_set_callback(SSL_get_rbio(TLScontext->con), bio_dump_cb);
if (var_smtpd_tls_loglevel >= 3)
do_dump = 1;
sts = do_tls_operation(vstream_fileno(stream), timeout, TLScontext,
SSL_accept, NULL, NULL, NULL, 0);
if (sts <= 0) {
msg_info("SSL_accept error from %s[%s]: %d", peername, peeraddr, sts);
pfixtls_print_errors();
SSL_free(TLScontext->con);
myfree((char *)TLScontext);
return (-1);
}
if (var_smtpd_tls_loglevel < 4)
do_dump = 0;
peer = SSL_get_peer_certificate(TLScontext->con);
if (peer != NULL) {
if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
tls_info->peer_verified = 1;
X509_NAME_oneline(X509_get_subject_name(peer),
TLScontext->peer_subject, CCERT_BUFSIZ);
if (var_smtpd_tls_loglevel >= 2)
msg_info("subject=%s", TLScontext->peer_subject);
tls_info->peer_subject = TLScontext->peer_subject;
X509_NAME_oneline(X509_get_issuer_name(peer),
TLScontext->peer_issuer, CCERT_BUFSIZ);
if (var_smtpd_tls_loglevel >= 2)
msg_info("issuer=%s", TLScontext->peer_issuer);
tls_info->peer_issuer = TLScontext->peer_issuer;
if (X509_digest(peer, EVP_md5(), TLScontext->md, &n)) {
for (j = 0; j < (int) n; j++) {
TLScontext->fingerprint[j * 3] =
hexcodes[(TLScontext->md[j] & 0xf0) >> 4];
TLScontext->fingerprint[(j * 3) + 1] =
hexcodes[(TLScontext->md[j] & 0x0f)];
if (j + 1 != (int) n)
TLScontext->fingerprint[(j * 3) + 2] = ':';
else
TLScontext->fingerprint[(j * 3) + 2] = '\0';
}
if (var_smtpd_tls_loglevel >= 1)
msg_info("fingerprint=%s", TLScontext->fingerprint);
tls_info->peer_fingerprint = TLScontext->fingerprint;
}
TLScontext->peer_CN[0] = '\0';
if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName, TLScontext->peer_CN, CCERT_BUFSIZ)) {
msg_info("Could not parse client's subject CN");
pfixtls_print_errors();
}
tls_info->peer_CN = TLScontext->peer_CN;
TLScontext->issuer_CN[0] = '\0';
if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
NID_commonName, TLScontext->issuer_CN, CCERT_BUFSIZ)) {
msg_info("Could not parse client's issuer CN");
pfixtls_print_errors();
}
if (!TLScontext->issuer_CN[0]) {
if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
NID_organizationName, TLScontext->issuer_CN, CCERT_BUFSIZ)) {
msg_info("Could not parse client's issuer Organization");
pfixtls_print_errors();
}
}
tls_info->issuer_CN = TLScontext->issuer_CN;
if (var_smtpd_tls_loglevel >= 1) {
if (tls_info->peer_verified)
msg_info("Verified: subject_CN=%s, issuer=%s",
TLScontext->peer_CN, TLScontext->issuer_CN);
else
msg_info("Unverified: subject_CN=%s, issuer=%s",
TLScontext->peer_CN, TLScontext->issuer_CN);
}
X509_free(peer);
}
if (requirecert) {
if (!tls_info->peer_verified || !tls_info->peer_CN) {
msg_info("Re-used session without peer certificate removed");
session = SSL_get_session(TLScontext->con);
SSL_CTX_remove_session(ctx, session);
return (-1);
}
}
tls_info->protocol = SSL_get_version(TLScontext->con);
cipher = SSL_get_current_cipher(TLScontext->con);
tls_info->cipher_name = SSL_CIPHER_get_name(cipher);
tls_info->cipher_usebits = SSL_CIPHER_get_bits(cipher,
&(tls_info->cipher_algbits));
pfixtls_serveractive = 1;
vstream_control(stream,
VSTREAM_CTL_READ_FN, pfixtls_timed_read,
VSTREAM_CTL_WRITE_FN, pfixtls_timed_write,
VSTREAM_CTL_CONTEXT, (void *)TLScontext,
VSTREAM_CTL_END);
if (var_smtpd_tls_loglevel >= 1)
msg_info("TLS connection established from %s[%s]: %s with cipher %s (%d/%d bits)",
peername, peeraddr,
tls_info->protocol, tls_info->cipher_name,
tls_info->cipher_usebits, tls_info->cipher_algbits);
pfixtls_stir_seed();
return (0);
}
int pfixtls_stop_servertls(VSTREAM *stream, int timeout, int failure,
tls_info_t *tls_info)
{
TLScontext_t *TLScontext;
int retval;
if (pfixtls_serveractive) {
TLScontext = (TLScontext_t *)vstream_context(stream);
if (!failure) {
retval = do_tls_operation(vstream_fileno(stream), timeout,
TLScontext, SSL_shutdown, NULL, NULL, NULL, 0);
if (retval == 0)
do_tls_operation(vstream_fileno(stream), timeout, TLScontext,
SSL_shutdown, NULL, NULL, NULL, 0);
}
SSL_free(TLScontext->con);
BIO_free(TLScontext->network_bio);
myfree((char *)TLScontext);
vstream_control(stream,
VSTREAM_CTL_READ_FN, (VSTREAM_FN) NULL,
VSTREAM_CTL_WRITE_FN, (VSTREAM_FN) NULL,
VSTREAM_CTL_CONTEXT, (void *) NULL,
VSTREAM_CTL_END);
SSL_CTX_flush_sessions(ctx, time(NULL));
pfixtls_stir_seed();
pfixtls_exchange_seed();
*tls_info = tls_info_zero;
pfixtls_serveractive = 0;
}
return (0);
}
int pfixtls_init_clientengine(int verifydepth)
{
int off = 0;
int verify_flags = SSL_VERIFY_NONE;
int rand_bytes;
int rand_source_dev_fd;
int rand_source_socket_fd;
unsigned char buffer[255];
char *CApath;
char *CAfile;
char *c_cert_file;
char *c_key_file;
if (pfixtls_clientengine)
return (0);
if (var_smtp_tls_loglevel >= 2)
msg_info("starting TLS engine");
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
needs_openssl_095_or_later();
#endif
randseed.pid = getpid();
GETTIMEOFDAY(&randseed.tv);
RAND_seed(&randseed, sizeof(randseed_t));
if (*var_tls_daemon_rand_source) {
if (!strncmp(var_tls_daemon_rand_source, "dev:", 4)) {
rand_source_dev_fd = open(var_tls_daemon_rand_source + 4, 0, 0);
if (rand_source_dev_fd == -1)
msg_info("Could not open entropy device %s",
var_tls_daemon_rand_source);
else {
if (var_tls_daemon_rand_bytes > 255)
var_tls_daemon_rand_bytes = 255;
read(rand_source_dev_fd, buffer, var_tls_daemon_rand_bytes);
RAND_seed(buffer, var_tls_daemon_rand_bytes);
close(rand_source_dev_fd);
}
} else if (!strncmp(var_tls_daemon_rand_source, "egd:", 4)) {
rand_source_socket_fd = unix_connect(var_tls_daemon_rand_source +4,
BLOCKING, 10);
if (rand_source_socket_fd == -1)
msg_info("Could not connect to %s", var_tls_daemon_rand_source);
else {
if (var_tls_daemon_rand_bytes > 255)
var_tls_daemon_rand_bytes = 255;
buffer[0] = 1;
buffer[1] = var_tls_daemon_rand_bytes;
if (write(rand_source_socket_fd, buffer, 2) != 2)
msg_info("Could not talk to %s",
var_tls_daemon_rand_source);
else if (read(rand_source_socket_fd, buffer, 1) != 1)
msg_info("Could not read info from %s",
var_tls_daemon_rand_source);
else {
rand_bytes = buffer[0];
read(rand_source_socket_fd, buffer, rand_bytes);
RAND_seed(buffer, rand_bytes);
}
close(rand_source_socket_fd);
}
} else {
RAND_load_file(var_tls_daemon_rand_source,
var_tls_daemon_rand_bytes);
}
}
if (*var_tls_rand_exch_name) {
rand_exch_fd = open(var_tls_rand_exch_name, O_RDWR | O_CREAT, 0600);
if (rand_exch_fd != -1)
pfixtls_exchange_seed();
}
randseed.pid = getpid();
GETTIMEOFDAY(&randseed.tv);
RAND_seed(&randseed, sizeof(randseed_t));
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL) {
pfixtls_print_errors();
return (-1);
};
off |= SSL_OP_ALL;
SSL_CTX_set_options(ctx, off);
if (var_smtp_tls_loglevel >= 2)
SSL_CTX_set_info_callback(ctx, apps_ssl_info_callback);
if (strlen(var_smtp_tls_cipherlist) != 0)
if (SSL_CTX_set_cipher_list(ctx, var_smtp_tls_cipherlist) == 0) {
pfixtls_print_errors();
return (-1);
}
if (strlen(var_smtp_tls_CAfile) == 0)
CAfile = NULL;
else
CAfile = var_smtp_tls_CAfile;
if (strlen(var_smtp_tls_CApath) == 0)
CApath = NULL;
else
CApath = var_smtp_tls_CApath;
if (CAfile || CApath) {
if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath)) {
msg_info("TLS engine: cannot load CA data");
pfixtls_print_errors();
return (-1);
}
if (!SSL_CTX_set_default_verify_paths(ctx)) {
msg_info("TLS engine: cannot set verify paths");
pfixtls_print_errors();
return (-1);
}
}
if (strlen(var_smtp_tls_cert_file) == 0)
c_cert_file = NULL;
else
c_cert_file = var_smtp_tls_cert_file;
if (strlen(var_smtp_tls_key_file) == 0)
c_key_file = NULL;
else
c_key_file = var_smtp_tls_key_file;
if (c_cert_file || c_key_file)
if (!set_cert_stuff(ctx, c_cert_file, c_key_file)) {
msg_info("TLS engine: cannot load cert/key data");
pfixtls_print_errors();
return (-1);
}
SSL_CTX_set_tmp_rsa_callback(ctx, tmp_rsa_cb);
SSL_CTX_set_verify(ctx, verify_flags, verify_callback);
SSL_CTX_sess_set_cache_size(ctx, 1);
SSL_CTX_set_timeout(ctx, var_smtp_tls_scache_timeout);
if (*var_smtp_tls_scache_db) {
if (strncmp(var_smtp_tls_scache_db, "sdbm:", 5))
msg_warn("Only sdbm: type allowed for %s",
var_smtp_tls_scache_db);
else
scache_db = dict_open(var_smtp_tls_scache_db, O_RDWR,
DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE);
if (!scache_db)
msg_warn("Could not open session cache %s",
var_smtp_tls_scache_db);
SSL_CTX_set_session_cache_mode(ctx,
SSL_SESS_CACHE_CLIENT|SSL_SESS_CACHE_NO_AUTO_CLEAR);
SSL_CTX_sess_set_new_cb(ctx, new_session_cb);
}
TLScontext_index = SSL_get_ex_new_index(0, "TLScontext ex_data index",
NULL, NULL, NULL);
TLSpeername_index = SSL_SESSION_get_ex_new_index(0,
"TLSpeername ex_data index",
new_peername_func,
dup_peername_func,
free_peername_func);
pfixtls_clientengine = 1;
return (0);
}
int pfixtls_start_clienttls(VSTREAM *stream, int timeout,
int enforce_peername,
const char *peername,
tls_info_t *tls_info)
{
int sts;
SSL_SESSION *session, *old_session;
SSL_CIPHER *cipher;
X509 *peer;
int verify_flags;
TLScontext_t *TLScontext;
if (!pfixtls_clientengine) {
msg_info("tls_engine not running");
return (-1);
}
if (var_smtpd_tls_loglevel >= 1)
msg_info("setting up TLS connection to %s", peername);
TLScontext = (TLScontext_t *)mymalloc(sizeof(TLScontext_t));
if (!TLScontext) {
msg_fatal("Could not allocate 'TLScontext' with mymalloc");
}
if ((TLScontext->con = (SSL *) SSL_new(ctx)) == NULL) {
msg_info("Could not allocate 'TLScontext->con' with SSL_new()");
pfixtls_print_errors();
myfree((char *)TLScontext);
return (-1);
}
if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
msg_info("Could not set application data for 'TLScontext->con'");
pfixtls_print_errors();
SSL_free(TLScontext->con);
myfree((char *)TLScontext);
return (-1);
}
if (enforce_peername) {
verify_flags = SSL_VERIFY_PEER;
TLScontext->enforce_verify_errors = 1;
TLScontext->enforce_CN = 1;
SSL_set_verify(TLScontext->con, verify_flags, verify_callback);
}
else {
TLScontext->enforce_verify_errors = 0;
TLScontext->enforce_CN = 0;
}
TLScontext->hostname_matched = 0;
if (!BIO_new_bio_pair(&TLScontext->internal_bio, BIO_bufsiz,
&TLScontext->network_bio, BIO_bufsiz)) {
msg_info("Could not obtain BIO_pair");
pfixtls_print_errors();
SSL_free(TLScontext->con);
myfree((char *)TLScontext);
return (-1);
}
old_session = NULL;
strncpy(TLScontext->peername_save, peername, ID_MAXLENGTH + 1);
TLScontext->peername_save[ID_MAXLENGTH] = '\0';
(void)lowercase(TLScontext->peername_save);
if (scache_db) {
old_session = load_clnt_session(peername, enforce_peername);
if (old_session) {
SSL_set_session(TLScontext->con, old_session);
#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L)
SSL_set_verify_result(TLScontext->con, old_session->verify_result);
#endif
}
}
pfixtls_stir_seed();
pfixtls_exchange_seed();
SSL_set_connect_state(TLScontext->con);
SSL_set_bio(TLScontext->con, TLScontext->internal_bio,
TLScontext->internal_bio);
if (var_smtp_tls_loglevel >= 3)
BIO_set_callback(SSL_get_rbio(TLScontext->con), bio_dump_cb);
if (var_smtp_tls_loglevel >= 3)
do_dump = 1;
sts = do_tls_operation(vstream_fileno(stream), timeout, TLScontext,
SSL_connect, NULL, NULL, NULL, 0);
if (sts <= 0) {
msg_info("SSL_connect error to %s: %d", peername, sts);
pfixtls_print_errors();
session = SSL_get_session(TLScontext->con);
if (session) {
SSL_CTX_remove_session(ctx, session);
if (var_smtp_tls_loglevel >= 2)
msg_info("SSL session removed");
}
if ((old_session) && (!SSL_session_reused(TLScontext->con)))
SSL_SESSION_free(old_session);
SSL_free(TLScontext->con);
myfree((char *)TLScontext);
return (-1);
}
if (!SSL_session_reused(TLScontext->con)) {
SSL_SESSION_free(old_session);
}
else if (var_smtp_tls_loglevel >= 3)
msg_info("Reusing old session");
if (var_smtp_tls_loglevel < 4)
do_dump = 0;
peer = SSL_get_peer_certificate(TLScontext->con);
if (peer != NULL) {
if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
tls_info->peer_verified = 1;
tls_info->hostname_matched = TLScontext->hostname_matched;
TLScontext->peer_CN[0] = '\0';
if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName, TLScontext->peer_CN, CCERT_BUFSIZ)) {
msg_info("Could not parse server's subject CN");
pfixtls_print_errors();
}
tls_info->peer_CN = TLScontext->peer_CN;
TLScontext->issuer_CN[0] = '\0';
if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
NID_commonName, TLScontext->issuer_CN, CCERT_BUFSIZ)) {
msg_info("Could not parse server's issuer CN");
pfixtls_print_errors();
}
if (!TLScontext->issuer_CN[0]) {
if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
NID_organizationName, TLScontext->issuer_CN, CCERT_BUFSIZ)) {
msg_info("Could not parse server's issuer Organization");
pfixtls_print_errors();
}
}
tls_info->issuer_CN = TLScontext->issuer_CN;
if (var_smtp_tls_loglevel >= 1) {
if (tls_info->peer_verified)
msg_info("Verified: subject_CN=%s, issuer=%s",
TLScontext->peer_CN, TLScontext->issuer_CN);
else
msg_info("Unverified: subject_CN=%s, issuer=%s",
TLScontext->peer_CN, TLScontext->issuer_CN);
}
X509_free(peer);
}
tls_info->protocol = SSL_get_version(TLScontext->con);
cipher = SSL_get_current_cipher(TLScontext->con);
tls_info->cipher_name = SSL_CIPHER_get_name(cipher);
tls_info->cipher_usebits = SSL_CIPHER_get_bits(cipher,
&(tls_info->cipher_algbits));
pfixtls_clientactive = 1;
vstream_control(stream,
VSTREAM_CTL_READ_FN, pfixtls_timed_read,
VSTREAM_CTL_WRITE_FN, pfixtls_timed_write,
VSTREAM_CTL_CONTEXT, (void *)TLScontext,
VSTREAM_CTL_END);
if (var_smtp_tls_loglevel >= 1)
msg_info("TLS connection established to %s: %s with cipher %s (%d/%d bits)",
peername, tls_info->protocol, tls_info->cipher_name,
tls_info->cipher_usebits, tls_info->cipher_algbits);
pfixtls_stir_seed();
return (0);
}
int pfixtls_stop_clienttls(VSTREAM *stream, int timeout, int failure,
tls_info_t *tls_info)
{
TLScontext_t *TLScontext;
int retval;
if (pfixtls_clientactive) {
TLScontext = (TLScontext_t *)vstream_context(stream);
if (!failure) {
retval = do_tls_operation(vstream_fileno(stream), timeout,
TLScontext, SSL_shutdown, NULL, NULL, NULL, 0);
if (retval == 0)
do_tls_operation(vstream_fileno(stream), timeout, TLScontext,
SSL_shutdown, NULL, NULL, NULL, 0);
}
SSL_free(TLScontext->con);
BIO_free(TLScontext->network_bio);
myfree((char *)TLScontext);
vstream_control(stream,
VSTREAM_CTL_READ_FN, (VSTREAM_FN) NULL,
VSTREAM_CTL_WRITE_FN, (VSTREAM_FN) NULL,
VSTREAM_CTL_CONTEXT, (void *) NULL,
VSTREAM_CTL_END);
SSL_CTX_flush_sessions(ctx, time(NULL));
pfixtls_stir_seed();
pfixtls_exchange_seed();
*tls_info = tls_info_zero;
pfixtls_clientactive = 0;
}
return (0);
}
#endif