changeset 12616:bd23d4e10fa1

Added lib-ssl-iostream for handling SSL connections more easily.
author Timo Sirainen <tss@iki.fi>
date Mon, 31 Jan 2011 18:40:27 +0200
parents 3dde816d945d
children 7b7434fef6ff
files configure.in src/Makefile.am src/lib-ssl-iostream/Makefile.am src/lib-ssl-iostream/iostream-openssl-context.c src/lib-ssl-iostream/iostream-openssl-params.c src/lib-ssl-iostream/iostream-openssl.c src/lib-ssl-iostream/iostream-openssl.h src/lib-ssl-iostream/iostream-ssl.h src/lib-ssl-iostream/istream-openssl.c src/lib-ssl-iostream/ostream-openssl.c
diffstat 10 files changed, 1470 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Mon Jan 31 04:02:04 2011 +0200
+++ b/configure.in	Mon Jan 31 18:40:27 2011 +0200
@@ -2664,6 +2664,7 @@
 src/lib-otp/Makefile
 src/lib-dovecot/Makefile
 src/lib-settings/Makefile
+src/lib-ssl-iostream/Makefile
 src/lib-test/Makefile
 src/lib-storage/Makefile
 src/lib-storage/list/Makefile
--- a/src/Makefile.am	Mon Jan 31 04:02:04 2011 +0200
+++ b/src/Makefile.am	Mon Jan 31 18:40:27 2011 +0200
@@ -17,6 +17,7 @@
 	lib-index \
 	lib-storage \
 	lib-sql \
+	lib-ssl-iostream \
 	lib-ntlm \
 	lib-otp \
 	lib-lda \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/Makefile.am	Mon Jan 31 18:40:27 2011 +0200
