/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "istream-private.h"
#include "ostream-private.h"
#include "iostream-openssl.h"

#include <openssl/rand.h>
#include <openssl/err.h>

static void openssl_iostream_free(struct ssl_iostream *ssl_io);

void openssl_iostream_set_error(struct ssl_iostream *ssl_io, const char *str)
{
	char *new_str;

	/* i_debug() may sometimes be overriden, making it write to this very
	   same SSL stream, in which case the provided str may be invalidated
	   before it is even used. Therefore, we duplicate it immediately. */
	new_str = i_strdup(str);

	if (ssl_io->verbose) {
		/* This error should normally be logged by lib-ssl-iostream's
		   caller. But if verbose=TRUE, log it here as well to make
		   sure that the error is always logged. */
		i_debug("%sSSL error: %s", ssl_io->log_prefix, new_str);
	}
	i_free(ssl_io->last_error);
	ssl_io->last_error = new_str;
}

static void openssl_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) {
		switch (ret & 0xff) {
		case SSL_AD_CLOSE_NOTIFY:
			i_debug("%sSSL alert: %s",
				ssl_io->log_prefix,
				SSL_alert_desc_string_long(ret));
			break;
		default:
			i_debug("%sSSL alert: where=0x%x, ret=%d: %s %s",
				ssl_io->log_prefix, where, ret,
				SSL_alert_type_string_long(ret),
				SSL_alert_desc_string_long(ret));
			break;
		}
	} else if (ret == 0) {
		i_debug("%sSSL failed: where=0x%x: %s",
			ssl_io->log_prefix, where, SSL_state_string_long(ssl));
	} else {
		i_debug("%sSSL: where=0x%x, ret=%d: %s",
			ssl_io->log_prefix, where, ret,
			SSL_state_string_long(ssl));
	}
}

static int
openssl_iostream_use_certificate(struct ssl_iostream *ssl_io, const char *cert,
				 const char **error_r)
{
	BIO *in;
	X509 *x;
	int ret = 0;

	in = BIO_new_mem_buf(t_strdup_noconst(cert), strlen(cert));
	if (in == NULL) {
		*error_r = t_strdup_printf("BIO_new_mem_buf() failed: %s",
					   openssl_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) {
		*error_r = t_strdup_printf("Can't load ssl_cert: %s",
			openssl_iostream_use_certificate_error(cert, NULL));
		return -1;
	}
	return 0;
}

static int
openssl_iostream_use_key(struct ssl_iostream *ssl_io, const char *set_name,
			 const struct ssl_iostream_cert *set,
			 const char **error_r)
{
	EVP_PKEY *pkey;
	int ret = 0;

	if (openssl_iostream_load_key(set, set_name, &pkey, error_r) < 0)
		return -1;
	if (SSL_use_PrivateKey(ssl_io->ssl, pkey) != 1) {
		*error_r = t_strdup_printf(
			"Can't load SSL private key (%s setting): %s",
			set_name, openssl_iostream_key_load_error());
		ret = -1;
	}
	EVP_PKEY_free(pkey);
	return ret;
}

static int
openssl_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;
	char certname[1024];
	X509_NAME *subject;

	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;

	subject = X509_get_subject_name(X509_STORE_CTX_get_current_cert(ctx));
	if (subject == NULL ||
	    X509_NAME_oneline(subject, certname, sizeof(certname)) == NULL)
		certname[0] = '\0';
	else
		certname[sizeof(certname)-1] = '\0'; /* just in case.. */
	if (preverify_ok == 0) {
		openssl_iostream_set_error(ssl_io, t_strdup_printf(
			"Received invalid SSL certificate: %s: %s (check %s)",
			X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)), certname,
			ssl_io->ctx->client_ctx ?
				"ssl_client_ca_* settings?" :
				"ssl_ca setting?"));
		if (ssl_io->verbose_invalid_cert)
			i_info("%s", ssl_io->last_error);
	} else if (ssl_io->verbose) {
		i_info("Received valid SSL certificate: %s", certname);
	}
	if (preverify_ok == 0) {
		ssl_io->cert_broken = TRUE;
		if (!ssl_io->allow_invalid_cert) {
			ssl_io->handshake_failed = TRUE;
			return 0;
		}
	}
	return 1;
}

