/* $Id: starttls.C,v 1.18 2006/02/04 01:10:33 dm Exp $ */
/*
*
* Copyright (C) 2005 David Mazieres (dm@uun.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
*/
#include "asmtpd.h"
#include "async_ssl.h"
static bool ssl_initialized;
static u_int64_t ssl_initno;
static SSL_CTX *ctx;
static bssstr ssl_randfile;
static void
ssl_shutdown ()
{
if (ssl_randfile) {
RAND_write_file (ssl_randfile);
ssl_randfile = NULL;
}
}
static u_int64_t ssl_tmpkey_initno;
static RSA *ssl_tmpkey_key;
timecb_t *ssl_tmpkey_tmo;
static void
ssl_tmpkey_erase (bool timedout = false)
{
if (!timedout && ssl_tmpkey_tmo)
timecb_remove (ssl_tmpkey_tmo);
ssl_tmpkey_tmo = NULL;
RSA_free (ssl_tmpkey_key);
ssl_tmpkey_key = NULL;
}
static RSA *
ssl_tmpkey (SSL *ssl, int exp, int keylen)
{
if (ssl_tmpkey_initno != opt->configno)
ssl_tmpkey_erase ();
if (ssl_tmpkey_key)
return ssl_tmpkey_key;
ssl_tmpkey_key = RSA_generate_key (keylen, 17, NULL, NULL);
if (!ssl_tmpkey_tmo)
ssl_tmpkey_tmo = delaycb (3600, wrap (ssl_tmpkey_erase, true));
return ssl_tmpkey_key;
}
static DH *
ssl_dhparm (SSL *, int exp, int len)
{
static qhash<int, DH *> parmtab;
if (DH **dhp = parmtab[len])
return *dhp;
DH *dh = NULL;
str cachefile = strbuf ("%s/dh%d.pem", opt->etcdir.cstr (), len);
if (FILE *fp = fopen (cachefile, "r")) {
dh = PEM_read_DHparams(fp, NULL, NULL, NULL);
fclose (fp);
}
if (!dh) {
warn << "Generating " << len << "-bit DH parameters... ";
err_flush ();
dh = DH_generate_parameters (len, 2, NULL, NULL);
if (!dh) {
warn << "failed: " << ssl_err () << "\n";
return NULL;
}
warnx << "done\n";
int fd = open (cachefile, O_CREAT|O_EXCL|O_WRONLY, 0644);
if (fd >= 0) {
if (FILE *fp = fdopen (fd, "w")) {
PEM_write_DHparams (fp, dh);
fclose (fp);
}
else
close (fd);
}
}
parmtab.insert (len, dh);
return dh;
}
static int
verify_cb (int, X509_STORE_CTX *)
{
return 1;
}
bool
ssl_init ()
{
if (ssl_initno == opt->configno)
return opt->ssl_status > 0;
opt->ssl_status = -1;
ssl_initno = opt->configno;
ssl_tmpkey_erase ();
if (!opt->ssl)
return false;
struct stat sb;
if (stat (opt->ssl_key, &sb) < 0) {
if (errno == ENOENT)
warn << "STARTTLS disabled (no file " << opt->ssl_key << ")\n";
else
warn << opt->ssl_key << ": " << strerror (errno) << "\n";
return false;
}
if (sb.st_mode & 044)
warn << "DANGER: " << opt->ssl_key << " should be read-protected\n";
if (!ssl_initialized) {
ssl_initialized = true;
SSL_load_error_strings ();
SSL_library_init ();
#ifdef SFS_DEV_RANDOM
if (RAND_load_file (SFS_DEV_RANDOM, 1024) <= 0)
warn << "DANGER: " << SFS_DEV_RANDOM ": " << ssl_err () << "\n";
#else /* !SFS_DEV_RANDOM */
ssl_randfile = opt->etcdir << "/.rnd";
if (RAND_load_file (ssl_randfile, 1024) <= 0)
warn << "DANGER: " << ssl_randfile << ": " << ssl_err () << "\n";
#endif /* !SFS_DEV_RANDOM */
atexit (ssl_shutdown);
}
else if (ssl_randfile)
RAND_write_file (ssl_randfile);
if (ctx)
SSL_CTX_free (ctx);
ctx = SSL_CTX_new (SSLv23_server_method ());
if (!ctx) {
warn << "SSL_CTX_new: " << ssl_err () << "\n";
return false;
}
bool verify = true;
if (SSL_CTX_load_verify_locations (ctx, opt->ssl_ca, NULL) <= 0) {
warn << opt->ssl_ca << ": " << ssl_err () << "\n";
verify = false;
}
if (verify)
SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_cb);
if (!access (opt->ssl_crl, 0)) {
X509_STORE *store = SSL_CTX_get_cert_store (ctx);
X509_LOOKUP *lookup;
if ((lookup = X509_STORE_add_lookup (store, X509_LOOKUP_file ()))
&& (X509_load_crl_file(lookup, opt->ssl_crl, X509_FILETYPE_PEM) == 1))
X509_STORE_set_flags(store, (X509_V_FLAG_CRL_CHECK |
X509_V_FLAG_CRL_CHECK_ALL));
}
if (SSL_CTX_use_certificate_file (ctx, opt->ssl_cert,
SSL_FILETYPE_PEM) <= 0) {
warn << opt->ssl_cert << ": " << ssl_err () << "\n";
return false;
}
if (SSL_CTX_use_PrivateKey_file (ctx, opt->ssl_key, SSL_FILETYPE_PEM) <= 0) {
warn << opt->ssl_key << ": " << ssl_err () << "\n";
return false;
}
if (SSL_CTX_check_private_key (ctx) <= 0) {
warn << opt->ssl_key << ": " << ssl_err () << "\n";
return false;
}
SSL_CTX_set_options (ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2);
if (opt->ssl_ciphers
&& SSL_CTX_set_cipher_list (ctx, opt->ssl_ciphers) <= 0) {
warn << "Cipher list " << opt->ssl_ciphers << ": " << ssl_err () << "\n";
return false;
}
SSL_CTX_set_tmp_rsa_callback (ctx, ssl_tmpkey);
SSL_CTX_set_tmp_dh_callback (ctx, ssl_dhparm);
u_char sessid[SSL_MAX_SSL_SESSION_ID_LENGTH];
if (RAND_pseudo_bytes (sessid, sizeof (sessid)) >= 0)
SSL_CTX_set_session_id_context (ctx, sessid, sizeof (sessid));
opt->ssl_status = 1;
return true;
}
str
smtpd::helo_starttls ()
{
if (!encrypted && !auth_user && opt->ssl_status > 0)
return "250-STARTTLS\r\n";
return "";
}
void
smtpd::cmd_starttls (str cmd, str arg)
{
aiossl *ssl;
if (encrypted || auth_user || opt->ssl_status <= 0
|| !(ssl = dynamic_cast<aiossl *> (aio.get ()))) {
respond ("502 command not implemented\r\n");
return;
}
reset ();
aio << "220 Starting TLS\r\n";
ssl->startssl (ctx, true);
encrypted = true;
ssl->verify_cb (wrap (this, &smtpd::set_quota_user));
cmdwait = true;
aio->readline (wrap (this, &smtpd::getcmd));
}
void
smtpd::set_quota_user ()
{
if (!encrypted)
return;
aiossl *ssl = dynamic_cast<aiossl *> (aio.get ());
if (ssl && ssl->subject) {
quota_user = ssl->subject;
if (opt->ssl >= 2 && trust < TRUST_AUTH)
trust = TRUST_AUTH;
}
}
void
smtpd::received_starttls (strbuf r) const
{
if (!encrypted)
return;
aiossl *ssl = dynamic_cast<aiossl *> (aio.get ());
if (!ssl || !ssl->cipher)
return;
r << " (";
if (char *vers = SSL_get_cipher_version (ssl->get_ssl ()))
r << vers;
else
r << "SSL";
int cipher_bits, alg_bits = 0;
cipher_bits = SSL_get_cipher_bits (ssl->get_ssl (), &alg_bits);
r << " " << ssl->cipher << " " << cipher_bits << "/" << alg_bits;
if (ssl->subject) {
r << ",\n ";
if (ssl->issuer)
r << " issuer=" << ssl->issuer << ",";
r << " subject=" << ssl->subject << ")\n";
}
else
r << ")\n";
}
void
smtpd::env_starttls (vec<str> *envp) const
{
if (!encrypted)
return;
aiossl *ssl = dynamic_cast<aiossl *> (aio.get ());
if (!ssl || !ssl->cipher)
return;
envp->push_back (strbuf () << "SSL_CIPHER=" << ssl->cipher);
int cipher_bits, alg_bits = 0;
cipher_bits = SSL_get_cipher_bits (ssl->get_ssl (), &alg_bits);
envp->push_back (strbuf ("SSL_CIPHER_BITS=%d", cipher_bits));
envp->push_back (strbuf ("SSL_ALG_BITS=%d", alg_bits));
if (char *vers = SSL_get_cipher_version (ssl->get_ssl ()))
envp->push_back (strbuf () << "SSL_VERSION=" << vers);
if (ssl->issuer)
envp->push_back (strbuf () << "SSL_ISSUER=" << ssl->issuer);
if (ssl->issuer_dn)
envp->push_back (strbuf () << "SSL_ISSUER_DN=" << ssl->issuer_dn);
if (ssl->subject)
envp->push_back (strbuf () << "SSL_SUBJECT=" << ssl->subject);
if (ssl->subject_dn)
envp->push_back (strbuf () << "SSL_SUBJECT_DN=" << ssl->subject_dn);
}
syntax highlighted by Code2HTML, v. 0.9.1