@@ -0,0 +1,28 @@
+noinst_LTLIBRARIES = libssl_iostream.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-test
+
+libssl_iostream_la_SOURCES = \
+	iostream-openssl.c \
+	iostream-openssl-context.c \
+	iostream-openssl-params.c \
+	istream-openssl.c \
+	ostream-openssl.c
+
+libssl_iostream_la_LIBADD = \
+	$(SSL_LIBS)
+
+headers = \
+	iostream-openssl.h \
+	iostream-ssl.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+pkglib_LTLIBRARIES = libdovecot-ssl.la
+libdovecot_ssl_la_SOURCES = 
+libdovecot_ssl_la_LIBADD = libssl_iostream.la
+libdovecot_ssl_la_DEPENDENCIES = libssl_iostream.la
+libdovecot_ssl_la_LDFLAGS = -export-dynamic
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/iostream-openssl-context.c	Mon Jan 31 18:40:27 2011 +0200
@@ -0,0 +1,450 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "safe-memset.h"
+#include "iostream-openssl.h"
+
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+struct ssl_iostream_password_context {
+	const char *password;
+	const char *key_source;
+};
+
+static bool ssl_global_initialized = FALSE;
+int dovecot_ssl_extdata_index;
+
+static void ssl_iostream_init_global(void);
+
+const char *ssl_iostream_error(void)
+{
+	unsigned long err;
+	char *buf;
+	size_t err_size = 256;
+
+	err = ERR_get_error();
+	if (err == 0) {
+		if (errno != 0)
+			return strerror(errno);
+		return "Unknown error";
+	}
+	if (ERR_GET_REASON(err) == ERR_R_MALLOC_FAILURE)
+		i_fatal_status(FATAL_OUTOFMEM, "OpenSSL malloc() failed");
+
+	buf = t_malloc(err_size);
+	buf[err_size-1] = '\0';
+	ERR_error_string_n(err, buf, err_size-1);
+	return buf;
+}
+
+const char *ssl_iostream_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_iostream_error();
+}
+
+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)
+{
+	struct ssl_iostream *ssl_io;
+
+	ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index);
+	/* Well, I'm not exactly sure why the logic in here is this.
+	   It's the same as in Postfix, so it can't be too wrong. */
+	if (is_export && keylength == 512 && ssl_io->ctx->dh_512 != NULL)
+		return ssl_io->ctx->dh_512;
+	else
+		return ssl_io->ctx->dh_1024;
+}
+
+static int
+pem_password_callback(char *buf, int size, int rwflag ATTR_UNUSED,
+		      void *userdata)
+{
+	struct ssl_iostream_password_context *ctx = userdata;
+
+	if (ctx->password == NULL) {
+		i_error("%s: SSL private key file is password protected, "
+			"but password isn't given", ctx->key_source);
+		return 0;
+	}
+
+	if (i_strocpy(buf, userdata, size) < 0) {
+		i_error("%s: SSL private key password is too long",
+			ctx->key_source);
+		return 0;
+	}
+	return strlen(buf);
+}
+
+int ssl_iostream_load_key(const struct ssl_iostream_settings *set,
+			  const char *key_source, EVP_PKEY **pkey_r)
+{
+	struct ssl_iostream_password_context ctx;
+	EVP_PKEY *pkey;
+	BIO *bio;
+	char *key;
+
+	key = t_strdup_noconst(set->key);
+	bio = BIO_new_mem_buf(key, strlen(key));
+	if (bio == NULL) {
+		i_error("BIO_new_mem_buf() failed: %s", ssl_iostream_error());
+		safe_memset(key, 0, strlen(key));
+		return -1;
+	}
+
+	ctx.password = set->key_password;
+	ctx.key_source = key_source;
+
+	pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback, &ctx);
+	if (pkey == NULL) {
+		i_error("%s: Couldn't parse private SSL key: %s",
+			key_source, ssl_iostream_error());
+	}
+	BIO_free(bio);
+
+	safe_memset(key, 0, strlen(key));
+	*pkey_r = pkey;
+	return pkey == NULL ? -1 : 0;
+}
+
+static int
+ssl_iostream_ctx_use_key(struct ssl_iostream_context *ctx,
+			 const struct ssl_iostream_settings *set)
+{
+	EVP_PKEY *pkey;
+	int ret = 0;
+
+	if (ssl_iostream_load_key(set, ctx->source, &pkey) < 0)
+		return -1;
+	if (!SSL_CTX_use_PrivateKey(ctx->ssl_ctx, pkey)) {
+		i_error("%s: Can't load SSL private key: %s",
+			ctx->source, ssl_iostream_key_load_error());
+		ret = -1;
+	}
+	EVP_PKEY_free(pkey);
+	return ret;
+}
+
+static bool is_pem_key(const char *cert)
+{
+	return strstr(cert, "PRIVATE KEY---") != NULL;
+}
+
+const char *ssl_iostream_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_iostream_error();
+	else if (is_pem_key(cert)) {
+		return "The file contains a private key "
+			"(you've mixed ssl_cert and ssl_key settings)";
+	} else {
+		return "There is no certificate.";
+	}
+}
+
+static int ssl_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 (ERR_peek_error() != 0)
+		ret = 0;
+
+	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;
+}
+
+static int 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;
+	STACK_OF(X509_NAME) *xnames;
+	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);
+	BIO_free(bio);
+
+	if (inf == NULL)
+		return -1;
+
+	xnames = sk_X509_NAME_new_null();
+	if (xnames == NULL)
+		i_fatal("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)
+				xname = X509_NAME_dup(xname);
+			if (xname != NULL)
+				sk_X509_NAME_push(xnames, xname);
+		}
+		if(itmp->crl)
+			X509_STORE_add_crl(store, itmp->crl);
+	}
+	sk_X509_INFO_pop_free(inf, X509_INFO_free);
+	*xnames_r = xnames;
+	return 0;
+}
+
+static int
+ssl_iostream_ctx_verify_remote_cert(struct ssl_iostream_context *ctx,
+				    STACK_OF(X509_NAME) *ca_names)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+	X509_STORE *store;
+
+	store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
+	X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
+			     X509_V_FLAG_CRL_CHECK_ALL);
+#endif
+
+	SSL_CTX_set_client_CA_list(ctx->ssl_ctx, ca_names);
+	return 0;
+}
+
+static struct ssl_iostream_settings *
+ssl_iostream_settings_dup(pool_t pool,
+			  const struct ssl_iostream_settings *old_set)
+{
+	struct ssl_iostream_settings *new_set;
+
+	new_set = p_new(pool, struct ssl_iostream_settings, 1);
+	new_set->cipher_list = p_strdup(pool, old_set->cipher_list);
+	new_set->cert = p_strdup(pool, old_set->cert);
+	new_set->key = p_strdup(pool, old_set->key);
+	new_set->key_password = p_strdup(pool, old_set->key_password);
+
+	new_set->verbose = old_set->verbose;
+	return new_set;
+}
+
+static int
+ssl_iostream_context_set(struct ssl_iostream_context *ctx,
+			 const struct ssl_iostream_settings *set)
+{
+	X509_STORE *store;
+	STACK_OF(X509_NAME) *xnames = NULL;
+
+	ctx->set = ssl_iostream_settings_dup(ctx->pool, set);
+	if (set->cipher_list != NULL &&
+	    !SSL_CTX_set_cipher_list(ctx->ssl_ctx, set->cipher_list)) {
+		i_error("%s: Can't set cipher list to '%s': %s",
+			ctx->source, set->cipher_list,
+			ssl_iostream_error());
+		return -1;
+	}
+
+	if (set->cert != NULL &&
+	    ssl_ctx_use_certificate_chain(ctx->ssl_ctx, set->cert) < 0) {
+		i_error("%s: Can't load SSL certificate: %s", ctx->source,
+			ssl_iostream_get_use_certificate_error(set->cert));
+	}
+	if (set->key != NULL) {
+		if (ssl_iostream_ctx_use_key(ctx, set) < 0)
+			return -1;
+	}
+
+	/* set trusted CA certs */
+	if (!set->verify_remote_cert) {
+		/* no CA */
+	} else if (set->ca != NULL) {
+		store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
+		if (load_ca(store, set->ca, &xnames) < 0) {
+			i_error("%s: Couldn't parse ssl_ca: %s", ctx->source,
+				ssl_iostream_error());
+			return -1;
+		}
+		if (ssl_iostream_ctx_verify_remote_cert(ctx, xnames) < 0)
+			return -1;
+	} else if (set->ca_dir != NULL) {
+		if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, NULL,
+						   set->ca_dir)) {
+			i_error("%s: Can't load CA certs from directory %s: %s",
+				ctx->source, set->ca_dir, ssl_iostream_error());
+			return -1;
+		}
+	} else {
+		i_error("%s: Can't verify remote certs without CA",
+			ctx->source);
+		return -1;
+	}
+
+	if (set->cert_username_field != NULL) {
+		ctx->username_nid = OBJ_txt2nid(set->cert_username_field);
+		if (ctx->username_nid == NID_undef) {
+			i_error("%s: Invalid cert_username_field: %s",
+				ctx->source, set->cert_username_field);
+		}
+	}
+	return 0;
+}
+
+static int
+ssl_iostream_context_init_common(struct ssl_iostream_context *ctx,
+				 const char *source,
+				 const struct ssl_iostream_settings *set)
+{
+	ctx->pool = pool_alloconly_create("ssl iostream context", 4096);
+	ctx->source = p_strdup(ctx->pool, source);
+
+	SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
+	if (SSL_CTX_need_tmp_RSA(ctx->ssl_ctx))
+		SSL_CTX_set_tmp_rsa_callback(ctx->ssl_ctx, ssl_gen_rsa_key);
+	SSL_CTX_set_tmp_dh_callback(ctx->ssl_ctx, ssl_tmp_dh_callback);
+
+	return ssl_iostream_context_set(ctx, set);
+}
+
+int ssl_iostream_context_init_client(const char *source,
+				     const struct ssl_iostream_settings *set,
+				     struct ssl_iostream_context **ctx_r)
+{
+	struct ssl_iostream_context *ctx;
+	SSL_CTX *ssl_ctx;
+
+	ssl_iostream_init_global();
+	if ((ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
+		i_error("SSL_CTX_new() failed: %s", ssl_iostream_error());
+		return -1;
+	}
+
+	ctx = i_new(struct ssl_iostream_context, 1);
+	ctx->ssl_ctx = ssl_ctx;
+	ctx->client_ctx = TRUE;
+	if (ssl_iostream_context_init_common(ctx, source, set) < 0) {
+		ssl_iostream_context_deinit(&ctx);
+		return -1;
+	}
+	*ctx_r = ctx;
+	return 0;
+}
+
+int ssl_iostream_context_init_server(const char *source,
+				     const struct ssl_iostream_settings *set,
+				     struct ssl_iostream_context **ctx_r)
+{
+	struct ssl_iostream_context *ctx;
+	SSL_CTX *ssl_ctx;
+
+	ssl_iostream_init_global();
+	if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
+		i_error("SSL_CTX_new() failed: %s", ssl_iostream_error());
+		return -1;
+	}
+
+	ctx = i_new(struct ssl_iostream_context, 1);
+	ctx->ssl_ctx = ssl_ctx;
+	if (ssl_iostream_context_init_common(ctx, source, set) < 0) {
+		ssl_iostream_context_deinit(&ctx);
+		return -1;
+	}
+	*ctx_r = ctx;
+	return 0;
+}
+
+void ssl_iostream_context_deinit(struct ssl_iostream_context **_ctx)
+{
+	struct ssl_iostream_context *ctx = *_ctx;
+
+	*_ctx = NULL;
+	SSL_CTX_free(ctx->ssl_ctx);
+	ssl_iostream_context_free_params(ctx);
+	pool_unref(&ctx->pool);
+	i_free(ctx);
+}
+
+static void ssl_iostream_deinit_global(void)
+{
+	EVP_cleanup();
+	ERR_free_strings();
+}
+
+static void ssl_iostream_init_global(void)
+{
+	static char dovecot[] = "dovecot";
+	unsigned char buf;
+
+	if (ssl_global_initialized)
+		return;
+
+	atexit(ssl_iostream_deinit_global);
+	ssl_global_initialized = TRUE;
+	SSL_library_init();
+	SSL_load_error_strings();
+
+	dovecot_ssl_extdata_index =
+		SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL);
+
+	/* 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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/iostream-openssl-params.c	Mon Jan 31 18:40:27 2011 +0200
@@ -0,0 +1,129 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "iostream-openssl.h"
+
+/* 2 or 5. Haven't seen their difference explained anywhere, but 2 is the
+   default.. */
+#define DH_GENERATOR 2
+
+static int dh_param_bitsizes[] = { 512, 1024 };
+
+static int generate_dh_parameters(int bitsize, buffer_t *output)
+{
+        DH *dh;
+	unsigned char *p;
+	int len, len2;
+
+	dh = DH_generate_parameters(bitsize, DH_GENERATOR, NULL, NULL);
+	if (dh == NULL) {
+		i_error("DH_generate_parameters(bits=%d, gen=%d) failed: %s",
+			bitsize, DH_GENERATOR, ssl_iostream_error());
+		return -1;
+	}
+
+	len = i2d_DHparams(dh, NULL);
+	if (len < 0) {
+		i_error("i2d_DHparams() failed: %s", ssl_iostream_error());
+		DH_free(dh);
+		return -1;
+	}
+
+	buffer_append(output, &bitsize, sizeof(bitsize));
+	buffer_append(output, &len, sizeof(len));
+
+	p = buffer_append_space_unsafe(output, len);
+	len2 = i2d_DHparams(dh, &p);
+	i_assert(len == len2);
+	DH_free(dh);
+	return 0;
+}
+
+int ssl_iostream_generate_params(buffer_t *output)
+{
+	unsigned int i;
+
+	for (i = 0; i < N_ELEMENTS(dh_param_bitsizes); i++) {
+		if (generate_dh_parameters(dh_param_bitsizes[i], output) < 0)
+			return -1;
+	}
+	buffer_append_zero(output, sizeof(int));
+	return 0;
+}
+
+static int read_int(const unsigned char **data, const unsigned char *end)
+{
+	unsigned int len = end - *data;
+	int ret;
+
+	if (len < sizeof(ret))
+		return -1;
+	memcpy(&ret, *data, sizeof(ret));
+	*data += sizeof(ret);
+	return ret;
+}
+
+static int
+read_dh_parameters_next(struct ssl_iostream_context *ctx,
+			const unsigned char **data, const unsigned char *end)
+{
+	const unsigned char *dbuf;
+	DH *dh;
+	int bits, len, ret = 1;
+
+	/* get bit size. 0 ends the DH parameters list. */
+	if ((bits = read_int(data, end)) <= 0)
+		return bits;
+
+	/* get data size */
+	if ((len = read_int(data, end)) <= 0 || end - *data < len)
+		return -1;
+
+	dbuf = *data;
+	dh = d2i_DHparams(NULL, &dbuf, len);
+	*data += len;
+
+	if (dh == NULL)
+		return -1;
+
+	switch (bits) {
+	case 512:
+		ctx->dh_512 = dh;
+		break;
+	case 1024:
+		ctx->dh_1024 = dh;
+		break;
+	default:
+		ret = -1;
+		break;
+	}
+	return ret;
+}
+
+int ssl_iostream_context_import_params(struct ssl_iostream_context *ctx,
+				       const buffer_t *input)
+{
+	const unsigned char *data, *end;
+	int ret;
+
+	ssl_iostream_context_free_params(ctx);
+
+	data = input->data;
+	end = data + input->used;
+	while ((ret = read_dh_parameters_next(ctx, &data, end)) > 0) ;
+
+	return ret < 0 || data != end ? -1 : 0;
+}
+
+void ssl_iostream_context_free_params(struct ssl_iostream_context *ctx)
+{
+	if (ctx->dh_512 != NULL) {
+		DH_free(ctx->dh_512);
+                ctx->dh_512 = NULL;
+	}
+	if (ctx->dh_1024 != NULL) {
+		DH_free(ctx->dh_1024);
+                ctx->dh_1024 = NULL;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/iostream-openssl.c	Mon Jan 31 18:40:27 2011 +0200
@@ -0,0 +1,482 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream-openssl.h"
+
+#include <openssl/err.h>
+
+static void ssl_iostream_free(struct ssl_iostream *ssl_io);
+
+static void ssl_info_callback(const SSL *ssl, int where, int ret)
+{
+	struct ssl_iostream *ssl_io;
+
+	ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index);
+	if ((where & SSL_CB_ALERT) != 0) {
+		i_warning("%s: SSL alert: where=0x%x, ret=%d: %s %s",
+			  ssl_io->source, where, ret,
+			  SSL_alert_type_string_long(ret),
+			  SSL_alert_desc_string_long(ret));
+	} else if (ret == 0) {
+		i_warning("%s: SSL failed: where=0x%x: %s",
+			  ssl_io->source, where, SSL_state_string_long(ssl));
+	} else {
+		i_warning("%s: SSL: where=0x%x, ret=%d: %s",
+			  ssl_io->source, where, ret,
+			  SSL_state_string_long(ssl));
+	}
+}
+
+static int
+ssl_iostream_use_certificate(struct ssl_iostream *ssl_io, const char *cert)
+{
+	BIO *in;
+	X509 *x;
+	int ret = 0;
+
+	in = BIO_new_mem_buf(t_strdup_noconst(cert), strlen(cert));
+	if (in == NULL) {
+		i_error("BIO_new_mem_buf() failed: %s", ssl_iostream_error());
+		return -1;
+	}
+
+	x = PEM_read_bio_X509(in, NULL, NULL, NULL);
+	if (x != NULL) {
+		ret = SSL_use_certificate(ssl_io->ssl, x);
+		if (ERR_peek_error() != 0)
+			ret = 0;
+		X509_free(x);
+	}
+	BIO_free(in);
+
+	if (ret == 0) {
+		i_error("%s: Can't load ssl_cert: %s", ssl_io->source,
+			ssl_iostream_get_use_certificate_error(cert));
+		return -1;
+	}
+	return 0;
+}
+
+static int
+ssl_iostream_use_key(struct ssl_iostream *ssl_io,
+		     const struct ssl_iostream_settings *set)
+{
+	EVP_PKEY *pkey;
+	int ret = 0;
+
+	if (ssl_iostream_load_key(set, ssl_io->source, &pkey) < 0)
+		return -1;
+	if (SSL_use_PrivateKey(ssl_io->ssl, pkey) != 1) {
+		i_error("%s: Can't load SSL private key: %s",
+			ssl_io->source, ssl_iostream_key_load_error());
+		ret = -1;
+	}
+	EVP_PKEY_free(pkey);
+	return ret;
+}
+
+static int
+ssl_iostream_verify_client_cert(int preverify_ok, X509_STORE_CTX *ctx)
+{
+	int ssl_extidx = SSL_get_ex_data_X509_STORE_CTX_idx();
+	SSL *ssl;
+	struct ssl_iostream *ssl_io;
+
+	ssl = X509_STORE_CTX_get_ex_data(ctx, ssl_extidx);
+	ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index);
+	ssl_io->cert_received = TRUE;
+
+	if (ssl_io->verbose ||
+	    (ssl_io->verbose_invalid_cert && !preverify_ok)) {
+		char buf[1024];
+		X509_NAME *subject;
+
+		subject = X509_get_subject_name(ctx->current_cert);
+		if (X509_NAME_oneline(subject, buf, sizeof(buf)) == NULL)
+			buf[0] = '\0';
+		else
+			buf[sizeof(buf)-1] = '\0'; /* just in case.. */
+		if (!preverify_ok) {
+			i_info("Invalid certificate: %s: %s",
+			       X509_verify_cert_error_string(ctx->error), buf);
+		} else {
+			i_info("Valid certificate: %s", buf);
+		}
+	}
+	if (!preverify_ok) {
+		ssl_io->cert_broken = TRUE;
+		if (ssl_io->require_valid_cert)
+			return 0;
+	}
+	return 1;
+}
+
+static int
+ssl_iostream_set(struct ssl_iostream *ssl_io,
+		 const struct ssl_iostream_settings *set)
+{
+	const struct ssl_iostream_settings *ctx_set = ssl_io->ctx->set;
+
+	if (set->verbose)
+		SSL_set_info_callback(ssl_io->ssl, ssl_info_callback);
+
+	if (set->cipher_list != NULL &&
+	    strcmp(ctx_set->cipher_list, set->cipher_list) != 0) {
+		if (!SSL_set_cipher_list(ssl_io->ssl, set->cipher_list)) {
+			i_error("%s: Can't set cipher list to '%s': %s",
+				ssl_io->source, set->cipher_list,
+				ssl_iostream_error());
+		}
+		return -1;
+
+	}
+	if (set->cert != NULL && strcmp(ctx_set->cert, set->cert) != 0) {
+		if (ssl_iostream_use_certificate(ssl_io, set->cert) < 0)
+			return -1;
+	}
+	if (set->key != NULL && strcmp(ctx_set->key, set->key) != 0) {
+		if (ssl_iostream_use_key(ssl_io, set) < 0)
+			return -1;
+	}
+	if (set->verify_remote_cert) {
+		SSL_set_verify(ssl_io->ssl,
+			       SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE,
+			       ssl_iostream_verify_client_cert);
+	}
+
+	if (set->cert_username_field != NULL) {
+		ssl_io->username_nid = OBJ_txt2nid(set->cert_username_field);
+		if (ssl_io->username_nid == NID_undef) {
+			i_error("%s: Invalid cert_username_field: %s",
+				ssl_io->source, set->cert_username_field);
+		}
+	} else {
+		ssl_io->username_nid = ssl_io->ctx->username_nid;
+	}
+
+	ssl_io->verbose = set->verbose;
+	ssl_io->verbose_invalid_cert = set->verbose_invalid_cert;
+	ssl_io->require_valid_cert = set->require_valid_cert;
+	return 0;
+}
+
+int io_stream_create_ssl(struct ssl_iostream_context *ctx, const char *source,
+			 const struct ssl_iostream_settings *set,
+			 struct istream **input, struct ostream **output,
+			 struct ssl_iostream **iostream_r)
+{
+	struct ssl_iostream *ssl_io;
+	SSL *ssl;
+	BIO *bio_int, *bio_ext;
+	int ret;
+
+	ssl = SSL_new(ctx->ssl_ctx);
+	if (ssl == NULL) {
+		i_error("SSL_new() failed: %s", ssl_iostream_error());
+		return -1;
+	}
+
+	if (BIO_new_bio_pair(&bio_int, 0, &bio_ext, 0) != 1) {
+		i_error("BIO_new_bio_pair() failed: %s", ssl_iostream_error());
+		SSL_free(ssl);
+		return -1;
+	}
+
+	ssl_io = i_new(struct ssl_iostream, 1);
+	ssl_io->refcount = 1;
+	ssl_io->ctx = ctx;
+	ssl_io->ssl = ssl;
+	ssl_io->bio_ext = bio_ext;
+	ssl_io->plain_input = *input;
+	ssl_io->plain_output = *output;
+	ssl_io->source = i_strdup(source);
+	SSL_set_bio(ssl_io->ssl, bio_int, bio_int);
+        SSL_set_ex_data(ssl_io->ssl, dovecot_ssl_extdata_index, ssl_io);
+
+	i_stream_ref(ssl_io->plain_input);
+	o_stream_ref(ssl_io->plain_output);
+
+	T_BEGIN {
+		ret = ssl_iostream_set(ssl_io, set);
+	} T_END;
+	if (ret < 0) {
+		ssl_iostream_free(ssl_io);
+		return -1;
+	}
+
+	*input = i_stream_create_ssl(ssl_io);
+	*output = o_stream_create_ssl(ssl_io);
+
+	ssl_io->ssl_output = *output;
+	*iostream_r = ssl_io;
+	return 0;
+}
+
+static void ssl_iostream_free(struct ssl_iostream *ssl_io)
+{
+	i_stream_unref(&ssl_io->plain_input);
+	o_stream_unref(&ssl_io->plain_output);
+	BIO_free(ssl_io->bio_ext);
+	SSL_free(ssl_io->ssl);
+	i_free(ssl_io->last_error);
+	i_free(ssl_io->source);
+	i_free(ssl_io);
+}
+
+void ssl_iostream_unref(struct ssl_iostream **_ssl_io)
+{
+	struct ssl_iostream *ssl_io = *_ssl_io;
+
+	*_ssl_io = NULL;
+
+	i_assert(ssl_io->refcount > 0);
+	if (--ssl_io->refcount >= 0)
+		return;
+
+	ssl_iostream_free(ssl_io);
+}
+
+static bool ssl_iostream_bio_output(struct ssl_iostream *ssl_io)
+{
+	size_t bytes, max_bytes;
+	ssize_t sent;
+	unsigned char buffer[1024];
+	bool bytes_sent = FALSE;
+	int ret;
+
+	while ((bytes = BIO_ctrl_pending(ssl_io->bio_ext)) > 0) {
+		max_bytes = o_stream_get_buffer_avail_size(ssl_io->plain_output);
+		if (bytes > max_bytes) {
+			if (max_bytes == 0) {
+				/* wait until output buffer clears */
+				break;
+			}
+			bytes = max_bytes;
+		}
+		if (bytes > sizeof(buffer))
+			bytes = sizeof(buffer);
+
+		ret = BIO_read(ssl_io->bio_ext, buffer, bytes);
+		i_assert(ret == (int)bytes);
+
+		sent = o_stream_send(ssl_io->plain_output, buffer, bytes);
+		i_assert(sent == (ssize_t)bytes);
+		bytes_sent = TRUE;
+	}
+	return bytes_sent;
+}
+
+static bool ssl_iostream_bio_input(struct ssl_iostream *ssl_io)
+{
+	const unsigned char *data;
+	size_t size;
+	bool bytes_read = FALSE;
+	int ret;
+
+	while (BIO_ctrl_get_read_request(ssl_io->bio_ext) > 0) {
+		(void)i_stream_read_data(ssl_io->plain_input, &data, &size, 0);
+		if (size == 0) {
+			/* wait for more input */
+			break;
+		}
+		ret = BIO_write(ssl_io->bio_ext, data, size);
+		i_assert(ret == (ssize_t)size);
+
+		i_stream_skip(ssl_io->plain_input, size);
+		bytes_read = TRUE;
+	}
+	return bytes_read;
+}
+
+bool ssl_iostream_bio_sync(struct ssl_iostream *ssl_io)
+{
+	bool ret;
+
+	ret = ssl_iostream_bio_output(ssl_io);
+	if (ssl_iostream_bio_input(ssl_io))
+		ret = TRUE;
+	return ret;
+}
+
+int ssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret,
+			      const char *func_name)
+{
+	const char *errstr = NULL;
+	int err;
+
+	err = SSL_get_error(ssl_io->ssl, ret);
+	switch (err) {
+	case SSL_ERROR_WANT_WRITE:
+		if (!ssl_iostream_bio_sync(ssl_io))
+			return 0;
+		return 1;
+	case SSL_ERROR_WANT_READ:
+		if (!ssl_iostream_bio_sync(ssl_io))
+			return 0;
+		return 1;
+	case SSL_ERROR_SYSCALL:
+		/* eat up the error queue */
+		if (ERR_peek_error() != 0) {
+			errstr = ssl_iostream_error();
+			errno = EINVAL;
+		} else if (ret != 0) {
+			errstr = strerror(errno);
+		} else {
+			/* EOF. */
+			errno = ECONNRESET;
+			errstr = "Disconnected";
+			break;
+		}
+		errstr = t_strdup_printf("%s syscall failed: %s",
+					 func_name, errstr);
+		break;
+	case SSL_ERROR_ZERO_RETURN:
+		/* clean connection closing */
+		errno = ECONNRESET;
+		break;
+	case SSL_ERROR_SSL:
+		errstr = t_strdup_printf("%s failed: %s",
+					 func_name, ssl_iostream_error());
+		errno = EINVAL;
+		break;
+	default:
+		errstr = t_strdup_printf("%s failed: unknown failure %d (%s)",
+					 func_name, err, ssl_iostream_error());
+		errno = EINVAL;
+		break;
+	}
+
+	if (errstr != NULL) {
+		i_free(ssl_io->last_error);
+		ssl_io->last_error = i_strdup(errstr);
+	}
+	return -1;
+}
+
+int ssl_iostream_handshake(struct ssl_iostream *ssl_io)
+{
+	int ret;
+
+	i_assert(!ssl_io->handshaked);
+
+	if (ssl_io->ctx->client_ctx) {
+		while ((ret = SSL_connect(ssl_io->ssl)) <= 0) {
+			ret = ssl_iostream_handle_error(ssl_io, ret,
+							"SSL_connect()");
+			if (ret <= 0)
+				return ret;
+		}
+	} else {
+		while ((ret = SSL_accept(ssl_io->ssl)) <= 0) {
+			ret = ssl_iostream_handle_error(ssl_io, ret,
+							"SSL_accept()");
+			if (ret <= 0)
+				return ret;
+		}
+	}
+	(void)ssl_iostream_bio_sync(ssl_io);
+
+	i_free_and_null(ssl_io->last_error);
+	ssl_io->handshaked = TRUE;
+
+	if (ssl_io->handshake_callback != NULL) {
+		if (ssl_io->handshake_callback(ssl_io->handshake_context) < 0) {
+			errno = EINVAL;
+			return -1;
+		}
+	}
+	if (ssl_io->ssl_output != NULL)
+		(void)o_stream_flush(ssl_io->ssl_output);
+	return 1;
+}
+
+void ssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io,
+					 int (*callback)(void *context),
+					 void *context)
+{
+	ssl_io->handshake_callback = callback;
+	ssl_io->handshake_context = context;
+}
+
+bool ssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io)
+{
+	return ssl_io->handshaked;
+}
+
+bool ssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io)
+{
+	return ssl_io->cert_received && !ssl_io->cert_broken;
+}
+
+bool ssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io)
+{
+	return ssl_io->cert_received && ssl_io->cert_broken;
+}
+
+const char *ssl_iostream_get_peer_name(struct ssl_iostream *ssl_io)
+{
+	X509 *x509;
+	char *name;
+	int len;
+
+	if (!ssl_iostream_has_valid_client_cert(ssl_io))
+		return NULL;
+
+	x509 = SSL_get_peer_certificate(ssl_io->ssl);
+	if (x509 == NULL)
+		return NULL; /* we should have had it.. */
+
+	len = X509_NAME_get_text_by_NID(X509_get_subject_name(x509),
+					ssl_io->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_io->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;
+}
+
+const char *ssl_iostream_get_security_string(struct ssl_iostream *ssl_io)
+{
+	SSL_CIPHER *cipher;
+#ifdef HAVE_SSL_COMPRESSION
+	const COMP_METHOD *comp;
+#endif
+	const char *comp_str;
+	int bits, alg_bits;
+
+	if (!ssl_io->handshaked)
+		return "";
+
+	cipher = SSL_get_current_cipher(ssl_io->ssl);
+	bits = SSL_CIPHER_get_bits(cipher, &alg_bits);
+#ifdef HAVE_SSL_COMPRESSION
+	comp = SSL_get_current_compression(ssl_io->ssl);
+	comp_str = comp == NULL ? "" :
+		t_strconcat(" ", SSL_COMP_get_name(comp), NULL);
+#else
+	comp_str = "";
+#endif
+	return t_strdup_printf("%s with cipher %s (%d/%d bits)",
+			       SSL_get_version(ssl_io->ssl),
+			       SSL_CIPHER_get_name(cipher),
+			       bits, alg_bits);
+}
+
+const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io)
+{
+	return ssl_io->last_error;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/iostream-openssl.h	Mon Jan 31 18:40:27 2011 +0200
@@ -0,0 +1,69 @@
+#ifndef IOSTREAM_OPENSSL_H
+#define IOSTREAM_OPENSSL_H
+
+#include "iostream-ssl.h"
+
+#include <openssl/ssl.h>
+
+struct ssl_iostream_context {
+	SSL_CTX *ssl_ctx;
+
+	pool_t pool;
+	const struct ssl_iostream_settings *set;
+	/* Used as logging prefix, e.g. "client" or "server" */
+	const char *source;
+
+	DH *dh_512, *dh_1024;
+	int username_nid;
+
+	unsigned int client_ctx:1;
+};
+
+struct ssl_iostream {
+	int refcount;
+	struct ssl_iostream_context *ctx;
+
+	const struct ssl_iostream_settings *set;
+
+	SSL *ssl;
+	BIO *bio_ext;
+
+	struct istream *plain_input;
+	struct ostream *plain_output;
+	struct ostream *ssl_output;
+
+	char *source;
+	char *last_error;
+
+	/* copied settings */
+	bool verbose, verbose_invalid_cert, require_valid_cert;
+	int username_nid;
+
+	int (*handshake_callback)(void *context);
+	void *handshake_context;
+
+	unsigned int handshaked:1;
+	unsigned int cert_received:1;
+	unsigned int cert_broken:1;
+};
+
+extern int dovecot_ssl_extdata_index;
+
+struct istream *i_stream_create_ssl(struct ssl_iostream *ssl_io);
+struct ostream *o_stream_create_ssl(struct ssl_iostream *ssl_io);
+void ssl_iostream_unref(struct ssl_iostream **ssl_io);
+
+int ssl_iostream_load_key(const struct ssl_iostream_settings *set,
+			  const char *key_source, EVP_PKEY **pkey_r);
+const char *ssl_iostream_get_use_certificate_error(const char *cert);
+
+bool ssl_iostream_bio_sync(struct ssl_iostream *ssl_io);
+int ssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret,
+			      const char *func_name);
+
+const char *ssl_iostream_error(void);
+const char *ssl_iostream_key_load_error(void);
+
+void ssl_iostream_context_free_params(struct ssl_iostream_context *ctx);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/iostream-ssl.h	Mon Jan 31 18:40:27 2011 +0200
@@ -0,0 +1,51 @@
+#ifndef IOSTREAM_SSL_H
+#define IOSTREAM_SSL_H
+
+struct ssl_iostream;
+struct ssl_iostream_context;
+
+struct ssl_iostream_settings {
+	const char *cipher_list;
+	const char *ca, *ca_dir;
+	const char *cert;
+	const char *key;
+	const char *key_password;
+	const char *cert_username_field;
+
+	bool verbose, verbose_invalid_cert;
+	bool verify_remote_cert;
+	bool require_valid_cert;
+};
+
+int io_stream_create_ssl(struct ssl_iostream_context *ctx, const char *source,
+			 const struct ssl_iostream_settings *set,
+			 struct istream **input, struct ostream **output,
+			 struct ssl_iostream **iostream_r);
+/* returned input and output streams must also be unreferenced */
+void ssl_iostream_unref(struct ssl_iostream **ssl_io);
+
+int ssl_iostream_handshake(struct ssl_iostream *ssl_io);
+void ssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io,
+					 int (*callback)(void *context),
+					 void *context);
+
+bool ssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io);
+bool ssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io);
+bool ssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io);
+const char *ssl_iostream_get_peer_name(struct ssl_iostream *ssl_io);
+const char *ssl_iostream_get_security_string(struct ssl_iostream *ssl_io);
+const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io);
+
+int ssl_iostream_generate_params(buffer_t *output);
+int ssl_iostream_context_import_params(struct ssl_iostream_context *ctx,
+				       const buffer_t *input);
+
+int ssl_iostream_context_init_client(const char *source,
+				     const struct ssl_iostream_settings *set,
+				     struct ssl_iostream_context **ctx_r);
+int ssl_iostream_context_init_server(const char *source,
+				     const struct ssl_iostream_settings *set,
+				     struct ssl_iostream_context **ctx_r);
+void ssl_iostream_context_deinit(struct ssl_iostream_context **ctx);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/istream-openssl.c	Mon Jan 31 18:40:27 2011 +0200
@@ -0,0 +1,74 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream-internal.h"
+#include "iostream-openssl.h"
+
+struct ssl_istream {
+	struct istream_private istream;
+	struct ssl_iostream *ssl_io;
+	bool seen_eof;
+};
+
+static void i_stream_ssl_destroy(struct iostream_private *stream)
+{
+	struct ssl_istream *sstream = (struct ssl_istream *)stream;
+
+	ssl_iostream_unref(&sstream->ssl_io);
+}
+
+static ssize_t i_stream_ssl_read(struct istream_private *stream)
+{
+	struct ssl_istream *sstream = (struct ssl_istream *)stream;
+	size_t size;
+	ssize_t ret;
+
+	if (sstream->seen_eof) {
+		stream->istream.eof = TRUE;
+		return -1;
+	}
+	if (!sstream->ssl_io->handshaked) {
+		if ((ret = ssl_iostream_handshake(sstream->ssl_io)) <= 0) {
+			if (ret < 0)
+				stream->istream.stream_errno = errno;
+			return ret;
+		}
+	}
+
+	if (!i_stream_get_buffer_space(stream, 1, &size))
+		return -2;
+
+	while ((ret = SSL_read(sstream->ssl_io->ssl,
+			       stream->w_buffer + stream->pos, size)) <= 0) {
+		ret = ssl_iostream_handle_error(sstream->ssl_io, ret,
+						"SSL_read");
+		if (ret <= 0) {
+			if (ret < 0) {
+				stream->istream.stream_errno = errno;
+				stream->istream.eof = TRUE;
+				sstream->seen_eof = TRUE;
+			}
+			return ret;
+		}
+		(void)ssl_iostream_bio_sync(sstream->ssl_io);
+	}
+	stream->pos += ret;
+	return ret;
+}
+
+struct istream *i_stream_create_ssl(struct ssl_iostream *ssl_io)
+{
+	struct ssl_istream *sstream;
+
+	ssl_io->refcount++;
+
+	sstream = i_new(struct ssl_istream, 1);
+	sstream->ssl_io = ssl_io;
+	sstream->istream.iostream.destroy = i_stream_ssl_destroy;
+	sstream->istream.max_buffer_size =
+		ssl_io->plain_input->real_stream->max_buffer_size;
+	sstream->istream.read = i_stream_ssl_read;
+
+	sstream->istream.istream.readable_fd = FALSE;
+	return i_stream_create(&sstream->istream, NULL, -1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/ostream-openssl.c	Mon Jan 31 18:40:27 2011 +0200
@@ -0,0 +1,185 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "ostream-internal.h"
+#include "iostream-openssl.h"
+
+struct ssl_ostream {
+	struct ostream_private ostream;
+	struct ssl_iostream *ssl_io;
+	buffer_t *buffer;
+};
+
+static void i_stream_ssl_destroy(struct iostream_private *stream)
+{
+	struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
+
+	sstream->ssl_io->ssl_output = NULL;
+	ssl_iostream_unref(&sstream->ssl_io);
+	i_free(sstream->buffer);
+}
+
+static size_t
+o_stream_ssl_buffer(struct ssl_ostream *sstream, const struct const_iovec *iov,
+		    unsigned int iov_count, size_t bytes_sent)
+{
+	size_t avail, skip_left, size;
+	unsigned int i;
+
+	if (sstream->buffer == NULL)
+		sstream->buffer = buffer_create_dynamic(default_pool, 4096);
+
+	skip_left = bytes_sent;
+	for (i = 0; i < iov_count; i++) {
+		if (skip_left < iov[i].iov_len)
+			break;
+		skip_left -= iov[i].iov_len;
+	}
+
+	avail = sstream->ostream.max_buffer_size > sstream->buffer->used ?
+		sstream->ostream.max_buffer_size - sstream->buffer->used : 0;
+	if (i < iov_count && skip_left > 0) {
+		size = I_MIN(iov[i].iov_len - skip_left, avail);
+		buffer_append(sstream->buffer,
+			      CONST_PTR_OFFSET(iov[i].iov_base, skip_left),
+			      size);
+		bytes_sent += size;
+		avail -= size;
+		if (size != iov[i].iov_len)
+			i = iov_count;
+	}
+	for (; i < iov_count; i++) {
+		size = I_MIN(iov[i].iov_len, avail);
+		buffer_append(sstream->buffer, iov[i].iov_base, size);
+		bytes_sent += size;
+		avail -= size;
+
+		if (size != iov[i].iov_len)
+			break;
+	}
+
+	sstream->ostream.ostream.offset += bytes_sent;
+	return bytes_sent;
+}
+
+static int o_stream_ssl_flush_buffer(struct ssl_ostream *sstream)
+{
+	size_t pos = 0;
+	int ret;
+
+	while (pos < sstream->buffer->used) {
+		ret = SSL_write(sstream->ssl_io->ssl,
+				CONST_PTR_OFFSET(sstream->buffer->data, pos),
+				sstream->buffer->used - pos);
+		if (ret <= 0) {
+			ret = ssl_iostream_handle_error(sstream->ssl_io, ret,
+							"SSL_write");
+			if (ret <= 0) {
+				if (ret < 0) {
+					sstream->ostream.ostream.stream_errno =
+						errno;
+				}
+				buffer_delete(sstream->buffer, 0, pos);
+				return ret;
+			}
+		} else {
+			pos += ret;
+			(void)ssl_iostream_bio_sync(sstream->ssl_io);
+		}
+	}
+	buffer_delete(sstream->buffer, 0, pos);
+	return 1;
+}
+
+static int o_stream_ssl_flush(struct ostream_private *stream)
+{
+	struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
+	int ret;
+
+	if (!sstream->ssl_io->handshaked) {
+		if ((ret = ssl_iostream_handshake(sstream->ssl_io)) <= 0) {
+			if (ret < 0)
+				stream->ostream.stream_errno = errno;
+			return ret;
+		}
+	}
+
+	if (sstream->buffer != NULL && sstream->buffer->used > 0) {
+		if ((ret = o_stream_ssl_flush_buffer(sstream)) <= 0)
+			return ret;
+	}
+	return 1;
+}
+
+static ssize_t
+o_stream_ssl_sendv(struct ostream_private *stream,
+		   const struct const_iovec *iov, unsigned int iov_count)
+{
+	struct ssl_ostream *sstream = (struct ssl_ostream *)stream;
+	unsigned int i;
+	size_t bytes_sent = 0;
+	size_t pos;
+	int ret = 0;
+
+	if (o_stream_flush(&stream->ostream) <= 0)
+		return o_stream_ssl_buffer(sstream, iov, iov_count, 0);
+
+	for (i = 0, pos = 0; i < iov_count; ) {
+		ret = SSL_write(sstream->ssl_io->ssl,
+				CONST_PTR_OFFSET(iov[i].iov_base, pos),
+				iov[i].iov_len - pos);
+		if (ret <= 0) {
+			ret = ssl_iostream_handle_error(sstream->ssl_io, ret,
+							"SSL_write");
+			if (ret <= 0) {
+				if (ret < 0)
+					stream->ostream.stream_errno = errno;
+				break;
+			}
+		} else {
+			bytes_sent += ret;
+			if ((size_t)ret < iov[i].iov_len)
+				pos += ret;
+			else {
+				i++;
+				pos = 0;
+			}
+			(void)ssl_iostream_bio_sync(sstream->ssl_io);
+		}
+	}
+	bytes_sent = o_stream_ssl_buffer(sstream, iov, iov_count, bytes_sent);
+	return bytes_sent != 0 ? (ssize_t)bytes_sent : ret;
+}
+
+static int plain_flush_callback(struct ssl_ostream *sstream)
+{
+	int ret;
+
+	if ((ret = o_stream_flush(sstream->ssl_io->plain_output)) < 0)
+		return 1;
+
+	if (ret > 0)
+		return o_stream_flush(&sstream->ostream.ostream);
+	return 1;
+}
+
+struct ostream *o_stream_create_ssl(struct ssl_iostream *ssl_io)
+{
+	struct ssl_ostream *sstream;
+
+	ssl_io->refcount++;
+
+	sstream = i_new(struct ssl_ostream, 1);
+	sstream->ssl_io = ssl_io;
+	sstream->ostream.max_buffer_size =
+		ssl_io->plain_output->real_stream->max_buffer_size;
+	sstream->ostream.iostream.destroy = i_stream_ssl_destroy;
+	sstream->ostream.sendv = o_stream_ssl_sendv;
+	sstream->ostream.flush = o_stream_ssl_flush;
+
+	o_stream_set_flush_callback(ssl_io->plain_output,
+				    plain_flush_callback, sstream);
+
+	return o_stream_create(&sstream->ostream);
+}