ssl-proxy-openssl.c revision efe78d3ba24fc866af1c79b9223dc0809ba26cad
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (c) 2002-2016 Dovecot authors, see the included COPYING file */
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "login-common.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "array.h"
70ead6466f9baa8294e71fc2fba0a4f54f488b5eTimo Sirainen#include "ioloop.h"
bdd36cfdba3ff66d25570a9ff568d69e1eb543cfTimo Sirainen#include "net.h"
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen#include "ostream.h"
57f5683fd9dc9bc79816c418bb30fdbc33b68a8cTimo Sirainen#include "read-full.h"
306b3f41b05da642d87e7ca7a1496efce9f5902fTimo Sirainen#include "safe-memset.h"
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen#include "hash.h"
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen#include "llist.h"
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include "master-interface.h"
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include "master-service-ssl-settings.h"
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen#include "client-common.h"
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen#include "ssl-proxy.h"
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen
9fd2181788a61500641c66aec0f8c746b19bf830Timo Sirainen#include <fcntl.h>
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen#include <unistd.h>
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include <sys/stat.h>
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#ifdef HAVE_OPENSSL
70ead6466f9baa8294e71fc2fba0a4f54f488b5eTimo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include "iostream-openssl.h"
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include <openssl/crypto.h>
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen#include <openssl/engine.h>
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen#include <openssl/x509.h>
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include <openssl/pem.h>
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen#include <openssl/ssl.h>
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include <openssl/err.h>
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#include <openssl/rand.h>
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen
9fd2181788a61500641c66aec0f8c746b19bf830Timo Sirainen#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x10000000L
9fd2181788a61500641c66aec0f8c746b19bf830Timo Sirainen# define HAVE_ECDH
9fd2181788a61500641c66aec0f8c746b19bf830Timo Sirainen#endif
9fd2181788a61500641c66aec0f8c746b19bf830Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME /* FIXME: this may be unnecessary.. */
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen# undef HAVE_SSL_GET_SERVERNAME
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen#endif
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainenenum ssl_io_action {
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen SSL_ADD_INPUT,
2fb9ae42f9e36388ec6db24188b9108434043fd0Timo Sirainen SSL_REMOVE_INPUT,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen SSL_ADD_OUTPUT,
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen SSL_REMOVE_OUTPUT
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen};
a8d47e2427558d5011dfc75694b704760c1ef8baTimo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainenstruct ssl_proxy {
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen int refcount;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen struct ssl_proxy *prev, *next;
4c892b0d94c5b1d6853dbe8e0b38059ea5b08ecaTimo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen SSL *ssl;
abc79eec93e58e0152cd1d483f37be66c26811b9Timo Sirainen struct client *client;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen struct ip_addr ip;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen const struct login_settings *login_set;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen const struct master_service_ssl_settings *ssl_set;
4c892b0d94c5b1d6853dbe8e0b38059ea5b08ecaTimo Sirainen pool_t set_pool;
2fb9ae42f9e36388ec6db24188b9108434043fd0Timo Sirainen
2fb9ae42f9e36388ec6db24188b9108434043fd0Timo Sirainen int fd_ssl, fd_plain;
2fb9ae42f9e36388ec6db24188b9108434043fd0Timo Sirainen struct io *io_ssl_read, *io_ssl_write, *io_plain_read, *io_plain_write;
013a8a91c83c6ea24bc75322b81235f19e26fa8fTimo Sirainen
d9076f5939edf5d20a261494b1a861dcbb0d32e2Timo Sirainen unsigned char plainout_buf[1024];
a8d47e2427558d5011dfc75694b704760c1ef8baTimo Sirainen unsigned int plainout_size;
a8d47e2427558d5011dfc75694b704760c1ef8baTimo Sirainen
a8d47e2427558d5011dfc75694b704760c1ef8baTimo Sirainen unsigned char sslout_buf[1024];
a8d47e2427558d5011dfc75694b704760c1ef8baTimo Sirainen unsigned int sslout_size;
a8d47e2427558d5011dfc75694b704760c1ef8baTimo Sirainen
a8d47e2427558d5011dfc75694b704760c1ef8baTimo Sirainen ssl_handshake_callback_t *handshake_callback;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen void *handshake_context;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen const char *cert_error;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen char *last_error;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen bool handshaked:1;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool destroyed:1;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool cert_received:1;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool cert_broken:1;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool client_proxy:1;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool flushing:1;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool failed:1;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen};
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainenstruct ssl_parameters {
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen DH *dh_default;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen};
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainenstruct ssl_server_context {
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen SSL_CTX *ctx;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen pool_t pool;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen const char *cert;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen const char *key;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen const char *ca;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen const char *dh;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen const char *cipher_list;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen const char *protocols;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool verify_client_cert;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool prefer_server_ciphers;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool compression;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen bool tickets;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen};
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainenstatic int extdata_index;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainenstatic HASH_TABLE(struct ssl_server_context *,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen struct ssl_server_context *) ssl_servers;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic SSL_CTX *ssl_client_ctx;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainenstatic unsigned int ssl_proxy_count;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic struct ssl_proxy *ssl_proxies;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainenstatic struct ssl_parameters ssl_params;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic int ssl_username_nid;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainenstatic ENGINE *ssl_engine;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainenstatic void plain_read(struct ssl_proxy *proxy);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainenstatic void ssl_read(struct ssl_proxy *proxy);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainenstatic void ssl_write(struct ssl_proxy *proxy);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainenstatic void ssl_step(struct ssl_proxy *proxy);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainenstatic void ssl_proxy_unref(struct ssl_proxy *proxy);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainenstatic struct ssl_server_context *
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenssl_server_context_init(const struct login_settings *login_set,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen const struct master_service_ssl_settings *ssl_set);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic void ssl_server_context_deinit(struct ssl_server_context **_ctx);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic void ssl_proxy_ctx_set_crypto_params(SSL_CTX *ssl_ctx,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen const struct master_service_ssl_settings *set);
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen#if defined(HAVE_ECDH) && !defined(SSL_CTRL_SET_ECDH_AUTO)
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainenstatic int ssl_proxy_ctx_get_pkey_ec_curve_name(const struct master_service_ssl_settings *set);
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen#endif
2fb9ae42f9e36388ec6db24188b9108434043fd0Timo Sirainen
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainenstatic void ssl_proxy_destroy_failed(struct ssl_proxy *proxy)
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen{
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen proxy->failed = TRUE;
5666a3d6a7ea89362b8d9e8b39b15424cd9d6388Timo Sirainen ssl_proxy_destroy(proxy);
a26b7e87b4157cfa800f9bcd8c4c044462d21268Timo Sirainen}
a26b7e87b4157cfa800f9bcd8c4c044462d21268Timo Sirainen
a26b7e87b4157cfa800f9bcd8c4c044462d21268Timo Sirainenstatic unsigned int ssl_server_context_hash(const struct ssl_server_context *ctx)
a26b7e87b4157cfa800f9bcd8c4c044462d21268Timo Sirainen{
a26b7e87b4157cfa800f9bcd8c4c044462d21268Timo Sirainen unsigned int i, g, h = 0;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen /* checking for different certs is typically good enough,
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen and it should be enough to check only the first few bytes. */
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen for (i = 0; i < 16 && ctx->cert[i] != '\0'; i++) {
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen h = (h << 4) + ctx->cert[i];
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen if ((g = h & 0xf0000000UL) != 0) {
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen h = h ^ (g >> 24);
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen h = h ^ g;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen }
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen }
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen return h;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen}
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainenstatic int ssl_server_context_cmp(const struct ssl_server_context *ctx1,
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen const struct ssl_server_context *ctx2)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen{
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen if (strcmp(ctx1->cert, ctx2->cert) != 0)
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen return 1;
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen if (strcmp(ctx1->key, ctx2->key) != 0)
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen return 1;
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen if (null_strcmp(ctx1->ca, ctx2->ca) != 0)
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen return 1;
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen if (null_strcmp(ctx1->cipher_list, ctx2->cipher_list) != 0)
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen return 1;
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen if (null_strcmp(ctx1->protocols, ctx2->protocols) != 0)
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen return 1;
e51cfb5506de764499cb5b81a098b23cf46f90f1Timo Sirainen
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen return ctx1->verify_client_cert == ctx2->verify_client_cert ? 0 : 1;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen}
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainenstatic void ssl_free_parameters(struct ssl_parameters *params)
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen{
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen if (params->dh_default != NULL) {
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen DH_free(params->dh_default);
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen params->dh_default = NULL;
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen }
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen}
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainenstatic void ssl_set_io(struct ssl_proxy *proxy, enum ssl_io_action action)
a443e5aaf632257bfd1e7aa9b3c42c09512bbe43Timo Sirainen{
13b063ba3ea51256fd97d7fa883f14cb08842b0dTimo Sirainen switch (action) {
13b063ba3ea51256fd97d7fa883f14cb08842b0dTimo Sirainen case SSL_ADD_INPUT:
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (proxy->io_ssl_read != NULL)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen break;
013a8a91c83c6ea24bc75322b81235f19e26fa8fTimo Sirainen proxy->io_ssl_read = io_add(proxy->fd_ssl, IO_READ,
013a8a91c83c6ea24bc75322b81235f19e26fa8fTimo Sirainen ssl_step, proxy);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen break;
d1fff80640050631b06bfab904a34b2ad24601e8Timo Sirainen case SSL_REMOVE_INPUT:
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (proxy->io_ssl_read != NULL)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen io_remove(&proxy->io_ssl_read);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen break;
d1fff80640050631b06bfab904a34b2ad24601e8Timo Sirainen case SSL_ADD_OUTPUT:
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (proxy->io_ssl_write != NULL)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen break;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen proxy->io_ssl_write = io_add(proxy->fd_ssl, IO_WRITE,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen ssl_step, proxy);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen break;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen case SSL_REMOVE_OUTPUT:
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (proxy->io_ssl_write != NULL)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen io_remove(&proxy->io_ssl_write);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen break;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen }
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen}
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenstatic void plain_block_input(struct ssl_proxy *proxy, bool block)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen{
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (block) {
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (proxy->io_plain_read != NULL)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen io_remove(&proxy->io_plain_read);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen } else {
a2150da2dc906c26a26219cbefbe28a119aafee2Timo Sirainen if (proxy->io_plain_read == NULL) {
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen proxy->io_plain_read = io_add(proxy->fd_plain, IO_READ,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen plain_read, proxy);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen }
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen }
f9142439f2b5e86065af7420e80fe52835227dc8Timo Sirainen}
f9142439f2b5e86065af7420e80fe52835227dc8Timo Sirainen
f9142439f2b5e86065af7420e80fe52835227dc8Timo Sirainenstatic void plain_read(struct ssl_proxy *proxy)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen{
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen ssize_t ret;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen bool corked = FALSE;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (proxy->sslout_size == sizeof(proxy->sslout_buf)) {
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen /* buffer full, block input until it's written */
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen plain_block_input(proxy, TRUE);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen return;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen }
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen proxy->refcount++;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen while (proxy->sslout_size < sizeof(proxy->sslout_buf) &&
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen !proxy->destroyed) {
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen ret = net_receive(proxy->fd_plain,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen proxy->sslout_buf + proxy->sslout_size,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen sizeof(proxy->sslout_buf) -
88b0427d90f1d3c2c5fb3171e53a505c46e2c39dTimo Sirainen proxy->sslout_size);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (ret <= 0) {
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen if (ret < 0)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen ssl_proxy_destroy(proxy);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen break;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen } else {
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen proxy->sslout_size += ret;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen if (!corked) {
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen if (net_set_cork(proxy->fd_ssl, TRUE) == 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen corked = TRUE;
d80f37f025593d959bdfa9c378915e4322f4f504Timo Sirainen }
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen ssl_write(proxy);
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen }
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen }
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen if (corked)
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen (void)net_set_cork(proxy->fd_ssl, FALSE);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen ssl_proxy_unref(proxy);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen}
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainenstatic void plain_write(struct ssl_proxy *proxy)
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen{
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen ssize_t ret;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen proxy->refcount++;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen ret = net_transmit(proxy->fd_plain, proxy->plainout_buf,
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen proxy->plainout_size);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (ret < 0)
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen ssl_proxy_destroy(proxy);
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen else {
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen proxy->plainout_size -= ret;
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen memmove(proxy->plainout_buf, proxy->plainout_buf + ret,
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen proxy->plainout_size);
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen if (proxy->plainout_size > 0) {
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen if (proxy->io_plain_write == NULL) {
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen proxy->io_plain_write =
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen io_add(proxy->fd_plain, IO_WRITE,
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen plain_write, proxy);
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen }
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen } else {
3398d5e2b883812de5d569721c8294b581e1d9e6Timo Sirainen if (proxy->io_plain_write != NULL)
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen io_remove(&proxy->io_plain_write);
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen }
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen ssl_set_io(proxy, SSL_ADD_INPUT);
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen if (SSL_pending(proxy->ssl) > 0)
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen ssl_read(proxy);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen }
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen
fc464e5b2b2ab4d415a5d5b90ce4475d34620a75Timo Sirainen ssl_proxy_unref(proxy);
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen}
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen
9f10cc61ec303351b43e54155c86699ef53cb8beTimo Sirainenstatic void ssl_handle_error(struct ssl_proxy *proxy, int ret,
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen const char *func_name)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen{
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen const char *errstr = NULL;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen int err;
fc464e5b2b2ab4d415a5d5b90ce4475d34620a75Timo Sirainen
5736aef6d0abe6796e57c2eda68f5c25db677918Timo Sirainen proxy->refcount++;
fc464e5b2b2ab4d415a5d5b90ce4475d34620a75Timo Sirainen
fc464e5b2b2ab4d415a5d5b90ce4475d34620a75Timo Sirainen i_free_and_null(proxy->last_error);
fc464e5b2b2ab4d415a5d5b90ce4475d34620a75Timo Sirainen err = SSL_get_error(proxy->ssl, ret);
fc464e5b2b2ab4d415a5d5b90ce4475d34620a75Timo Sirainen
fc464e5b2b2ab4d415a5d5b90ce4475d34620a75Timo Sirainen switch (err) {
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen case SSL_ERROR_WANT_READ:
7afde4b6c600f86ef6f742ea3b01640075ce16a2Timo Sirainen ssl_set_io(proxy, SSL_ADD_INPUT);
7afde4b6c600f86ef6f742ea3b01640075ce16a2Timo Sirainen break;
5e751dbaecf7c337abc149f328c4a13ee5c15134Timo Sirainen case SSL_ERROR_WANT_WRITE:
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen ssl_set_io(proxy, SSL_ADD_OUTPUT);
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen break;
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen case SSL_ERROR_SYSCALL:
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen /* eat up the error queue */
9f10cc61ec303351b43e54155c86699ef53cb8beTimo Sirainen if (ERR_peek_error() != 0)
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen errstr = openssl_iostream_error();
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen else if (ret != 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen errstr = strerror(errno);
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen else {
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen /* EOF. */
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen errstr = "Disconnected";
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen break;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen }
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen errstr = t_strdup_printf("%s syscall failed: %s",
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen func_name, errstr);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen break;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen case SSL_ERROR_ZERO_RETURN:
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen /* clean connection closing */
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen ssl_proxy_destroy(proxy);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen break;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen case SSL_ERROR_SSL:
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen if (ERR_GET_REASON(ERR_peek_error()) == ERR_R_MALLOC_FAILURE) {
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen i_error("OpenSSL malloc() failed. "
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen "You may need to increase service %s { vsz_limit }",
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen login_binary->process_name);
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen }
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen errstr = t_strdup_printf("%s failed: %s",
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen func_name, openssl_iostream_error());
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen break;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen default:
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen errstr = t_strdup_printf("%s failed: unknown failure %d (%s)",
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen func_name, err, openssl_iostream_error());
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen break;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen }
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen if (errstr != NULL) {
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen if (proxy->ssl_set->verbose_ssl)
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen i_debug("SSL error: %s", errstr);
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen proxy->last_error = i_strdup(errstr);
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen ssl_proxy_destroy_failed(proxy);
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen }
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen ssl_proxy_unref(proxy);
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen}
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainenstatic void ssl_handshake(struct ssl_proxy *proxy)
dd7cbb32412c2f4d2d223af66672535bc1237246Timo Sirainen{
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen int ret;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if (proxy->client_proxy) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen ret = SSL_connect(proxy->ssl);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if (ret != 1) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen ssl_handle_error(proxy, ret, "SSL_connect()");
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen return;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen }
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen } else {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen ret = SSL_accept(proxy->ssl);
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen if (ret != 1) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen ssl_handle_error(proxy, ret, "SSL_accept()");
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen return;
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen }
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen }
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen i_free_and_null(proxy->last_error);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen proxy->handshaked = TRUE;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen ssl_set_io(proxy, SSL_ADD_INPUT);
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen plain_block_input(proxy, FALSE);
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen if (proxy->handshake_callback != NULL) {
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen if (proxy->handshake_callback(proxy->handshake_context) < 0)
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen ssl_proxy_destroy_failed(proxy);
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen }
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen}
08a33e7c0cf5ab2c4a0c96a55056cc3251d14c5eTimo Sirainen
cb17980a661554ebb3fd099c77e92a5be4d304ecTimo Sirainenstatic void ssl_read(struct ssl_proxy *proxy)
cb17980a661554ebb3fd099c77e92a5be4d304ecTimo Sirainen{
cb17980a661554ebb3fd099c77e92a5be4d304ecTimo Sirainen int ret;
cb17980a661554ebb3fd099c77e92a5be4d304ecTimo Sirainen
d2d5871fa9e7226df694ff7a4be511167b35b305Timo Sirainen while (proxy->plainout_size < sizeof(proxy->plainout_buf) &&
d2c853636ec2d99c9f96da877ff520a3b86a18baTimo Sirainen !proxy->destroyed) {
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen ret = SSL_read(proxy->ssl,
57f5683fd9dc9bc79816c418bb30fdbc33b68a8cTimo Sirainen proxy->plainout_buf + proxy->plainout_size,
57f5683fd9dc9bc79816c418bb30fdbc33b68a8cTimo Sirainen sizeof(proxy->plainout_buf) -
57f5683fd9dc9bc79816c418bb30fdbc33b68a8cTimo Sirainen proxy->plainout_size);
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen if (ret <= 0) {
57f5683fd9dc9bc79816c418bb30fdbc33b68a8cTimo Sirainen ssl_handle_error(proxy, ret, "SSL_read()");
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen break;
96f2533c48ce5def0004931606a2fdf275578880Timo Sirainen } else {
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen i_free_and_null(proxy->last_error);
55a14bce15b9f44441b5f56616d73651a294d770Timo Sirainen proxy->plainout_size += ret;
55a14bce15b9f44441b5f56616d73651a294d770Timo Sirainen plain_write(proxy);
b16ee3cbbcd18cb86f2f73b5cc163ebfb995ffafTimo Sirainen }
55a14bce15b9f44441b5f56616d73651a294d770Timo Sirainen }
55a14bce15b9f44441b5f56616d73651a294d770Timo Sirainen}
55a14bce15b9f44441b5f56616d73651a294d770Timo Sirainen
55a14bce15b9f44441b5f56616d73651a294d770Timo Sirainenstatic void ssl_write(struct ssl_proxy *proxy)
b16ee3cbbcd18cb86f2f73b5cc163ebfb995ffafTimo Sirainen{
b16ee3cbbcd18cb86f2f73b5cc163ebfb995ffafTimo Sirainen int ret;
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen ret = SSL_write(proxy->ssl, proxy->sslout_buf, proxy->sslout_size);
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen if (ret <= 0)
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen ssl_handle_error(proxy, ret, "SSL_write()");
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen else {
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen i_free_and_null(proxy->last_error);
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen proxy->sslout_size -= ret;
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen memmove(proxy->sslout_buf, proxy->sslout_buf + ret,
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen proxy->sslout_size);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen ssl_set_io(proxy, proxy->sslout_size > 0 ?
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen SSL_ADD_OUTPUT : SSL_REMOVE_OUTPUT);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen plain_block_input(proxy, FALSE);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen }
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen}
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainenstatic void ssl_step(struct ssl_proxy *proxy)
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen{
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen proxy->refcount++;
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen if (!proxy->handshaked) {
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen ssl_set_io(proxy, SSL_REMOVE_OUTPUT);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen ssl_handshake(proxy);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen }
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen if (proxy->handshaked) {
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen if (proxy->plainout_size == sizeof(proxy->plainout_buf))
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen ssl_set_io(proxy, SSL_REMOVE_INPUT);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen else
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen ssl_read(proxy);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen if (proxy->sslout_size == 0)
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen ssl_set_io(proxy, SSL_REMOVE_OUTPUT);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen else {
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen (void)net_set_cork(proxy->fd_ssl, TRUE);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen ssl_write(proxy);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen (void)net_set_cork(proxy->fd_ssl, FALSE);
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen }
318ef3683d67683173f1b552cf5f9af4375b3017Timo Sirainen }
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen ssl_proxy_unref(proxy);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen}
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainenstatic int
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainenssl_proxy_alloc_common(SSL_CTX *ssl_ctx, int fd, const struct ip_addr *ip,
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen pool_t set_pool, const struct login_settings *login_set,
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen const struct master_service_ssl_settings *ssl_set,
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen struct ssl_proxy **proxy_r)
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen{
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen struct ssl_proxy *proxy;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen SSL *ssl;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen int sfd[2];
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen i_assert(fd != -1);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen *proxy_r = NULL;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen if (!ssl_initialized) {
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen i_error("SSL support not enabled in configuration");
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen return -1;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen }
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen ssl = SSL_new(ssl_ctx);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen if (ssl == NULL) {
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen i_error("SSL_new() failed: %s", openssl_iostream_error());
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen return -1;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen }
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen if (SSL_set_fd(ssl, fd) != 1) {
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen i_error("SSL_set_fd() failed: %s", openssl_iostream_error());
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen SSL_free(ssl);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen return -1;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen }
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) < 0) {
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen i_error("socketpair() failed: %m");
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen SSL_free(ssl);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen return -1;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen }
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen net_set_nonblock(sfd[0], TRUE);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen net_set_nonblock(sfd[1], TRUE);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen net_set_nonblock(fd, TRUE);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen proxy = i_new(struct ssl_proxy, 1);
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen proxy->refcount = 2;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen proxy->ssl = ssl;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen proxy->login_set = login_set;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen proxy->ssl_set = ssl_set;
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen 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;
i_zero(&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;
lookup_ctx.compression = set->parsed_opts.compression;
lookup_ctx.tickets = set->parsed_opts.tickets;
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_malloc0(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)
{
#if defined(HAVE_SSL_COMPRESSION) && !defined(OPENSSL_NO_COMP)
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_flush(struct ssl_proxy *proxy)
{
/* this is pretty kludgy. mainly this is just for flushing the final
LOGOUT command output. */
plain_read(proxy);
ssl_step(proxy);
}
void ssl_proxy_destroy(struct ssl_proxy *proxy)
{
if (proxy->destroyed || proxy->flushing)
return;
proxy->flushing = TRUE;
if (!proxy->failed && proxy->handshaked)
ssl_proxy_flush(proxy);
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);
if (SSL_shutdown(proxy->ssl) != 1) {
/* if bidirectional shutdown fails we need to clear
the error queue. */
openssl_iostream_clear_errors();
}
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)
{
RSA *rsa = RSA_new();
BIGNUM *e = BN_new();
BN_set_word(e, RSA_F4);
RSA_generate_key_ex(rsa, keylength, e, NULL);
BN_free(e);
return rsa;
}
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;
int ctxerr;
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;
ctxerr = X509_STORE_CTX_get_error(ctx);
if (proxy->client_proxy && !proxy->login_set->ssl_require_crl &&
(ctxerr == X509_V_ERR_UNABLE_TO_GET_CRL ||
ctxerr == X509_V_ERR_CRL_HAS_EXPIRED)) {
/* no CRL given with the CA list. don't worry about it. */
preverify_ok = 1;
}
if (preverify_ok == 0)
proxy->cert_broken = TRUE;
subject = X509_get_subject_name(X509_STORE_CTX_get_current_cert(ctx));
(void)X509_NAME_oneline(subject, buf, sizeof(buf));
buf[sizeof(buf)-1] = '\0'; /* just in case.. */
ctxerr = X509_STORE_CTX_get_error(ctx);
if (proxy->cert_error == NULL) {
proxy->cert_error = p_strdup_printf(proxy->client->pool, "%s: %s",
X509_verify_cert_error_string(ctxerr), buf);
}
if (proxy->ssl_set->verbose_ssl ||
(proxy->login_set->auth_verbose && preverify_ok == 0)) {
if (preverify_ok != 0) {
client_log(proxy->client, t_strdup_printf(
"Valid certificate: %s", buf));
} else {
client_log(proxy->client, t_strdup_printf(
"Invalid certificate: %s: %s",
X509_verify_cert_error_string(ctxerr), 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 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", openssl_iostream_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 != NULL) {
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 != NULL)
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 */
long ssl_ops = SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
#ifdef SSL_OP_NO_COMPRESSION
if (!set->parsed_opts.compression)
ssl_ops |= SSL_OP_NO_COMPRESSION;
#endif
#ifdef SSL_OP_NO_TICKET
if (!set->parsed_opts.tickets)
ssl_ops |= SSL_OP_NO_TICKET;
#endif
SSL_CTX_set_options(ssl_ctx, ssl_ops);
#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) && !defined(SSL_CTRL_SET_ECDH_AUTO)
EC_KEY *ecdh;
int nid;
const char *curve_name;
#endif
if (SSL_CTX_need_tmp_RSA(ssl_ctx) != 0)
SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
#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);
#ifdef SSL_CTRL_SET_ECDH_AUTO
/* 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 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",
openssl_iostream_key_load_error());
}
BIO_free(bio);
return pkey;
}
static DH *
ssl_proxy_load_dh(const char *dhparam)
{
DH *dh;
BIO *bio;
bio = BIO_new_mem_buf(t_strdup_noconst(dhparam), strlen(dhparam));
if (bio == NULL)
i_fatal("BIO_new_mem_buf() failed");
dh = NULL;
dh = PEM_read_bio_DHparams(bio, &dh, NULL, NULL);
if (dh == NULL)
i_fatal("Couldn't parse DH parameters: %s",
openssl_iostream_key_load_error());
BIO_free(bio);
return dh;
}
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", openssl_iostream_key_load_error());
EVP_PKEY_free(pkey);
}
static void
ssl_proxy_ctx_use_dh(SSL_CTX *ctx,
const struct master_service_ssl_settings *set)
{
DH *dh;
dh = ssl_proxy_load_dh(set->ssl_dh);
if (SSL_CTX_set_tmp_dh(ctx, dh) != 1)
i_fatal("Can't load DH parameters: %s", openssl_iostream_key_load_error());
DH_free(dh);
}
#if defined(HAVE_ECDH) && !defined(SSL_CTRL_SET_ECDH_AUTO)
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);
else {
/* clear errors added by the above calls */
openssl_iostream_clear_errors();
}
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 == 0) {
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);
}
client->local_name = p_strdup(client->pool, host);
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->dh = p_strdup(pool, ssl_set->ssl_dh);
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->compression = ssl_set->parsed_opts.compression;
ctx->tickets = ssl_set->parsed_opts.tickets;
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, openssl_iostream_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",
openssl_iostream_use_certificate_error(ctx->cert, "ssl_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);
ssl_proxy_ctx_use_dh(ctx->ctx, ssl_set);
if (ctx->verify_client_cert)
ssl_proxy_ctx_verify_client(ctx->ctx, xnames);
i_assert(hash_table_lookup(ssl_servers, ctx) == NULL);
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",
openssl_iostream_use_certificate_error(
set->ssl_client_cert, "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",
openssl_iostream_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);
i_zero(&ssl_params);
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