static int
openssl_iostream_set(struct ssl_iostream *ssl_io,
		     const struct ssl_iostream_settings *set,
		     const char **error_r)
{
	const struct ssl_iostream_settings *ctx_set = &ssl_io->ctx->set;
	int verify_flags;

	if (set->verbose)
		SSL_set_info_callback(ssl_io->ssl, openssl_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) == 0) {
			*error_r = t_strdup_printf(
				"Can't set cipher list to '%s': %s",
				set->cipher_list, openssl_iostream_error());
			return -1;
		}
	}
#ifdef HAVE_SSL_CTX_SET1_CURVES_LIST
	if (set->curve_list != NULL && strlen(set->curve_list) > 0 &&
		(ctx_set->curve_list == NULL || strcmp(ctx_set->curve_list, set->curve_list) != 0)) {
		if (SSL_set1_curves_list(ssl_io->ssl, set->curve_list) == 0) {
			*error_r = t_strdup_printf(
					"Failed to set curve list to '%s'",
					set->curve_list);
			return -1;
		}
	}
#endif
#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
        if (set->ciphersuites != NULL &&
	    strcmp(ctx_set->ciphersuites, set->ciphersuites) != 0) {
		if (SSL_set_ciphersuites(ssl_io->ssl, set->ciphersuites) == 0) {
			*error_r = t_strdup_printf(
				"Can't set ciphersuites to '%s': %s",
				set->ciphersuites, openssl_iostream_error());
			return -1;
		}
	}
#endif
	if (set->prefer_server_ciphers)
		SSL_set_options(ssl_io->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
	if (set->min_protocol != NULL) {
#if defined(HAVE_SSL_CLEAR_OPTIONS)
		SSL_clear_options(ssl_io->ssl, OPENSSL_ALL_PROTOCOL_OPTIONS);
#endif
		long opts;
		int min_protocol;
		if (openssl_min_protocol_to_options(set->min_protocol, &opts,
						    &min_protocol) < 0) {
			*error_r = t_strdup_printf(
					"Unknown ssl_min_protocol setting '%s'",
					set->min_protocol);
			return -1;
		}
#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
		SSL_set_min_proto_version(ssl_io->ssl, min_protocol);
#else
		SSL_set_options(ssl_io->ssl, opts);
#endif
	}

	if (set->cert.cert != NULL && strcmp(ctx_set->cert.cert, set->cert.cert) != 0) {
		if (openssl_iostream_use_certificate(ssl_io, set->cert.cert, error_r) < 0)
			return -1;
	}
	if (set->cert.key != NULL && strcmp(ctx_set->cert.key, set->cert.key) != 0) {
		if (openssl_iostream_use_key(ssl_io, "ssl_key", &set->cert, error_r) < 0)
			return -1;
	}
	if (set->alt_cert.cert != NULL && strcmp(ctx_set->alt_cert.cert, set->alt_cert.cert) != 0) {
		if (openssl_iostream_use_certificate(ssl_io, set->alt_cert.cert, error_r) < 0)
			return -1;
	}
	if (set->alt_cert.key != NULL && strcmp(ctx_set->alt_cert.key, set->alt_cert.key) != 0) {
		if (openssl_iostream_use_key(ssl_io, "ssl_alt_key", &set->alt_cert, error_r) < 0)
			return -1;
	}
	if (set->verify_remote_cert) {
		if (ssl_io->ctx->client_ctx)
			verify_flags = SSL_VERIFY_NONE;
		else
			verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
		SSL_set_verify(ssl_io->ssl, verify_flags,
			       openssl_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) {
			*error_r = t_strdup_printf(
				"Invalid cert_username_field: %s",
				set->cert_username_field);
			return -1;
		}
	} else {
		ssl_io->username_nid = ssl_io->ctx->username_nid;
	}

	ssl_io->verbose = set->verbose;
	ssl_io->verbose_invalid_cert = set->verbose_invalid_cert || set->verbose;
	ssl_io->allow_invalid_cert = set->allow_invalid_cert;
	return 0;
}

