ssl-proxy-openssl.c revision c77b85e6017a3f15943fd1c47340daefd87b1993
/* Copyright (c) 2002-2012 Dovecot authors, see the included COPYING file */
#include "login-common.h"
#include "array.h"
#include "ioloop.h"
#include "network.h"
#include "ostream.h"
#include "read-full.h"
#include "safe-memset.h"
#include "hash.h"
#include "llist.h"
#include "master-interface.h"
#include "client-common.h"
#include "ssl-proxy.h"
#include <fcntl.h>
#include <unistd.h>
#ifdef HAVE_OPENSSL
#include "iostream-openssl.h"
/* Check every 30 minutes if parameters file has been updated */
#define SSL_PARAMETERS_PATH "ssl-params"
#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME /* FIXME: this may be unnecessary.. */
#endif
enum ssl_io_action {
};
struct ssl_proxy {
int refcount;
const struct login_settings *set;
unsigned char plainout_buf[1024];
unsigned int plainout_size;
unsigned char sslout_buf[1024];
unsigned int sslout_size;
void *handshake_context;
const char *cert_error;
char *last_error;
unsigned int handshaked:1;
unsigned int destroyed:1;
unsigned int cert_received:1;
unsigned int cert_broken:1;
unsigned int client_proxy:1;
};
struct ssl_parameters {
const char *path;
int fd;
};
struct ssl_server_context {
const char *cert;
const char *key;
const char *ca;
const char *cipher_list;
const char *protocols;
bool verify_client_cert;
};
static int extdata_index;
static struct hash_table *ssl_servers;
static SSL_CTX *ssl_client_ctx;
static unsigned int ssl_proxy_count;
static struct ssl_proxy *ssl_proxies;
static struct ssl_parameters ssl_params;
static int ssl_username_nid;
static ENGINE *ssl_engine;
static struct ssl_server_context *
static unsigned int ssl_server_context_hash(const void *p)
{
const struct ssl_server_context *ctx = p;
unsigned int i, g, h = 0;
/* checking for different certs is typically good enough,
and it should be enough to check only the first few bytes. */
if ((g = h & 0xf0000000UL)) {
h = h ^ (g >> 24);
h = h ^ g;
}
}
return h;
}
{
return 1;
return 1;
return 1;
return 1;
return 1;
}
static void ssl_params_corrupted(void)
{
i_fatal("Corrupted SSL parameters file: "
PKG_STATEDIR"/ssl-parameters.dat");
}
{
int ret;
if (ret == 0)
}
{
unsigned char *buf;
const unsigned char *cbuf;
unsigned int len;
int bits;
/* read bit size. 0 ends the DH parameters list. */
if (bits == 0)
return FALSE;
/* read data size. */
switch (bits) {
case 512:
break;
case 1024:
break;
default:
}
return TRUE;
}
{
}
}
}
{
char c;
int ret;
return;
return;
}
while (read_dh_parameters_next(params)) ;
else if (ret != 0) {
/* more data than expected */
}
}
{
switch (action) {
case SSL_ADD_INPUT:
break;
break;
case SSL_REMOVE_INPUT:
break;
case SSL_ADD_OUTPUT:
break;
break;
case SSL_REMOVE_OUTPUT:
break;
}
}
{
if (block) {
} else {
plain_read, proxy);
}
}
}
{
/* buffer full, block input until it's written */
return;
}
sizeof(proxy->sslout_buf) -
proxy->sslout_size);
if (ret <= 0) {
if (ret < 0)
break;
} else {
if (!corked) {
}
}
}
if (corked)
}
{
if (ret < 0)
else {
if (proxy->plainout_size > 0) {
plain_write, proxy);
}
} else {
}
}
}
{
const char *ret;
char *buf;
if ((flags & ERR_TXT_STRING) != 0)
return ret;
}
static const char *ssl_last_error(void)
{
unsigned long err;
const char *data;
int flags;
while (err != 0 && ERR_peek_error() != 0) {
i_error("SSL: Stacked error: %s",
err = ERR_get_error();
}
if (err == 0) {
if (errno != 0)
return "Unknown error";
}
}
const char *func_name)
{
int err;
switch (err) {
case SSL_ERROR_WANT_READ:
break;
case SSL_ERROR_WANT_WRITE:
break;
case SSL_ERROR_SYSCALL:
/* eat up the error queue */
if (ERR_peek_error() != 0)
errstr = ssl_last_error();
else if (ret != 0)
else {
/* EOF. */
errstr = "Disconnected";
break;
}
break;
case SSL_ERROR_ZERO_RETURN:
/* clean connection closing */
break;
case SSL_ERROR_SSL:
i_error("OpenSSL malloc() failed. "
"You may need to increase login_process_size");
}
func_name, ssl_last_error());
break;
default:
break;
}
}
}
{
int ret;
if (proxy->client_proxy) {
if (ret != 1) {
return;
}
} else {
if (ret != 1) {
return;
}
}
}
}
{
int ret;
sizeof(proxy->plainout_buf) -
if (ret <= 0) {
break;
} else {
}
}
}
{
int ret;
if (ret <= 0)
else {
proxy->sslout_size);
}
}
{
if (!proxy->handshaked)
if (proxy->handshaked) {
else
if (proxy->sslout_size == 0)
else {
}
}
}
static int
{
int sfd[2];
if (!ssl_initialized) {
i_error("SSL support not enabled in configuration");
return -1;
}
return -1;
}
return -1;
}
i_error("socketpair() failed: %m");
return -1;
}
return sfd[1];
}
static struct ssl_server_context *
{
return ctx;
}
const struct login_settings *set,
{
struct ssl_server_context *ctx;
}
const struct login_settings *set,
{
int ret;
if (ret < 0)
return -1;
return ret;
}
{
}
{
}
{
}
{
}
{
}
{
char *name;
int len;
return NULL;
return NULL; /* we should have had it.. */
ssl_username_nid, NULL, 0);
if (len < 0)
name = "";
else {
name = "";
/* NUL characters in name. Someone's trying to fake
being another user? Don't allow it. */
name = "";
}
}
}
{
return proxy->handshaked;
}
{
return proxy->last_error;
}
{
const SSL_CIPHER *cipher;
const char *comp_str;
if (!proxy->handshaked)
return "";
return t_strdup_printf("%s with cipher %s (%d/%d bits)%s",
}
{
#ifdef HAVE_SSL_COMPRESSION
const COMP_METHOD *comp;
#else
return NULL;
#endif
}
{
"(Unknown error)";
}
{
}
{
return;
}
{
return;
}
{
}
{
/* Well, I'm not exactly sure why the logic in here is this.
It's the same as in Postfix, so it can't be too wrong. */
return ssl_params.dh_512;
return ssl_params.dh_1024;
}
{
return;
if ((where & SSL_CB_ALERT) != 0) {
i_warning("SSL alert: where=0x%x, ret=%d: %s %s [%s]",
} else if (ret == 0) {
i_warning("SSL failed: where=0x%x: %s [%s]",
} else {
i_warning("SSL: where=0x%x, ret=%d: %s [%s]",
}
}
{
char buf[1024];
/* no CRL given with the CA list. don't worry about it. */
preverify_ok = 1;
}
if (!preverify_ok)
}
if (preverify_ok)
else {
i_info("Invalid certificate: %s: %s",
}
}
/* Return success anyway, because if ssl_require_client_cert=no we
could still allow authentication. */
return 1;
}
static int
void *userdata)
{
i_error("SSL private key file is password protected, "
"but password isn't given");
return 0;
}
return 0;
}
unsigned int ssl_proxy_get_count(void)
{
return ssl_proxy_count;
}
static bool is_pem_key(const char *cert)
{
}
{
/* mostly just copy&pasted from X509_load_cert_crl_file() */
int i;
i_fatal("BIO_new_mem_buf() failed");
}
for(i = 0; i < sk_X509_INFO_num(inf); i++) {
}
}
}
}
bool load_xnames)
{
/* enable all SSL workarounds, except empty fragments as it
makes SSL more vulnerable against attacks */
#ifdef SSL_MODE_RELEASE_BUFFERS
#endif
/* set trusted CA certs */
}
if (SSL_CTX_need_tmp_RSA(ssl_ctx))
return xnames;
}
static void
{
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
#endif
/* set list of CA names that are sent to client */
}
static const char *ssl_proxy_get_use_certificate_error(const char *cert)
{
unsigned long err;
err = ERR_peek_error();
return ssl_last_error();
else if (is_pem_key(cert)) {
return "The file contains a private key "
"(you've mixed ssl_cert and ssl_key settings)";
return t_strdup_printf("There is no valid PEM certificate. "
"(You probably forgot '<' from ssl_cert=<%s)", cert);
} else {
return "There is no valid PEM certificate.";
}
}
static EVP_PKEY *
{
char *dup_password;
i_fatal("BIO_new_mem_buf() failed");
i_fatal("Couldn't parse private ssl_key");
return pkey;
}
static const char *ssl_key_load_error(void)
{
unsigned long err = ERR_peek_error();
return "Key is for a different cert than ssl_cert";
else
return ssl_last_error();
}
{
const char *password;
}
static int
{
/* mostly just copy&pasted from SSL_CTX_use_certificate_chain_file() */
X509 *x;
int ret = 0;
i_fatal("BIO_new_mem_buf() failed");
if (x == NULL)
goto end;
#if 0
/* This is in OpenSSL code, but it seems to cause failures.. */
if (ERR_peek_error() != 0)
ret = 0;
#endif
if (ret != 0) {
/* If we could set up our certificate, now proceed to
* the CA certificates.
*/
int r;
unsigned long err;
if (!r) {
ret = 0;
goto end;
}
}
/* When the while loop ends, it's usually just EOF. */
err = ERR_peek_last_error();
else
ret = 0; /* some real error */
}
end:
return ret;
}
#ifdef HAVE_SSL_GET_SERVERNAME
void *context ATTR_UNUSED)
{
struct ssl_server_context *ctx;
const char *host;
void **other_sets;
if (!client->ssl_servername_settings_read) {
&other_sets);
}
}
#endif
enum {
DOVECOT_SSL_PROTO_SSLv2 = 0x01,
DOVECOT_SSL_PROTO_SSLv3 = 0x02,
DOVECOT_SSL_PROTO_TLSv1 = 0x04,
DOVECOT_SSL_PROTO_ALL = 0x07
};
static void
const char *protocols)
{
const char *const *tmp;
bool neg;
if (*name != '!')
else {
name++;
}
else {
i_fatal("Invalid ssl_protocols setting: "
"Unknown protocol '%s'", name);
}
if (neg)
else
include |= proto;
}
if (include != 0) {
/* exclude everything, except those that are included
(and let excludes still override those) */
exclude |= DOVECOT_SSL_PROTO_ALL & ~include;
}
}
static struct ssl_server_context *
{
struct ssl_server_context *ctx;
i_fatal("SSL_CTX_new() failed");
i_fatal("Can't set cipher list to '%s': %s",
}
i_fatal("Can't load ssl_cert: %s",
}
#ifdef HAVE_SSL_GET_SERVERNAME
ssl_servername_callback) != 1) {
if (set->verbose_ssl)
i_debug("OpenSSL library doesn't support SNI");
}
#endif
if (ctx->verify_client_cert)
return ctx;
}
{
}
static void
const struct login_settings *set)
{
return;
i_fatal("Can't load ssl_client_cert: %s",
}
i_fatal("Can't load private ssl_client_key: %s",
}
}
{
i_fatal("SSL_CTX_new() failed");
}
void ssl_proxy_init(void)
{
static char dovecot[] = "dovecot";
unsigned char buf;
return;
if (ssl_engine == NULL) {
i_fatal("Unknown ssl_crypto_device: %s",
}
}
(void)ssl_server_context_init(set);
if (ssl_username_nid == NID_undef) {
i_fatal("Invalid ssl_cert_username_field: %s",
}
does it before chrooting. We might not have enough entropy at
the first try, so this function may fail. It's still been
initialized though. */
ssl_proxy_count = 0;
ssl_proxies = NULL;
}
void ssl_proxy_deinit(void)
{
struct hash_iterate_context *iter;
if (!ssl_initialized)
return;
while (ssl_proxies != NULL)
}
if (ssl_engine != NULL) {
}
EVP_cleanup();
}
#endif