ssl-proxy-openssl.c revision a8c5a86d183db25a57bf193c06b41e092ec2e151
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch/* Copyright (c) 2002-2014 Dovecot authors, see the included COPYING file */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "login-common.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "array.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "ioloop.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "net.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "ostream.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "read-full.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "safe-memset.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "hash.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "llist.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "master-interface.h"
042a58438dc324c6d4de18e4c8a68044d6328e66Stephan Bosch#include "master-service-ssl-settings.h"
042a58438dc324c6d4de18e4c8a68044d6328e66Stephan Bosch#include "client-common.h"
042a58438dc324c6d4de18e4c8a68044d6328e66Stephan Bosch#include "ssl-proxy.h"
042a58438dc324c6d4de18e4c8a68044d6328e66Stephan Bosch
042a58438dc324c6d4de18e4c8a68044d6328e66Stephan Bosch#include <fcntl.h>
042a58438dc324c6d4de18e4c8a68044d6328e66Stephan Bosch#include <unistd.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include <sys/stat.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#ifdef HAVE_OPENSSL
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include "iostream-openssl.h"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include <openssl/crypto.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include <openssl/engine.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include <openssl/x509.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include <openssl/pem.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include <openssl/ssl.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include <openssl/err.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#include <openssl/rand.h>
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x10000000L
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch# define HAVE_ECDH
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#endif
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch/* Check every 30 minutes if parameters file has been updated */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#define SSL_PARAMFILE_CHECK_INTERVAL (60*30)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#define SSL_PARAMETERS_PATH "ssl-params"
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME /* FIXME: this may be unnecessary.. */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch# undef HAVE_SSL_GET_SERVERNAME
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#endif
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschenum ssl_io_action {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch SSL_ADD_INPUT,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch SSL_REMOVE_INPUT,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch SSL_ADD_OUTPUT,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch SSL_REMOVE_OUTPUT
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch};
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstruct ssl_proxy {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int refcount;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch struct ssl_proxy *prev, *next;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch SSL *ssl;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch struct client *client;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch struct ip_addr ip;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const struct login_settings *login_set;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const struct master_service_ssl_settings *ssl_set;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch pool_t set_pool;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int fd_ssl, fd_plain;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch struct io *io_ssl_read, *io_ssl_write, *io_plain_read, *io_plain_write;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned char plainout_buf[1024];
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int plainout_size;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned char sslout_buf[1024];
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int sslout_size;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_handshake_callback_t *handshake_callback;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch void *handshake_context;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *cert_error;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch char *last_error;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int handshaked:1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int destroyed:1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int cert_received:1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int cert_broken:1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int client_proxy:1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch};
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstruct ssl_parameters {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *path;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch time_t last_refresh;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int fd;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch DH *dh_512, *dh_default;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch};
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstruct ssl_server_context {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch SSL_CTX *ctx;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch pool_t pool;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *cert;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *key;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *ca;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *cipher_list;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *protocols;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch bool verify_client_cert;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch bool prefer_server_ciphers;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch};
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic int extdata_index;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic HASH_TABLE(struct ssl_server_context *,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch struct ssl_server_context *) ssl_servers;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic SSL_CTX *ssl_client_ctx;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic unsigned int ssl_proxy_count;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic struct ssl_proxy *ssl_proxies;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic struct ssl_parameters ssl_params;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic int ssl_username_nid;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic ENGINE *ssl_engine;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void plain_read(struct ssl_proxy *proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_read(struct ssl_proxy *proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_write(struct ssl_proxy *proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_step(struct ssl_proxy *proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_proxy_destroy(struct ssl_proxy *proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_proxy_unref(struct ssl_proxy *proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic struct ssl_server_context *
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschssl_server_context_init(const struct login_settings *login_set,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const struct master_service_ssl_settings *ssl_set);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_server_context_deinit(struct ssl_server_context **_ctx);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_proxy_ctx_set_crypto_params(SSL_CTX *ssl_ctx,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const struct master_service_ssl_settings *set);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#if defined(HAVE_ECDH) && OPENSSL_VERSION_NUMBER < 0x10002000L
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic int ssl_proxy_ctx_get_pkey_ec_curve_name(const struct master_service_ssl_settings *set);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch#endif
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic unsigned int ssl_server_context_hash(const struct ssl_server_context *ctx)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int i, g, h = 0;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch /* checking for different certs is typically good enough,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch and it should be enough to check only the first few bytes. */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch for (i = 0; i < 16 && ctx->cert[i] != '\0'; i++) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch h = (h << 4) + ctx->cert[i];
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if ((g = h & 0xf0000000UL)) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch h = h ^ (g >> 24);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch h = h ^ g;
b674bd911aaab7e8b1a77c106a0b5bccb603439fStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return h;
b674bd911aaab7e8b1a77c106a0b5bccb603439fStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic int ssl_server_context_cmp(const struct ssl_server_context *ctx1,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const struct ssl_server_context *ctx2)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (strcmp(ctx1->cert, ctx2->cert) != 0)
b674bd911aaab7e8b1a77c106a0b5bccb603439fStephan Bosch return 1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (strcmp(ctx1->key, ctx2->key) != 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return 1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (null_strcmp(ctx1->ca, ctx2->ca) != 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return 1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (null_strcmp(ctx1->cipher_list, ctx2->cipher_list) != 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return 1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (null_strcmp(ctx1->protocols, ctx2->protocols) != 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return 1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return ctx1->verify_client_cert == ctx2->verify_client_cert ? 0 : 1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_params_corrupted(const char *reason)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_fatal("Corrupted SSL ssl-parameters.dat in state_dir: %s", reason);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void read_next(struct ssl_parameters *params, void *data, size_t size)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if ((ret = read_full(params->fd, data, size)) < 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_fatal("read(%s) failed: %m", params->path);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (ret == 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_params_corrupted("Truncated file");
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic bool read_dh_parameters_next(struct ssl_parameters *params)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned char *buf;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const unsigned char *cbuf;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned int len;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int bits;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch /* read bit size. 0 ends the DH parameters list. */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch read_next(params, &bits, sizeof(bits));
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (bits == 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return FALSE;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch /* read data size. */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch read_next(params, &len, sizeof(len));
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (len > 1024*100) /* should be enough? */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_params_corrupted("File too large");
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch buf = i_malloc(len);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch read_next(params, buf, len);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch cbuf = buf;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch switch (bits) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case 512:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (params->dh_512 != NULL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_params_corrupted("Duplicate 512bit parameters");
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch params->dh_512 = d2i_DHparams(NULL, &cbuf, len);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch default:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (params->dh_default != NULL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_params_corrupted("Duplicate default parameters");
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch params->dh_default = d2i_DHparams(NULL, &cbuf, len);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_free(buf);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return TRUE;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_free_parameters(struct ssl_parameters *params)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (params->dh_512 != NULL) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch DH_free(params->dh_512);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch params->dh_512 = NULL;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (params->dh_default != NULL) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch DH_free(params->dh_default);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch params->dh_default = NULL;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_refresh_parameters(struct ssl_parameters *params)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch char c;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (params->last_refresh > ioloop_time - SSL_PARAMFILE_CHECK_INTERVAL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch params->last_refresh = ioloop_time;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch params->fd = net_connect_unix(params->path);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (params->fd == -1) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_error("connect(%s) failed: %m", params->path);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch net_set_nonblock(params->fd, FALSE);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_free_parameters(params);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch while (read_dh_parameters_next(params)) ;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if ((ret = read_full(params->fd, &c, 1)) < 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_fatal("read(%s) failed: %m", params->path);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch else if (ret != 0) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch /* more data than expected */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_params_corrupted("More data than expected");
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (close(params->fd) < 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_error("close(%s) failed: %m", params->path);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch params->fd = -1;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_set_io(struct ssl_proxy *proxy, enum ssl_io_action action)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch switch (action) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_ADD_INPUT:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->io_ssl_read != NULL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->io_ssl_read = io_add(proxy->fd_ssl, IO_READ,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_step, proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_REMOVE_INPUT:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->io_ssl_read != NULL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch io_remove(&proxy->io_ssl_read);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_ADD_OUTPUT:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->io_ssl_write != NULL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->io_ssl_write = io_add(proxy->fd_ssl, IO_WRITE,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_step, proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_REMOVE_OUTPUT:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->io_ssl_write != NULL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch io_remove(&proxy->io_ssl_write);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void plain_block_input(struct ssl_proxy *proxy, bool block)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (block) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->io_plain_read != NULL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch io_remove(&proxy->io_plain_read);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch } else {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->io_plain_read == NULL) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->io_plain_read = io_add(proxy->fd_plain, IO_READ,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch plain_read, proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void plain_read(struct ssl_proxy *proxy)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssize_t ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch bool corked = FALSE;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->sslout_size == sizeof(proxy->sslout_buf)) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch /* buffer full, block input until it's written */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch plain_block_input(proxy, TRUE);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->refcount++;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch while (proxy->sslout_size < sizeof(proxy->sslout_buf) &&
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch !proxy->destroyed) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ret = net_receive(proxy->fd_plain,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->sslout_buf + proxy->sslout_size,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch sizeof(proxy->sslout_buf) -
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->sslout_size);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (ret <= 0) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (ret < 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_proxy_destroy(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch } else {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->sslout_size += ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (!corked) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (net_set_cork(proxy->fd_ssl, TRUE) == 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch corked = TRUE;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_write(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (corked)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch net_set_cork(proxy->fd_ssl, FALSE);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_proxy_unref(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void plain_write(struct ssl_proxy *proxy)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssize_t ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->refcount++;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ret = net_transmit(proxy->fd_plain, proxy->plainout_buf,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->plainout_size);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (ret < 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_proxy_destroy(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch else {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->plainout_size -= ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch memmove(proxy->plainout_buf, proxy->plainout_buf + ret,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->plainout_size);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->plainout_size > 0) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->io_plain_write == NULL) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->io_plain_write =
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch io_add(proxy->fd_plain, IO_WRITE,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch plain_write, proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch } else {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->io_plain_write != NULL)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch io_remove(&proxy->io_plain_write);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_set_io(proxy, SSL_ADD_INPUT);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (SSL_pending(proxy->ssl) > 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_read(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_proxy_unref(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic const char *ssl_err2str(unsigned long err, const char *data, int flags)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch char *buf;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch size_t err_size = 256;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch buf = t_malloc(err_size);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch buf[err_size-1] = '\0';
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ERR_error_string_n(err, buf, err_size-1);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ret = buf;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if ((flags & ERR_TXT_STRING) != 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ret = t_strdup_printf("%s: %s", buf, data);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic const char *ssl_last_error(void)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch unsigned long err;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *data;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int flags;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch err = ERR_get_error_line_data(NULL, NULL, &data, &flags);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch while (err != 0 && ERR_peek_error() != 0) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_error("SSL: Stacked error: %s",
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_err2str(err, data, flags));
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch err = ERR_get_error();
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (err == 0) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (errno != 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return strerror(errno);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return "Unknown error";
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch return ssl_err2str(err, data, flags);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_handle_error(struct ssl_proxy *proxy, int ret,
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *func_name)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch const char *errstr = NULL;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int err;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->refcount++;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_free_and_null(proxy->last_error);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch err = SSL_get_error(proxy->ssl, ret);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch switch (err) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_ERROR_WANT_READ:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_set_io(proxy, SSL_ADD_INPUT);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_ERROR_WANT_WRITE:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_set_io(proxy, SSL_ADD_OUTPUT);
b674bd911aaab7e8b1a77c106a0b5bccb603439fStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_ERROR_SYSCALL:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch /* eat up the error queue */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (ERR_peek_error() != 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch errstr = ssl_last_error();
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch else if (ret != 0)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch errstr = strerror(errno);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch else {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch /* EOF. */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch errstr = "Disconnected";
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch errstr = t_strdup_printf("%s syscall failed: %s",
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch func_name, errstr);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_ERROR_ZERO_RETURN:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch /* clean connection closing */
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_proxy_destroy(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch case SSL_ERROR_SSL:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (ERR_GET_REASON(ERR_peek_error()) == ERR_R_MALLOC_FAILURE) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch i_error("OpenSSL malloc() failed. "
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch "You may need to increase service %s { vsz_limit }",
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch login_binary->process_name);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch errstr = t_strdup_printf("%s failed: %s",
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch func_name, ssl_last_error());
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch default:
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch errstr = t_strdup_printf("%s failed: unknown failure %d (%s)",
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch func_name, err, ssl_last_error());
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch break;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (errstr != NULL) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch proxy->last_error = i_strdup(errstr);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_proxy_destroy(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch }
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ssl_proxy_unref(proxy);
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch}
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Boschstatic void ssl_handshake(struct ssl_proxy *proxy)
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch{
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch int ret;
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch if (proxy->client_proxy) {
57e3b63a75335f45cf6cf9cd89317e2e6cec249dStephan Bosch ret = SSL_connect(proxy->ssl);
if (ret != 1) {
ssl_handle_error(proxy, ret, "SSL_connect()");
return;
}
} else {
ret = SSL_accept(proxy->ssl);
if (ret != 1) {
ssl_handle_error(proxy, ret, "SSL_accept()");
return;
}
}
i_free_and_null(proxy->last_error);
proxy->handshaked = TRUE;
ssl_set_io(proxy, SSL_ADD_INPUT);
plain_block_input(proxy, FALSE);
if (proxy->handshake_callback != NULL) {
if (proxy->handshake_callback(proxy->handshake_context) < 0)
ssl_proxy_destroy(proxy);
}
}
static void ssl_read(struct ssl_proxy *proxy)
{
int ret;
while (proxy->plainout_size < sizeof(proxy->plainout_buf) &&
!proxy->destroyed) {
ret = SSL_read(proxy->ssl,
proxy->plainout_buf + proxy->plainout_size,
sizeof(proxy->plainout_buf) -
proxy->plainout_size);
if (ret <= 0) {
ssl_handle_error(proxy, ret, "SSL_read()");
break;
} else {
i_free_and_null(proxy->last_error);
proxy->plainout_size += ret;
plain_write(proxy);
}
}
}
static void ssl_write(struct ssl_proxy *proxy)
{
int ret;
ret = SSL_write(proxy->ssl, proxy->sslout_buf, proxy->sslout_size);
if (ret <= 0)
ssl_handle_error(proxy, ret, "SSL_write()");
else {
i_free_and_null(proxy->last_error);
proxy->sslout_size -= ret;
memmove(proxy->sslout_buf, proxy->sslout_buf + ret,
proxy->sslout_size);
ssl_set_io(proxy, proxy->sslout_size > 0 ?
SSL_ADD_OUTPUT : SSL_REMOVE_OUTPUT);
plain_block_input(proxy, FALSE);
}
}
static void ssl_step(struct ssl_proxy *proxy)
{
proxy->refcount++;
if (!proxy->handshaked) {
ssl_set_io(proxy, SSL_REMOVE_OUTPUT);
ssl_handshake(proxy);
}
if (proxy->handshaked) {
if (proxy->plainout_size == sizeof(proxy->plainout_buf))
ssl_set_io(proxy, SSL_REMOVE_INPUT);
else
ssl_read(proxy);
if (proxy->sslout_size == 0)
ssl_set_io(proxy, SSL_REMOVE_OUTPUT);
else {
net_set_cork(proxy->fd_ssl, TRUE);
ssl_write(proxy);
net_set_cork(proxy->fd_ssl, FALSE);
}
}
ssl_proxy_unref(proxy);
}
static int
ssl_proxy_alloc_common(SSL_CTX *ssl_ctx, int fd, const struct ip_addr *ip,
pool_t set_pool, const struct login_settings *login_set,
const struct master_service_ssl_settings *ssl_set,
struct ssl_proxy **proxy_r)
{
struct ssl_proxy *proxy;
SSL *ssl;
int sfd[2];
i_assert(fd != -1);
*proxy_r = NULL;
if (!ssl_initialized) {
i_error("SSL support not enabled in configuration");
return -1;
}
ssl_refresh_parameters(&ssl_params);
ssl = SSL_new(ssl_ctx);
if (ssl == NULL) {
i_error("SSL_new() failed: %s", ssl_last_error());
return -1;
}
if (SSL_set_fd(ssl, fd) != 1) {
i_error("SSL_set_fd() failed: %s", ssl_last_error());
SSL_free(ssl);
return -1;
}
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) < 0) {
i_error("socketpair() failed: %m");
SSL_free(ssl);
return -1;
}
net_set_nonblock(sfd[0], TRUE);
net_set_nonblock(sfd[1], TRUE);
net_set_nonblock(fd, TRUE);
proxy = i_new(struct ssl_proxy, 1);
proxy->refcount = 2;
proxy->ssl = ssl;
proxy->login_set = login_set;
proxy->ssl_set = ssl_set;
proxy->fd_ssl = fd;
proxy->fd_plain = sfd[0];
proxy->ip = *ip;
proxy->set_pool = set_pool;
pool_ref(set_pool);
SSL_set_ex_data(ssl, extdata_index, proxy);
ssl_proxy_count++;
DLLIST_PREPEND(&ssl_proxies, proxy);
*proxy_r = proxy;
return sfd[1];
}
static struct ssl_server_context *
ssl_server_context_get(const struct login_settings *login_set,
const struct master_service_ssl_settings *set)
{
struct ssl_server_context *ctx, lookup_ctx;
memset(&lookup_ctx, 0, sizeof(lookup_ctx));
lookup_ctx.cert = set->ssl_cert;
lookup_ctx.key = set->ssl_key;
lookup_ctx.ca = set->ssl_ca;
lookup_ctx.cipher_list = set->ssl_cipher_list;
lookup_ctx.protocols = set->ssl_protocols;
lookup_ctx.verify_client_cert = set->ssl_verify_client_cert ||
login_set->auth_ssl_require_client_cert ||
login_set->auth_ssl_username_from_cert;
lookup_ctx.prefer_server_ciphers = set->ssl_prefer_server_ciphers;
ctx = hash_table_lookup(ssl_servers, &lookup_ctx);
if (ctx == NULL)
ctx = ssl_server_context_init(login_set, set);
return ctx;
}
int ssl_proxy_alloc(int fd, const struct ip_addr *ip, pool_t set_pool,
const struct login_settings *login_set,
const struct master_service_ssl_settings *ssl_set,
struct ssl_proxy **proxy_r)
{
struct ssl_server_context *ctx;
ctx = ssl_server_context_get(login_set, ssl_set);
return ssl_proxy_alloc_common(ctx->ctx, fd, ip,
set_pool, login_set, ssl_set, proxy_r);
}
int ssl_proxy_client_alloc(int fd, struct ip_addr *ip, pool_t set_pool,
const struct login_settings *login_set,
const struct master_service_ssl_settings *ssl_set,
ssl_handshake_callback_t *callback, void *context,
struct ssl_proxy **proxy_r)
{
int ret;
ret = ssl_proxy_alloc_common(ssl_client_ctx, fd, ip,
set_pool, login_set, ssl_set, proxy_r);
if (ret < 0)
return -1;
(*proxy_r)->handshake_callback = callback;
(*proxy_r)->handshake_context = context;
(*proxy_r)->client_proxy = TRUE;
return ret;
}
void ssl_proxy_start(struct ssl_proxy *proxy)
{
ssl_step(proxy);
}
void ssl_proxy_set_client(struct ssl_proxy *proxy, struct client *client)
{
i_assert(proxy->client == NULL);
client_ref(client);
proxy->client = client;
}
bool ssl_proxy_has_valid_client_cert(const struct ssl_proxy *proxy)
{
return proxy->cert_received && !proxy->cert_broken;
}
bool ssl_proxy_has_broken_client_cert(struct ssl_proxy *proxy)
{
return proxy->cert_received && proxy->cert_broken;
}
int ssl_proxy_cert_match_name(struct ssl_proxy *proxy, const char *verify_name)
{
return openssl_cert_match_name(proxy->ssl, verify_name);
}
const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy)
{
X509 *x509;
char *name;
int len;
if (!ssl_proxy_has_valid_client_cert(proxy))
return NULL;
x509 = SSL_get_peer_certificate(proxy->ssl);
if (x509 == NULL)
return NULL; /* we should have had it.. */
len = X509_NAME_get_text_by_NID(X509_get_subject_name(x509),
ssl_username_nid, NULL, 0);
if (len < 0)
name = "";
else {
name = t_malloc(len + 1);
if (X509_NAME_get_text_by_NID(X509_get_subject_name(x509),
ssl_username_nid, name, len + 1) < 0)
name = "";
else if (strlen(name) != (size_t)len) {
/* NUL characters in name. Someone's trying to fake
being another user? Don't allow it. */
name = "";
}
}
X509_free(x509);
return *name == '\0' ? NULL : name;
}
bool ssl_proxy_is_handshaked(const struct ssl_proxy *proxy)
{
return proxy->handshaked;
}
const char *ssl_proxy_get_last_error(const struct ssl_proxy *proxy)
{
return proxy->last_error;
}
const char *ssl_proxy_get_security_string(struct ssl_proxy *proxy)
{
const SSL_CIPHER *cipher;
int bits, alg_bits;
const char *comp_str;
if (!proxy->handshaked)
return "";
cipher = SSL_get_current_cipher(proxy->ssl);
bits = SSL_CIPHER_get_bits(cipher, &alg_bits);
comp_str = ssl_proxy_get_compression(proxy);
comp_str = comp_str == NULL ? "" : t_strconcat(" ", comp_str, NULL);
return t_strdup_printf("%s with cipher %s (%d/%d bits)%s",
SSL_get_version(proxy->ssl),
SSL_CIPHER_get_name(cipher),
bits, alg_bits, comp_str);
}
const char *ssl_proxy_get_compression(struct ssl_proxy *proxy ATTR_UNUSED)
{
#ifdef HAVE_SSL_COMPRESSION
const COMP_METHOD *comp;
comp = SSL_get_current_compression(proxy->ssl);
return comp == NULL ? NULL : SSL_COMP_get_name(comp);
#else
return NULL;
#endif
}
const char *ssl_proxy_get_cert_error(struct ssl_proxy *proxy)
{
return proxy->cert_error != NULL ? proxy->cert_error :
"(Unknown error)";
}
void ssl_proxy_free(struct ssl_proxy **_proxy)
{
struct ssl_proxy *proxy = *_proxy;
*_proxy = NULL;
ssl_proxy_unref(proxy);
}
static void ssl_proxy_unref(struct ssl_proxy *proxy)
{
if (--proxy->refcount > 0)
return;
i_assert(proxy->refcount == 0);
SSL_free(proxy->ssl);
pool_unref(&proxy->set_pool);
i_free(proxy->last_error);
i_free(proxy);
}
static void ssl_proxy_destroy(struct ssl_proxy *proxy)
{
if (proxy->destroyed)
return;
proxy->destroyed = TRUE;
ssl_proxy_count--;
DLLIST_REMOVE(&ssl_proxies, proxy);
if (proxy->io_ssl_read != NULL)
io_remove(&proxy->io_ssl_read);
if (proxy->io_ssl_write != NULL)
io_remove(&proxy->io_ssl_write);
if (proxy->io_plain_read != NULL)
io_remove(&proxy->io_plain_read);
if (proxy->io_plain_write != NULL)
io_remove(&proxy->io_plain_write);
(void)SSL_shutdown(proxy->ssl);
net_disconnect(proxy->fd_ssl);
net_disconnect(proxy->fd_plain);
if (proxy->client != NULL)
client_unref(&proxy->client);
ssl_proxy_unref(proxy);
}
static RSA *ssl_gen_rsa_key(SSL *ssl ATTR_UNUSED,
int is_export ATTR_UNUSED, int keylength)
{
return RSA_generate_key(keylength, RSA_F4, NULL, NULL);
}
static DH *ssl_tmp_dh_callback(SSL *ssl ATTR_UNUSED,
int is_export, int keylength)
{
if (is_export && keylength == 512 && ssl_params.dh_512 != NULL)
return ssl_params.dh_512;
return ssl_params.dh_default;
}
static void ssl_info_callback(const SSL *ssl, int where, int ret)
{
struct ssl_proxy *proxy;
proxy = SSL_get_ex_data(ssl, extdata_index);
if (!proxy->ssl_set->verbose_ssl)
return;
if ((where & SSL_CB_ALERT) != 0) {
switch (ret & 0xff) {
case SSL_AD_CLOSE_NOTIFY:
i_debug("SSL alert: %s [%s]",
SSL_alert_desc_string_long(ret),
net_ip2addr(&proxy->ip));
break;
default:
i_warning("SSL alert: where=0x%x, ret=%d: %s %s [%s]",
where, ret, SSL_alert_type_string_long(ret),
SSL_alert_desc_string_long(ret),
net_ip2addr(&proxy->ip));
break;
}
} else if (ret == 0) {
i_warning("SSL failed: where=0x%x: %s [%s]",
where, SSL_state_string_long(ssl),
net_ip2addr(&proxy->ip));
} else {
i_debug("SSL: where=0x%x, ret=%d: %s [%s]",
where, ret, SSL_state_string_long(ssl),
net_ip2addr(&proxy->ip));
}
}
static int ssl_verify_client_cert(int preverify_ok, X509_STORE_CTX *ctx)
{
SSL *ssl;
struct ssl_proxy *proxy;
char buf[1024];
X509_NAME *subject;
ssl = X509_STORE_CTX_get_ex_data(ctx,
SSL_get_ex_data_X509_STORE_CTX_idx());
proxy = SSL_get_ex_data(ssl, extdata_index);
proxy->cert_received = TRUE;
if (proxy->client_proxy && !proxy->login_set->ssl_require_crl &&
(ctx->error == X509_V_ERR_UNABLE_TO_GET_CRL ||
ctx->error == X509_V_ERR_CRL_HAS_EXPIRED)) {
/* no CRL given with the CA list. don't worry about it. */
preverify_ok = 1;
}
if (!preverify_ok)
proxy->cert_broken = TRUE;
subject = X509_get_subject_name(ctx->current_cert);
(void)X509_NAME_oneline(subject, buf, sizeof(buf));
buf[sizeof(buf)-1] = '\0'; /* just in case.. */
if (proxy->cert_error == NULL) {
proxy->cert_error = p_strdup_printf(proxy->client->pool, "%s: %s",
X509_verify_cert_error_string(ctx->error), buf);
}
if (proxy->ssl_set->verbose_ssl ||
(proxy->login_set->auth_verbose && !preverify_ok)) {
if (preverify_ok)
i_info("Valid certificate: %s", buf);
else {
i_info("Invalid certificate: %s: %s",
X509_verify_cert_error_string(ctx->error), buf);
}
}
/* Return success anyway, because if ssl_require_client_cert=no we
could still allow authentication. */
return 1;
}
static int
pem_password_callback(char *buf, int size, int rwflag ATTR_UNUSED,
void *userdata)
{
if (userdata == NULL) {
i_error("SSL private key file is password protected, "
"but password isn't given");
return 0;
}
if (i_strocpy(buf, userdata, size) < 0)
return 0;
return strlen(buf);
}
unsigned int ssl_proxy_get_count(void)
{
return ssl_proxy_count;
}
static bool is_pem_key(const char *cert)
{
return strstr(cert, "PRIVATE KEY---") != NULL;
}
static void load_ca(X509_STORE *store, const char *ca,
STACK_OF(X509_NAME) **xnames_r)
{
/* mostly just copy&pasted from X509_load_cert_crl_file() */
STACK_OF(X509_INFO) *inf;
X509_INFO *itmp;
X509_NAME *xname;
BIO *bio;
int i;
bio = BIO_new_mem_buf(t_strdup_noconst(ca), strlen(ca));
if (bio == NULL)
i_fatal("BIO_new_mem_buf() failed");
inf = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
if (inf == NULL)
i_fatal("Couldn't parse ssl_ca: %s", ssl_last_error());
BIO_free(bio);
if (xnames_r != NULL) {
*xnames_r = sk_X509_NAME_new_null();
if (*xnames_r == NULL)
i_fatal_status(FATAL_OUTOFMEM, "sk_X509_NAME_new_null() failed");
}
for(i = 0; i < sk_X509_INFO_num(inf); i++) {
itmp = sk_X509_INFO_value(inf, i);
if(itmp->x509) {
X509_STORE_add_cert(store, itmp->x509);
xname = X509_get_subject_name(itmp->x509);
if (xname != NULL && xnames_r != NULL) {
xname = X509_NAME_dup(xname);
if (xname == NULL)
i_fatal_status(FATAL_OUTOFMEM, "X509_NAME_dup() failed");
sk_X509_NAME_push(*xnames_r, xname);
}
}
if(itmp->crl)
X509_STORE_add_crl(store, itmp->crl);
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
}
static STACK_OF(X509_NAME) *
ssl_proxy_ctx_init(SSL_CTX *ssl_ctx, const struct master_service_ssl_settings *set,
bool load_xnames)
{
X509_STORE *store;
STACK_OF(X509_NAME) *xnames = NULL;
/* enable all SSL workarounds, except empty fragments as it
makes SSL more vulnerable against attacks */
SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL &
~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
if (*set->ssl_ca != '\0') {
/* set trusted CA certs */
store = SSL_CTX_get_cert_store(ssl_ctx);
load_ca(store, set->ssl_ca, load_xnames ? &xnames : NULL);
}
ssl_proxy_ctx_set_crypto_params(ssl_ctx, set);
SSL_CTX_set_info_callback(ssl_ctx, ssl_info_callback);
return xnames;
}
static void
ssl_proxy_ctx_set_crypto_params(SSL_CTX *ssl_ctx,
const struct master_service_ssl_settings *set ATTR_UNUSED)
{
#if defined(HAVE_ECDH) && OPENSSL_VERSION_NUMBER < 0x10002000L
EC_KEY *ecdh;
int nid;
const char *curve_name;
#endif
if (SSL_CTX_need_tmp_RSA(ssl_ctx))
SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback);
#ifdef HAVE_ECDH
/* In the non-recommended situation where ECDH cipher suites are being
used instead of ECDHE, do not reuse the same ECDH key pair for
different sessions. This option improves forward secrecy. */
SSL_CTX_set_options(ssl_ctx, SSL_OP_SINGLE_ECDH_USE);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
/* OpenSSL >= 1.0.2 automatically handles ECDH temporary key parameter
selection. */
SSL_CTX_set_ecdh_auto(ssl_ctx, 1);
#else
/* For OpenSSL < 1.0.2, ECDH temporary key parameter selection must be
performed manually. Attempt to select the same curve as that used
in the server's private EC key file. Otherwise fall back to the
NIST P-384 (secp384r1) curve to be compliant with RFC 6460 when
AES-256 TLS cipher suites are in use. This fall back option does
however make Dovecot non-compliant with RFC 6460 which requires
curve NIST P-256 (prime256v1) be used when AES-128 TLS cipher
suites are in use. At least the non-compliance is in the form of
providing too much security rather than too little. */
nid = ssl_proxy_ctx_get_pkey_ec_curve_name(set);
ecdh = EC_KEY_new_by_curve_name(nid);
if (ecdh == NULL) {
/* Fall back option */
nid = NID_secp384r1;
ecdh = EC_KEY_new_by_curve_name(nid);
}
if ((curve_name = OBJ_nid2sn(nid)) != NULL && set->verbose_ssl)
i_debug("SSL: elliptic curve %s will be used for ECDH and"
" ECDHE key exchanges", curve_name);
if (ecdh != NULL) {
SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
EC_KEY_free(ecdh);
}
#endif
#endif
}
static void
ssl_proxy_ctx_verify_client(SSL_CTX *ssl_ctx, STACK_OF(X509_NAME) *ca_names)
{
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
X509_STORE *store;
store = SSL_CTX_get_cert_store(ssl_ctx);
X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
X509_V_FLAG_CRL_CHECK_ALL);
#endif
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE,
ssl_verify_client_cert);
/* set list of CA names that are sent to client */
SSL_CTX_set_client_CA_list(ssl_ctx, ca_names);
}
static const char *ssl_proxy_get_use_certificate_error(const char *cert)
{
unsigned long err;
err = ERR_peek_error();
if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
ERR_GET_REASON(err) != PEM_R_NO_START_LINE)
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)";
} else if (strchr(cert, '\n') == NULL) {
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 const char *ssl_key_load_error(void)
{
unsigned long err = ERR_peek_error();
if (ERR_GET_LIB(err) == ERR_LIB_X509 &&
ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH)
return "Key is for a different cert than ssl_cert";
else
return ssl_last_error();
}
static EVP_PKEY * ATTR_NULL(2)
ssl_proxy_load_key(const char *key, const char *password)
{
EVP_PKEY *pkey;
BIO *bio;
char *dup_password;
bio = BIO_new_mem_buf(t_strdup_noconst(key), strlen(key));
if (bio == NULL)
i_fatal("BIO_new_mem_buf() failed");
dup_password = t_strdup_noconst(password);
pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback,
dup_password);
if (pkey == NULL) {
i_fatal("Couldn't parse private ssl_key: %s",
ssl_key_load_error());
}
BIO_free(bio);
return pkey;
}
static void
ssl_proxy_ctx_use_key(SSL_CTX *ctx,
const struct master_service_ssl_settings *set)
{
EVP_PKEY *pkey;
const char *password;
password = *set->ssl_key_password != '\0' ? set->ssl_key_password :
getenv(MASTER_SSL_KEY_PASSWORD_ENV);
pkey = ssl_proxy_load_key(set->ssl_key, password);
if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1)
i_fatal("Can't load private ssl_key: %s", ssl_key_load_error());
EVP_PKEY_free(pkey);
}
#if defined(HAVE_ECDH) && OPENSSL_VERSION_NUMBER < 0x10002000L
static int
ssl_proxy_ctx_get_pkey_ec_curve_name(const struct master_service_ssl_settings *set)
{
int nid = 0;
EVP_PKEY *pkey;
const char *password;
EC_KEY *eckey;
const EC_GROUP *ecgrp;
password = *set->ssl_key_password != '\0' ? set->ssl_key_password :
getenv(MASTER_SSL_KEY_PASSWORD_ENV);
pkey = ssl_proxy_load_key(set->ssl_key, password);
if (pkey != NULL &&
(eckey = EVP_PKEY_get1_EC_KEY(pkey)) != NULL &&
(ecgrp = EC_KEY_get0_group(eckey)) != NULL)
nid = EC_GROUP_get_curve_name(ecgrp);
EVP_PKEY_free(pkey);
return nid;
}
#endif
static int
ssl_proxy_ctx_use_certificate_chain(SSL_CTX *ctx, const char *cert)
{
/* mostly just copy&pasted from SSL_CTX_use_certificate_chain_file() */
BIO *in;
X509 *x;
int ret = 0;
in = BIO_new_mem_buf(t_strdup_noconst(cert), strlen(cert));
if (in == NULL)
i_fatal("BIO_new_mem_buf() failed");
x = PEM_read_bio_X509(in, NULL, NULL, NULL);
if (x == NULL)
goto end;
ret = SSL_CTX_use_certificate(ctx, x);
#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.
*/
X509 *ca;
int r;
unsigned long err;
while ((ca = PEM_read_bio_X509(in,NULL,NULL,NULL)) != NULL) {
r = SSL_CTX_add_extra_chain_cert(ctx, ca);
if (!r) {
X509_free(ca);
ret = 0;
goto end;
}
}
/* When the while loop ends, it's usually just EOF. */
err = ERR_peek_last_error();
if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)
ERR_clear_error();
else
ret = 0; /* some real error */
}
end:
if (x != NULL) X509_free(x);
BIO_free(in);
return ret;
}
#ifdef HAVE_SSL_GET_SERVERNAME
static void ssl_servername_callback(SSL *ssl, int *al ATTR_UNUSED,
void *context ATTR_UNUSED)
{
struct ssl_server_context *ctx;
struct ssl_proxy *proxy;
struct client *client;
const char *host;
void **other_sets;
proxy = SSL_get_ex_data(ssl, extdata_index);
host = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
client = proxy->client;
if (!client->ssl_servername_settings_read) {
client->ssl_servername_settings_read = TRUE;
client->set = login_settings_read(client->pool,
&client->local_ip,
&client->ip, host,
&client->ssl_set,
&other_sets);
}
ctx = ssl_server_context_get(client->set, client->ssl_set);
SSL_set_SSL_CTX(ssl, ctx->ctx);
}
#endif
static struct ssl_server_context *
ssl_server_context_init(const struct login_settings *login_set,
const struct master_service_ssl_settings *ssl_set)
{
struct ssl_server_context *ctx;
SSL_CTX *ssl_ctx;
pool_t pool;
STACK_OF(X509_NAME) *xnames;
pool = pool_alloconly_create("ssl server context", 4096);
ctx = p_new(pool, struct ssl_server_context, 1);
ctx->pool = pool;
ctx->cert = p_strdup(pool, ssl_set->ssl_cert);
ctx->key = p_strdup(pool, ssl_set->ssl_key);
ctx->ca = p_strdup(pool, ssl_set->ssl_ca);
ctx->cipher_list = p_strdup(pool, ssl_set->ssl_cipher_list);
ctx->protocols = p_strdup(pool, ssl_set->ssl_protocols);
ctx->verify_client_cert = ssl_set->ssl_verify_client_cert ||
login_set->auth_ssl_require_client_cert ||
login_set->auth_ssl_username_from_cert;
ctx->prefer_server_ciphers = ssl_set->ssl_prefer_server_ciphers;
ctx->ctx = ssl_ctx = SSL_CTX_new(SSLv23_server_method());
if (ssl_ctx == NULL)
i_fatal("SSL_CTX_new() failed");
xnames = ssl_proxy_ctx_init(ssl_ctx, ssl_set, ctx->verify_client_cert);
if (SSL_CTX_set_cipher_list(ssl_ctx, ctx->cipher_list) != 1) {
i_fatal("Can't set cipher list to '%s': %s",
ctx->cipher_list, ssl_last_error());
}
if (ctx->prefer_server_ciphers)
SSL_CTX_set_options(ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
SSL_CTX_set_options(ssl_ctx, openssl_get_protocol_options(ctx->protocols));
if (ssl_proxy_ctx_use_certificate_chain(ctx->ctx, ctx->cert) != 1) {
i_fatal("Can't load ssl_cert: %s",
ssl_proxy_get_use_certificate_error(ctx->cert));
}
#ifdef HAVE_SSL_GET_SERVERNAME
if (SSL_CTX_set_tlsext_servername_callback(ctx->ctx,
ssl_servername_callback) != 1) {
if (ssl_set->verbose_ssl)
i_debug("OpenSSL library doesn't support SNI");
}
#endif
ssl_proxy_ctx_use_key(ctx->ctx, ssl_set);
if (ctx->verify_client_cert)
ssl_proxy_ctx_verify_client(ctx->ctx, xnames);
hash_table_insert(ssl_servers, ctx, ctx);
return ctx;
}
static void ssl_server_context_deinit(struct ssl_server_context **_ctx)
{
struct ssl_server_context *ctx = *_ctx;
SSL_CTX_free(ctx->ctx);
pool_unref(&ctx->pool);
}
static void
ssl_proxy_client_ctx_set_client_cert(SSL_CTX *ctx,
const struct login_settings *set)
{
EVP_PKEY *pkey;
if (*set->ssl_client_cert == '\0')
return;
if (ssl_proxy_ctx_use_certificate_chain(ctx, set->ssl_client_cert) != 1) {
i_fatal("Can't load ssl_client_cert: %s",
ssl_proxy_get_use_certificate_error(set->ssl_client_cert));
}
pkey = ssl_proxy_load_key(set->ssl_client_key, NULL);
if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) {
i_fatal("Can't load private ssl_client_key: %s",
ssl_key_load_error());
}
EVP_PKEY_free(pkey);
}
static void
ssl_proxy_init_client(const struct login_settings *login_set,
const struct master_service_ssl_settings *ssl_set)
{
STACK_OF(X509_NAME) *xnames;
if ((ssl_client_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL)
i_fatal("SSL_CTX_new() failed");
xnames = ssl_proxy_ctx_init(ssl_client_ctx, ssl_set, TRUE);
ssl_proxy_ctx_verify_client(ssl_client_ctx, xnames);
ssl_proxy_client_ctx_set_client_cert(ssl_client_ctx, login_set);
}
void ssl_proxy_init(void)
{
const struct login_settings *login_set = global_login_settings;
const struct master_service_ssl_settings *ssl_set = global_ssl_settings;
static char dovecot[] = "dovecot";
unsigned char buf;
if (strcmp(ssl_set->ssl, "no") == 0)
return;
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
if (*ssl_set->ssl_crypto_device != '\0') {
ENGINE_load_builtin_engines();
ssl_engine = ENGINE_by_id(ssl_set->ssl_crypto_device);
if (ssl_engine == NULL) {
i_fatal("Unknown ssl_crypto_device: %s",
ssl_set->ssl_crypto_device);
}
ENGINE_init(ssl_engine);
ENGINE_set_default_RSA(ssl_engine);
ENGINE_set_default_DSA(ssl_engine);
ENGINE_set_default_ciphers(ssl_engine);
}
extdata_index = SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL);
hash_table_create(&ssl_servers, default_pool, 0,
ssl_server_context_hash, ssl_server_context_cmp);
(void)ssl_server_context_init(login_set, ssl_set);
ssl_proxy_init_client(login_set, ssl_set);
ssl_username_nid = OBJ_txt2nid(ssl_set->ssl_cert_username_field);
if (ssl_username_nid == NID_undef) {
i_fatal("Invalid ssl_cert_username_field: %s",
ssl_set->ssl_cert_username_field);
}
/* PRNG initialization might want to use /dev/urandom, make sure it
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. */
(void)RAND_bytes(&buf, 1);
memset(&ssl_params, 0, sizeof(ssl_params));
ssl_params.path = SSL_PARAMETERS_PATH;
ssl_proxy_count = 0;
ssl_proxies = NULL;
ssl_initialized = TRUE;
}
void ssl_proxy_deinit(void)
{
struct hash_iterate_context *iter;
struct ssl_server_context *ctx;
if (!ssl_initialized)
return;
while (ssl_proxies != NULL)
ssl_proxy_destroy(ssl_proxies);
iter = hash_table_iterate_init(ssl_servers);
while (hash_table_iterate(iter, ssl_servers, &ctx, &ctx))
ssl_server_context_deinit(&ctx);
hash_table_iterate_deinit(&iter);
hash_table_destroy(&ssl_servers);
ssl_free_parameters(&ssl_params);
SSL_CTX_free(ssl_client_ctx);
if (ssl_engine != NULL) {
ENGINE_finish(ssl_engine);
ENGINE_cleanup();
}
EVP_cleanup();
ERR_free_strings();
}
#endif