static int
openssl_iostream_create(struct ssl_iostream_context *ctx, const char *host,
			const struct ssl_iostream_settings *set,
			struct istream **input, struct ostream **output,
			struct ssl_iostream **iostream_r,
			const char **error_r)
{
	struct ssl_iostream *ssl_io;
	SSL *ssl;
	BIO *bio_int, *bio_ext;

	/* Don't allow an existing io_add_istream() to be use on the input.
	   It would seem to work, but it would also cause hangs. */
	i_assert(i_stream_get_root_io(*input)->real_stream->io == NULL);

	ssl = SSL_new(ctx->ssl_ctx);
	if (ssl == NULL) {
		*error_r = t_strdup_printf("SSL_new() failed: %s",
					   openssl_iostream_error());
		return -1;
	}

	/* BIO pairs use default buffer sizes (17 kB in OpenSSL 0.9.8e).
	   Each of the BIOs have one "write buffer". BIO_write() copies data
	   to them, while BIO_read() reads from the other BIO's write buffer
	   into the given buffer. The bio_int is used by OpenSSL and bio_ext
	   is used by this library. */
	if (BIO_new_bio_pair(&bio_int, 0, &bio_ext, 0) != 1) {
		*error_r = t_strdup_printf("BIO_new_bio_pair() failed: %s",
					   openssl_iostream_error());
		SSL_free(ssl);
		return -1;
	}

	ssl_io = i_new(struct ssl_iostream, 1);
	ssl_io->refcount = 1;
	ssl_io->ctx = ctx;
	ssl_iostream_context_ref(ssl_io->ctx);
	ssl_io->ssl = ssl;
	ssl_io->bio_ext = bio_ext;
	ssl_io->plain_input = *input;
	ssl_io->plain_output = *output;
	ssl_io->connected_host = i_strdup(host);
	ssl_io->log_prefix = host == NULL ? i_strdup("") :
		i_strdup_printf("%s: ", host);
	/* bio_int will be freed by SSL_free() */
	SSL_set_bio(ssl_io->ssl, bio_int, bio_int);
        SSL_set_ex_data(ssl_io->ssl, dovecot_ssl_extdata_index, ssl_io);
#ifdef HAVE_SSL_GET_SERVERNAME
	SSL_set_tlsext_host_name(ssl_io->ssl, host);
#endif

	if (openssl_iostream_set(ssl_io, set, error_r) < 0) {
		openssl_iostream_free(ssl_io);
		return -1;
	}

	o_stream_uncork(ssl_io->plain_output);

	*input = openssl_i_stream_create_ssl(ssl_io);
	ssl_io->ssl_input = *input;

	*output = openssl_o_stream_create_ssl(ssl_io);
	i_stream_set_name(*input, t_strconcat("SSL ",
		i_stream_get_name(ssl_io->plain_input), NULL));
	o_stream_set_name(*output, t_strconcat("SSL ",
		o_stream_get_name(ssl_io->plain_output), NULL));

	if (ssl_io->plain_output->real_stream->error_handling_disabled)
		o_stream_set_no_error_handling(*output, TRUE);

	ssl_io->ssl_output = *output;
	*iostream_r = ssl_io;
	return 0;
}

