diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-ssl-iostream/iostream-openssl-context.c | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/lib-ssl-iostream/iostream-openssl-context.c | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/src/lib-ssl-iostream/iostream-openssl-context.c b/src/lib-ssl-iostream/iostream-openssl-context.c new file mode 100644 index 0000000..fe9b059 --- /dev/null +++ b/src/lib-ssl-iostream/iostream-openssl-context.c @@ -0,0 +1,755 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "safe-memset.h" +#include "iostream-openssl.h" +#include "dovecot-openssl-common.h" + +#include <openssl/crypto.h> +#include <openssl/rsa.h> +#include <openssl/dh.h> +#include <openssl/bn.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> +#include <openssl/err.h> + +#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x10000000L +# define HAVE_ECDH +#endif + +struct ssl_iostream_password_context { + const char *password; + const char *error; +}; + +static bool ssl_global_initialized = FALSE; +int dovecot_ssl_extdata_index; + +static RSA *ssl_gen_rsa_key(SSL *ssl ATTR_UNUSED, + int is_export ATTR_UNUSED, int keylength) +{ +#ifdef HAVE_RSA_GENERATE_KEY_EX + BIGNUM *bn = BN_new(); + RSA *rsa = RSA_new(); + + if (bn != NULL && BN_set_word(bn, RSA_F4) != 0 && + RSA_generate_key_ex(rsa, keylength, bn, NULL) != 0) { + BN_free(bn); + return rsa; + } + + if (bn != NULL) + BN_free(bn); + if (rsa != NULL) + RSA_free(rsa); + return NULL; +#else + return RSA_generate_key(keylength, RSA_F4, NULL, NULL); +#endif +} + +static DH *ssl_tmp_dh_callback(SSL *ssl ATTR_UNUSED, + int is_export ATTR_UNUSED, int keylength ATTR_UNUSED) +{ + i_error("Diffie-Hellman key exchange requested, " + "but no DH parameters provided. Set ssl_dh=</path/to/dh.pem"); + return NULL; +} + +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) { + ctx->error = "SSL private key file is password protected, " + "but password isn't given"; + return 0; + } + + if (i_strocpy(buf, ctx->password, size) < 0) { + ctx->error = "SSL private key password is too long"; + return 0; + } + return strlen(buf); +} + +int openssl_iostream_load_key(const struct ssl_iostream_cert *set, + const char *set_name, + EVP_PKEY **pkey_r, const char **error_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) { + *error_r = t_strdup_printf("BIO_new_mem_buf() failed: %s", + openssl_iostream_error()); + safe_memset(key, 0, strlen(key)); + return -1; + } + + ctx.password = set->key_password; + ctx.error = NULL; + + pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback, &ctx); + if (pkey == NULL && ctx.error == NULL) { + ctx.error = t_strdup_printf( + "Couldn't parse private SSL key (%s setting)%s: %s", + set_name, + ctx.password != NULL ? + " (maybe ssl_key_password is wrong?)" : + "", + openssl_iostream_error()); + } + BIO_free(bio); + + safe_memset(key, 0, strlen(key)); + *pkey_r = pkey; + *error_r = ctx.error; + return pkey == NULL ? -1 : 0; +} + +static +int openssl_iostream_load_dh(const struct ssl_iostream_settings *set, + DH **dh_r, const char **error_r) +{ + DH *dh; + BIO *bio; + char *dhvalue; + + dhvalue = t_strdup_noconst(set->dh); + bio = BIO_new_mem_buf(dhvalue, strlen(dhvalue)); + + if (bio == NULL) { + *error_r = t_strdup_printf("BIO_new_mem_buf() failed: %s", + openssl_iostream_error()); + return -1; + } + + dh = NULL; + dh = PEM_read_bio_DHparams(bio, &dh, NULL, NULL); + + if (dh == NULL) { + *error_r = t_strdup_printf("Couldn't parse DH parameters: %s", + openssl_iostream_error()); + } + BIO_free(bio); + *dh_r = dh; + return dh == NULL ? -1 : 0; +} + +static int +ssl_iostream_ctx_use_key(struct ssl_iostream_context *ctx, 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_CTX_use_PrivateKey(ctx->ssl_ctx, pkey) == 0) { + *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 +ssl_iostream_ctx_use_dh(struct ssl_iostream_context *ctx, + const struct ssl_iostream_settings *set, + const char **error_r) +{ + DH *dh; + int ret = 0; + if (*set->dh == '\0') { + return 0; + } + if (openssl_iostream_load_dh(set, &dh, error_r) < 0) + return -1; + if (SSL_CTX_set_tmp_dh(ctx->ssl_ctx, dh) == 0) { + *error_r = t_strdup_printf( + "Can't load DH parameters (ssl_dh setting): %s", + openssl_iostream_key_load_error()); + ret = -1; + } + DH_free(dh); + return ret; +} + +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) { +#ifdef HAVE_SSL_CTX_SET_CURRENT_CERT + SSL_CTX_select_current_cert(ctx, x); +#endif + /* 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) { +#ifdef HAVE_SSL_CTX_ADD0_CHAIN_CERT + r = SSL_CTX_add0_chain_cert(ctx, ca); +#else + r = SSL_CTX_add_extra_chain_cert(ctx, ca); +#endif + 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); +#ifdef HAVE_SSL_CTX_SET_CURRENT_CERT + SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST); +#endif + 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 != NULL) { + 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 != NULL) + X509_STORE_add_crl(store, itmp->crl); + } + sk_X509_INFO_pop_free(inf, X509_INFO_free); + *xnames_r = xnames; + return 0; +} + +static int +load_ca_locations(struct ssl_iostream_context *ctx, const char *ca_file, + const char *ca_dir, const char **error_r) +{ + if (SSL_CTX_load_verify_locations(ctx->ssl_ctx, ca_file, ca_dir) != 0) + return 0; + + if (ca_dir == NULL) { + *error_r = t_strdup_printf( + "Can't load CA certs from %s " + "(ssl_client_ca_file setting): %s", + ca_file, openssl_iostream_error()); + } else if (ca_file == NULL) { + *error_r = t_strdup_printf( + "Can't load CA certs from directory %s " + "(ssl_client_ca_dir setting): %s", + ca_dir, openssl_iostream_error()); + } else { + *error_r = t_strdup_printf( + "Can't load CA certs from file %s and directory %s " + "(ssl_client_ca_* settings): %s", + ca_file, ca_dir, openssl_iostream_error()); + } + return -1; +} + +static void +ssl_iostream_ctx_verify_remote_cert(struct ssl_iostream_context *ctx, + STACK_OF(X509_NAME) *ca_names) +{ +#if OPENSSL_VERSION_NUMBER >= 0x00907000L + if (!ctx->set.skip_crl_check) { + 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); +} + +#ifdef HAVE_SSL_GET_SERVERNAME +static int ssl_servername_callback(SSL *ssl, int *al ATTR_UNUSED, + void *context ATTR_UNUSED) +{ + struct ssl_iostream *ssl_io; + const char *host, *error; + + ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index); + host = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (SSL_get_servername_type(ssl) != -1) { + i_free(ssl_io->sni_host); + ssl_io->sni_host = i_strdup(host); + } else if (ssl_io->verbose) { + i_debug("SSL_get_servername() failed"); + } + + if (ssl_io->sni_callback != NULL) { + if (ssl_io->sni_callback(ssl_io->sni_host, &error, + ssl_io->sni_context) < 0) { + openssl_iostream_set_error(ssl_io, error); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + return SSL_TLSEXT_ERR_OK; +} +#endif + +static int +ssl_iostream_context_load_ca(struct ssl_iostream_context *ctx, + const struct ssl_iostream_settings *set, + const char **error_r) +{ + X509_STORE *store; + STACK_OF(X509_NAME) *xnames = NULL; + const char *ca_file, *ca_dir; + bool have_ca = FALSE; + + if (set->ca != NULL) { + store = SSL_CTX_get_cert_store(ctx->ssl_ctx); + if (load_ca(store, set->ca, &xnames) < 0) { + *error_r = t_strdup_printf("Couldn't parse ssl_ca: %s", + openssl_iostream_error()); + return -1; + } + ssl_iostream_ctx_verify_remote_cert(ctx, xnames); + have_ca = TRUE; + } + ca_file = set->ca_file == NULL || *set->ca_file == '\0' ? + NULL : set->ca_file; + ca_dir = set->ca_dir == NULL || *set->ca_dir == '\0' ? + NULL : set->ca_dir; + if (ca_file != NULL || ca_dir != NULL) { + if (load_ca_locations(ctx, ca_file, ca_dir, error_r) < 0) + return -1; + have_ca = TRUE; + } + if (!have_ca && ctx->client_ctx) { + if (SSL_CTX_set_default_verify_paths(ctx->ssl_ctx) != 1) { + *error_r = t_strdup_printf( + "Can't load default CA locations: %s (ssl_client_ca_* settings missing)", + openssl_iostream_error()); + return -1; + } + } else if (!have_ca) { + *error_r = "Can't verify remote client certs without CA (ssl_ca setting)"; + return -1; + } + return 0; +} + +static int +ssl_iostream_context_set(struct ssl_iostream_context *ctx, + const struct ssl_iostream_settings *set, + const char **error_r) +{ + ssl_iostream_settings_init_from(ctx->pool, &ctx->set, set); + if (set->cipher_list != NULL && + SSL_CTX_set_cipher_list(ctx->ssl_ctx, set->cipher_list) == 0) { + *error_r = t_strdup_printf( + "Can't set cipher list to '%s' (ssl_cipher_list setting): %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 && + SSL_CTX_set1_curves_list(ctx->ssl_ctx, set->curve_list) == 0) { + *error_r = t_strdup_printf( + "Can't set curve list to '%s' (ssl_curve_list setting)", + set->curve_list); + return -1; + } +#endif +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + if (set->ciphersuites != NULL && + SSL_CTX_set_ciphersuites(ctx->ssl_ctx, 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_CTX_set_options(ctx->ssl_ctx, + SSL_OP_CIPHER_SERVER_PREFERENCE); + } + if (ctx->set.min_protocol != NULL) { + long opts; + int min_protocol; + if (openssl_min_protocol_to_options(ctx->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_CTX_set_min_proto_version(ctx->ssl_ctx, min_protocol); +#else + SSL_CTX_set_options(ctx->ssl_ctx, opts); +#endif + } + + if (set->cert.cert != NULL && + ssl_ctx_use_certificate_chain(ctx->ssl_ctx, set->cert.cert) == 0) { + *error_r = t_strdup_printf( + "Can't load SSL certificate (ssl_cert setting): %s", + openssl_iostream_use_certificate_error(set->cert.cert, NULL)); + return -1; + } + if (set->cert.key != NULL) { + if (ssl_iostream_ctx_use_key(ctx, "ssl_key", &set->cert, error_r) < 0) + return -1; + } + if (set->alt_cert.cert != NULL && + ssl_ctx_use_certificate_chain(ctx->ssl_ctx, set->alt_cert.cert) == 0) { + *error_r = t_strdup_printf( + "Can't load alternative SSL certificate " + "(ssl_alt_cert setting): %s", + openssl_iostream_use_certificate_error(set->alt_cert.cert, NULL)); + return -1; + } + if (set->alt_cert.key != NULL) { + if (ssl_iostream_ctx_use_key(ctx, "ssl_alt_key", &set->alt_cert, error_r) < 0) + return -1; + } + + if (set->dh != NULL) { + if (ssl_iostream_ctx_use_dh(ctx, set, error_r) < 0) + return -1; + } + + /* set trusted CA certs */ + if (set->verify_remote_cert) { + if (ssl_iostream_context_load_ca(ctx, set, error_r) < 0) + return -1; + } + + if (set->cert_username_field != NULL) { + ctx->username_nid = OBJ_txt2nid(set->cert_username_field); + if (ctx->username_nid == NID_undef) { + *error_r = t_strdup_printf( + "Invalid cert_username_field: %s", + set->cert_username_field); + return -1; + } + } +#ifdef HAVE_SSL_GET_SERVERNAME + if (!ctx->client_ctx) { + if (SSL_CTX_set_tlsext_servername_callback(ctx->ssl_ctx, + ssl_servername_callback) != 1) { + if (set->verbose) + i_debug("OpenSSL library doesn't support SNI"); + } + } +#endif + return 0; +} + +#if defined(HAVE_ECDH) && !defined(SSL_CTX_set_ecdh_auto) +static int +ssl_proxy_ctx_get_pkey_ec_curve_name(const struct ssl_iostream_settings *set, + int *nid_r, const char **error_r) +{ + int nid = 0; + EVP_PKEY *pkey; + EC_KEY *eckey; + const EC_GROUP *ecgrp; + + if (set->cert.key != NULL) { + if (openssl_iostream_load_key(&set->cert, "ssl_key", &pkey, error_r) < 0) + return -1; + + if ((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); + } + if (nid == 0 && set->alt_cert.key != NULL) { + if (openssl_iostream_load_key(&set->alt_cert, "ssl_alt_key", &pkey, error_r) < 0) + return -1; + + if ((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); + } + + *nid_r = nid; + return 0; +} +#endif + +static int +ssl_proxy_ctx_set_crypto_params(SSL_CTX *ssl_ctx, + const struct ssl_iostream_settings *set, + const char **error_r ATTR_UNUSED) +{ +#if defined(HAVE_ECDH) && !defined(SSL_CTX_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); + if (set->dh == NULL || *set->dh == '\0') + SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback); +#ifdef HAVE_ECDH + /* In the non-recommended situation where ECDH cipher suites are being + used instead of ECDHE, do not reuse the same ECDH key pair for + different sessions. This option improves forward secrecy. */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_SINGLE_ECDH_USE); +#ifdef SSL_CTX_set_ecdh_auto + /* OpenSSL >= 1.0.2 automatically handles ECDH temporary key parameter + selection. The return value of this function changes is changed to + bool in OpenSSL 1.1 and is int in OpenSSL 1.0.2+ */ + if ((long)(SSL_CTX_set_ecdh_auto(ssl_ctx, 1)) == 0) { + /* shouldn't happen */ + *error_r = t_strdup_printf("SSL_CTX_set_ecdh_auto() failed: %s", + openssl_iostream_error()); + return -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. */ + if (ssl_proxy_ctx_get_pkey_ec_curve_name(set, &nid, error_r) < 0) + return -1; + 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) { + 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 +#ifdef SSL_OP_SINGLE_DH_USE + /* Improves forward secrecy with DH parameters, especially if the + parameters used aren't strong primes. See OpenSSL manual. */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_SINGLE_DH_USE); +#endif + return 0; +} + +static int +ssl_iostream_context_init_common(struct ssl_iostream_context *ctx, + const struct ssl_iostream_settings *set, + const char **error_r) +{ + unsigned long ssl_ops = SSL_OP_NO_SSLv2 | + (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); + + ctx->pool = pool_alloconly_create("ssl iostream context", 4096); + + /* enable all SSL workarounds, except empty fragments as it + makes SSL more vulnerable against attacks */ +#ifdef SSL_OP_NO_COMPRESSION + if (!set->compression) + ssl_ops |= SSL_OP_NO_COMPRESSION; +#endif +#ifdef SSL_OP_NO_TICKET + if (!set->tickets) + ssl_ops |= SSL_OP_NO_TICKET; +#endif + SSL_CTX_set_options(ctx->ssl_ctx, ssl_ops); +#ifdef SSL_MODE_RELEASE_BUFFERS + SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_RELEASE_BUFFERS); +#endif +#ifdef SSL_MODE_ENABLE_PARTIAL_WRITE + SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); +#endif +#ifdef SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER + SSL_CTX_set_mode(ctx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +#endif + if (ssl_proxy_ctx_set_crypto_params(ctx->ssl_ctx, set, error_r) < 0) + return -1; + + return ssl_iostream_context_set(ctx, set, error_r); +} + +int openssl_iostream_context_init_client(const struct ssl_iostream_settings *set, + struct ssl_iostream_context **ctx_r, + const char **error_r) +{ + struct ssl_iostream_context *ctx; + SSL_CTX *ssl_ctx; + + if ((ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) { + *error_r = t_strdup_printf("SSL_CTX_new() failed: %s", + openssl_iostream_error()); + return -1; + } + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + + ctx = i_new(struct ssl_iostream_context, 1); + ctx->refcount = 1; + ctx->ssl_ctx = ssl_ctx; + ctx->client_ctx = TRUE; + if (ssl_iostream_context_init_common(ctx, set, error_r) < 0) { + ssl_iostream_context_unref(&ctx); + return -1; + } + *ctx_r = ctx; + return 0; +} + +int openssl_iostream_context_init_server(const struct ssl_iostream_settings *set, + struct ssl_iostream_context **ctx_r, + const char **error_r) +{ + struct ssl_iostream_context *ctx; + SSL_CTX *ssl_ctx; + + if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { + *error_r = t_strdup_printf("SSL_CTX_new() failed: %s", + openssl_iostream_error()); + return -1; + } + + ctx = i_new(struct ssl_iostream_context, 1); + ctx->refcount = 1; + ctx->ssl_ctx = ssl_ctx; + if (ssl_iostream_context_init_common(ctx, set, error_r) < 0) { + ssl_iostream_context_unref(&ctx); + return -1; + } + *ctx_r = ctx; + return 0; +} + +void openssl_iostream_context_ref(struct ssl_iostream_context *ctx) +{ + i_assert(ctx->refcount > 0); + ctx->refcount++; +} + +void openssl_iostream_context_unref(struct ssl_iostream_context *ctx) +{ + i_assert(ctx->refcount > 0); + if (--ctx->refcount > 0) + return; + + SSL_CTX_free(ctx->ssl_ctx); + pool_unref(&ctx->pool); + i_free(ctx); +} + +void openssl_iostream_global_deinit(void) +{ + if (!ssl_global_initialized) + return; + dovecot_openssl_common_global_unref(); +} + +int openssl_iostream_global_init(const struct ssl_iostream_settings *set, + const char **error_r) +{ + static char dovecot[] = "dovecot"; + const char *error; + + if (ssl_global_initialized) + return 0; + + ssl_global_initialized = TRUE; + dovecot_openssl_common_global_ref(); + + dovecot_ssl_extdata_index = + SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL); + + if (set->crypto_device != NULL && *set->crypto_device != '\0') { + switch (dovecot_openssl_common_global_set_engine(set->crypto_device, &error)) { + case 0: + error = t_strdup_printf( + "Unknown ssl_crypto_device: %s", + set->crypto_device); + /* fall through */ + case -1: + *error_r = error; + /* we'll deinit at exit in any case */ + return -1; + } + } + return 0; +} |