static void openssl_iostream_free(struct ssl_iostream *ssl_io)
{
	ssl_iostream_context_unref(&ssl_io->ctx);
	o_stream_unref(&ssl_io->plain_output);
	i_stream_unref(&ssl_io->plain_input);
	BIO_free(ssl_io->bio_ext);
	SSL_free(ssl_io->ssl);
	i_free(ssl_io->plain_stream_errstr);
	i_free(ssl_io->last_error);
	i_free(ssl_io->connected_host);
	i_free(ssl_io->sni_host);
	i_free(ssl_io->log_prefix);
	i_free(ssl_io);
}

static void openssl_iostream_unref(struct ssl_iostream *ssl_io)
{
	i_assert(ssl_io->refcount > 0);
	if (--ssl_io->refcount > 0)
		return;

	openssl_iostream_free(ssl_io);
}

void openssl_iostream_shutdown(struct ssl_iostream *ssl_io)
{
	if (ssl_io->destroyed)
		return;

	i_assert(ssl_io->ssl_input != NULL);
	i_assert(ssl_io->ssl_output != NULL);

	ssl_io->destroyed = TRUE;
	if (ssl_io->handshaked && SSL_shutdown(ssl_io->ssl) != 1) {
		/* if bidirectional shutdown fails we need to clear
		   the error queue */
		openssl_iostream_clear_errors();
	}
	if (ssl_io->handshaked) {
		(void)openssl_iostream_bio_sync(ssl_io,
			OPENSSL_IOSTREAM_SYNC_TYPE_WRITE);
	}
	(void)o_stream_flush(ssl_io->plain_output);
	/* close the plain i/o streams, because their fd may be closed soon,
	   but we may still keep this ssl-iostream referenced until later. */
	i_stream_close(ssl_io->plain_input);
	o_stream_close(ssl_io->plain_output);
}

static void openssl_iostream_destroy(struct ssl_iostream *ssl_io)
{
	openssl_iostream_shutdown(ssl_io);
	ssl_iostream_unref(&ssl_io);
}

static int openssl_iostream_bio_output_real(struct ssl_iostream *ssl_io)
{
	size_t bytes, max_bytes = 0;
	ssize_t sent;
	unsigned char buffer[IO_BLOCK_SIZE];
	int result = 0;
	int ret;

	o_stream_cork(ssl_io->plain_output);
	while ((bytes = BIO_ctrl_pending(ssl_io->bio_ext)) > 0) {
		/* bytes contains how many SSL encrypted bytes we should be
		   sending out */
		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);

		/* BIO_read() is guaranteed to return all the bytes that
		   BIO_ctrl_pending() returned */
		ret = BIO_read(ssl_io->bio_ext, buffer, bytes);
		i_assert(ret == (int)bytes);

		/* we limited number of read bytes to plain_output's
		   available size. this send() is guaranteed to either
		   fully succeed or completely fail due to some error. */
		sent = o_stream_send(ssl_io->plain_output, buffer, bytes);
		if (sent < 0) {
			o_stream_uncork(ssl_io->plain_output);
			return -1;
		}
		i_assert(sent == (ssize_t)bytes);
		result = 1;
	}

	ret = o_stream_uncork_flush(ssl_io->plain_output);
	if (ret < 0)
		return -1;
	if (ret == 0 || (bytes > 0 && max_bytes == 0))
		o_stream_set_flush_pending(ssl_io->plain_output, TRUE);

	return result;
}

static int openssl_iostream_bio_output(struct ssl_iostream *ssl_io)
{
	int ret;

	ret = openssl_iostream_bio_output_real(ssl_io);
	if (ret < 0) {
		i_assert(ssl_io->plain_output->stream_errno != 0);
		i_free(ssl_io->plain_stream_errstr);
		ssl_io->plain_stream_errstr =
			i_strdup(o_stream_get_error(ssl_io->plain_output));
		ssl_io->plain_stream_errno =
			ssl_io->plain_output->stream_errno;
		ssl_io->closed = TRUE;
	}
	return ret;
}

static ssize_t
openssl_iostream_read_more(struct ssl_iostream *ssl_io,
			   enum openssl_iostream_sync_type type, size_t wanted,
			   const unsigned char **data_r, size_t *size_r)
{
	*data_r = i_stream_get_data(ssl_io->plain_input, size_r);
	if (*size_r > 0)
		return 0;

	if (type == OPENSSL_IOSTREAM_SYNC_TYPE_CONTINUE_READ) {
		/* only the first i_stream_read() call attempts to read more
		   input. the following reads will just process the buffered
		   data. */
		return 0;
	}

	if (i_stream_read_limited(ssl_io->plain_input, data_r, size_r,
				  wanted) < 0)
		return -1;
	return 0;
}

static int
openssl_iostream_bio_input(struct ssl_iostream *ssl_io,
			   enum openssl_iostream_sync_type type)
{
	const unsigned char *data;
	size_t bytes, size;
	int ret;
	bool bytes_read = FALSE;

	while ((bytes = BIO_ctrl_get_write_guarantee(ssl_io->bio_ext)) > 0) {
		/* bytes contains how many bytes we can write to bio_ext */
		ret = openssl_iostream_read_more(ssl_io, type, bytes,
						 &data, &size);
		if (ret == -1 && size == 0 && !bytes_read) {
			if (ssl_io->plain_input->stream_errno != 0) {
				i_free(ssl_io->plain_stream_errstr);
				ssl_io->plain_stream_errstr =
					i_strdup(i_stream_get_error(ssl_io->plain_input));
				ssl_io->plain_stream_errno =
					ssl_io->plain_input->stream_errno;
			}
			ssl_io->closed = TRUE;
			return -1;
		}
		if (size == 0) {
			/* wait for more input */
			break;
		}
		if (size > bytes)
			size = bytes;

		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;
	}
	if (bytes == 0 && !bytes_read && ssl_io->want_read) {
		/* shouldn't happen */
		i_error("SSL BIO buffer size too small");
		i_free(ssl_io->plain_stream_errstr);
		ssl_io->plain_stream_errstr =
			i_strdup("SSL BIO buffer size too small");
		ssl_io->plain_stream_errno = EINVAL;
		ssl_io->closed = TRUE;
		return -1;
	}
	if (bytes_read) {
		if (ssl_io->ostream_flush_waiting_input) {
			ssl_io->ostream_flush_waiting_input = FALSE;
			o_stream_set_flush_pending(ssl_io->plain_output, TRUE);
		}
	}
	if (bytes_read || i_stream_get_data_size(ssl_io->plain_input) > 0) {
		if (i_stream_get_data_size(ssl_io->plain_input) > 0 ||
		    (type != OPENSSL_IOSTREAM_SYNC_TYPE_FIRST_READ &&
		     type != OPENSSL_IOSTREAM_SYNC_TYPE_CONTINUE_READ))
			i_stream_set_input_pending(ssl_io->ssl_input, TRUE);
		ssl_io->want_read = FALSE;
	}
	return (bytes_read ? 1 : 0);
}

int openssl_iostream_bio_sync(struct ssl_iostream *ssl_io,
			      enum openssl_iostream_sync_type type)
{
	int ret;

	i_assert(type != OPENSSL_IOSTREAM_SYNC_TYPE_NONE);

	ret = openssl_iostream_bio_output(ssl_io);
	if (ret >= 0 && openssl_iostream_bio_input(ssl_io, type) > 0)
		ret = 1;
	return ret;
}

static void openssl_iostream_closed(struct ssl_iostream *ssl_io)
{
	if (ssl_io->plain_stream_errno != 0) {
		i_assert(ssl_io->plain_stream_errstr != NULL);
		openssl_iostream_set_error(ssl_io, ssl_io->plain_stream_errstr);
		errno = ssl_io->plain_stream_errno;
	} else {
		openssl_iostream_set_error(ssl_io, "Connection closed");
		errno = EPIPE;
	}
}

int openssl_iostream_handle_error(struct ssl_iostream *ssl_io, int ret,
				  enum openssl_iostream_sync_type type,
				  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 (type != OPENSSL_IOSTREAM_SYNC_TYPE_NONE &&
		    openssl_iostream_bio_sync(ssl_io, type) == 0) {
			if (type != OPENSSL_IOSTREAM_SYNC_TYPE_WRITE)
				i_panic("SSL ostream buffer size not unlimited");
			return 0;
		}
		if (ssl_io->closed) {
			openssl_iostream_closed(ssl_io);
			return -1;
		}
		if (type == OPENSSL_IOSTREAM_SYNC_TYPE_NONE)
			return 0;
		return 1;
	case SSL_ERROR_WANT_READ:
		ssl_io->want_read = TRUE;
		if (type != OPENSSL_IOSTREAM_SYNC_TYPE_NONE)
			(void)openssl_iostream_bio_sync(ssl_io, type);
		if (ssl_io->closed) {
			openssl_iostream_closed(ssl_io);
			return -1;
		}
		if (type == OPENSSL_IOSTREAM_SYNC_TYPE_NONE)
			return 0;
		return ssl_io->want_read ? 0 : 1;
	case SSL_ERROR_SYSCALL:
		/* eat up the error queue */
		if (ERR_peek_error() != 0) {
			errstr = openssl_iostream_error();
			errno = EINVAL;
		} else if (ret == 0) {
			/* EOF. */
			errno = EPIPE;
			errstr = "Disconnected";
			break;
		} else if (errno != 0) {
			errstr = strerror(errno);
		} else {
			/* Seen this at least with v1.1.0l SSL_accept() */
			errstr = "OpenSSL BUG: errno=0";
			errno = EINVAL;
		}
		errstr = t_strdup_printf("%s syscall failed: %s",
					 func_name, errstr);
		break;
	case SSL_ERROR_ZERO_RETURN:
		/* clean connection closing */
		errno = EPIPE;
		if (ssl_io->handshaked)
			i_free_and_null(ssl_io->last_error);
		else if (ssl_io->last_error == NULL) {
			errstr = "SSL connection closed during handshake";
			break;
		}
		return -1;
	case SSL_ERROR_SSL:
		errstr = t_strdup_printf("%s failed: %s",
					 func_name, openssl_iostream_error());
		errno = EINVAL;
		break;
	default:
		errstr = t_strdup_printf("%s failed: unknown failure %d (%s)",
					 func_name, err,
					 openssl_iostream_error());
		errno = EINVAL;
		break;
	}

	openssl_iostream_set_error(ssl_io, errstr);
	return -1;
}

static bool
openssl_iostream_cert_match_name(struct ssl_iostream *ssl_io,
				 const char *verify_name, const char **reason_r)
{
	if (!ssl_iostream_has_valid_client_cert(ssl_io)) {
		*reason_r = "Invalid certificate";
		return FALSE;
	}

	return openssl_cert_match_name(ssl_io->ssl, verify_name, reason_r);
}

static int openssl_iostream_handshake(struct ssl_iostream *ssl_io)
{
	const char *reason, *error = NULL;
	int ret;

	i_assert(!ssl_io->handshaked);

	/* we are being destroyed, so do not do any more handshaking */
	if (ssl_io->destroyed)
		return 0;

	if (ssl_io->ctx->client_ctx) {
		while ((ret = SSL_connect(ssl_io->ssl)) <= 0) {
			ret = openssl_iostream_handle_error(ssl_io, ret,
				OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE, "SSL_connect()");
			if (ret <= 0)
				return ret;
		}
	} else {
		while ((ret = SSL_accept(ssl_io->ssl)) <= 0) {
			ret = openssl_iostream_handle_error(ssl_io, ret,
				OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE, "SSL_accept()");
			if (ret <= 0)
				return ret;
		}
	}
	/* handshake finished */
	(void)openssl_iostream_bio_sync(ssl_io, OPENSSL_IOSTREAM_SYNC_TYPE_HANDSHAKE);

	if (ssl_io->handshake_callback != NULL) {
		if (ssl_io->handshake_callback(&error, ssl_io->handshake_context) < 0) {
			i_assert(error != NULL);
			openssl_iostream_set_error(ssl_io, error);
			ssl_io->handshake_failed = TRUE;
		}
       } else if (ssl_io->connected_host != NULL && !ssl_io->handshake_failed &&
		  !ssl_io->allow_invalid_cert) {
		if (ssl_iostream_check_cert_validity(ssl_io, ssl_io->connected_host, &reason) < 0) {
			openssl_iostream_set_error(ssl_io, reason);
			ssl_io->handshake_failed = TRUE;
		}
	}
	if (ssl_io->handshake_failed) {
		i_stream_close(ssl_io->plain_input);
		o_stream_close(ssl_io->plain_output);
		errno = EINVAL;
		return -1;
	}
	i_free_and_null(ssl_io->last_error);
	ssl_io->handshaked = TRUE;

	if (ssl_io->ssl_output != NULL)
		(void)o_stream_flush(ssl_io->ssl_output);
	return 1;
}

static void
openssl_iostream_set_handshake_callback(struct ssl_iostream *ssl_io,
					ssl_iostream_handshake_callback_t *callback,
					void *context)
{
	ssl_io->handshake_callback = callback;
	ssl_io->handshake_context = context;
}

static void
openssl_iostream_set_sni_callback(struct ssl_iostream *ssl_io,
				  ssl_iostream_sni_callback_t *callback,
				  void *context)
{
	ssl_io->sni_callback = callback;
	ssl_io->sni_context = context;
}

static void
openssl_iostream_change_context(struct ssl_iostream *ssl_io,
				struct ssl_iostream_context *ctx)
{
	if (ctx != ssl_io->ctx) {
		SSL_set_SSL_CTX(ssl_io->ssl, ctx->ssl_ctx);
		ssl_iostream_context_ref(ctx);
		ssl_iostream_context_unref(&ssl_io->ctx);
		ssl_io->ctx = ctx;
	}
}

static void openssl_iostream_set_log_prefix(struct ssl_iostream *ssl_io,
					    const char *prefix)
{
	i_free(ssl_io->log_prefix);
	ssl_io->log_prefix = i_strdup(prefix);
}

static bool openssl_iostream_is_handshaked(const struct ssl_iostream *ssl_io)
{
	return ssl_io->handshaked;
}

static bool
openssl_iostream_has_handshake_failed(const struct ssl_iostream *ssl_io)
{
	return ssl_io->handshake_failed;
}

static bool
openssl_iostream_has_valid_client_cert(const struct ssl_iostream *ssl_io)
{
	return ssl_io->cert_received && !ssl_io->cert_broken;
}

static bool
openssl_iostream_has_broken_client_cert(struct ssl_iostream *ssl_io)
{
	return ssl_io->cert_received && ssl_io->cert_broken;
}

static const char *
openssl_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);
	i_assert(x509 != NULL);

	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_malloc0(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;
}

static const char *openssl_iostream_get_server_name(struct ssl_iostream *ssl_io)
{
	return ssl_io->sni_host;
}

static const char *
openssl_iostream_get_compression(struct ssl_iostream *ssl_io)
{
#if defined(HAVE_SSL_COMPRESSION) && !defined(OPENSSL_NO_COMP)
	const COMP_METHOD *comp;

	comp = SSL_get_current_compression(ssl_io->ssl);
	return comp == NULL ? NULL : SSL_COMP_get_name(comp);
#else
	return NULL;
#endif
}

static const char *
openssl_iostream_get_security_string(struct ssl_iostream *ssl_io)
{
	const SSL_CIPHER *cipher;
#if defined(HAVE_SSL_COMPRESSION) && !defined(OPENSSL_NO_COMP)
	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);
#if defined(HAVE_SSL_COMPRESSION) && !defined(OPENSSL_NO_COMP)
	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)%s",
			       SSL_get_version(ssl_io->ssl),
			       SSL_CIPHER_get_name(cipher),
			       bits, alg_bits, comp_str);
}

static const char *
openssl_iostream_get_last_error(struct ssl_iostream *ssl_io)
{
	return ssl_io->last_error;
}

static const char *
openssl_iostream_get_cipher(struct ssl_iostream *ssl_io, unsigned int *bits_r)
{
	if (!ssl_io->handshaked)
		return NULL;

	const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl_io->ssl);
	*bits_r = SSL_CIPHER_get_bits(cipher, NULL);
	return SSL_CIPHER_get_name(cipher);
}

static const char *
openssl_iostream_get_pfs(struct ssl_iostream *ssl_io)
{
	if (!ssl_io->handshaked)
		return NULL;

	const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl_io->ssl);
#if defined(HAVE_SSL_CIPHER_get_kx_nid)
	int nid = SSL_CIPHER_get_kx_nid(cipher);
	return OBJ_nid2sn(nid);
#else
	char buf[128];
	const char *desc, *ptr;
	if ((desc = SSL_CIPHER_description(cipher, buf, sizeof(buf)))==NULL ||
	    (ptr = strstr(desc, "Kx=")) == NULL)
		return "";
	return t_strcut(ptr+3, ' ');
#endif
}

static const char *
openssl_iostream_get_protocol_name(struct ssl_iostream *ssl_io)
{
	if (!ssl_io->handshaked)
		return NULL;
	return SSL_get_version(ssl_io->ssl);
}


static const struct iostream_ssl_vfuncs ssl_vfuncs = {
	.global_init = openssl_iostream_global_init,
	.context_init_client = openssl_iostream_context_init_client,
	.context_init_server = openssl_iostream_context_init_server,
	.context_ref = openssl_iostream_context_ref,
	.context_unref = openssl_iostream_context_unref,

	.create = openssl_iostream_create,
	.unref = openssl_iostream_unref,
	.destroy = openssl_iostream_destroy,

	.handshake = openssl_iostream_handshake,
	.set_handshake_callback = openssl_iostream_set_handshake_callback,
	.set_sni_callback = openssl_iostream_set_sni_callback,
	.change_context = openssl_iostream_change_context,

	.set_log_prefix = openssl_iostream_set_log_prefix,
	.is_handshaked = openssl_iostream_is_handshaked,
	.has_handshake_failed = openssl_iostream_has_handshake_failed,
	.has_valid_client_cert = openssl_iostream_has_valid_client_cert,
	.has_broken_client_cert = openssl_iostream_has_broken_client_cert,
	.cert_match_name = openssl_iostream_cert_match_name,
	.get_peer_name = openssl_iostream_get_peer_name,
	.get_server_name = openssl_iostream_get_server_name,
	.get_compression = openssl_iostream_get_compression,
	.get_security_string = openssl_iostream_get_security_string,
	.get_last_error = openssl_iostream_get_last_error,
	.get_cipher = openssl_iostream_get_cipher,
	.get_pfs = openssl_iostream_get_pfs,
	.get_protocol_name = openssl_iostream_get_protocol_name,
};

void ssl_iostream_openssl_init(void)
{
	unsigned char buf;
	if (RAND_bytes(&buf, 1) < 1)
		i_fatal("OpenSSL RNG failed to initialize");
	iostream_ssl_module_init(&ssl_vfuncs);
}

void ssl_iostream_openssl_deinit(void)
{
	openssl_iostream_global_deinit();
}
