+BIO * bio;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+DH * dh;
+#else
+EVP_PKEY * pkey;
+#endif
+uschar * dhexpanded;
+const char * pem;
+int dh_bitsize;
+
+if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr))
+ return FALSE;
+
+if (!dhexpanded || !*dhexpanded)
+ bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
+else if (dhexpanded[0] == '/')
+ {
+ if (!(bio = BIO_new_file(CS dhexpanded, "r")))
+ {
+ tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
+ NULL, US strerror(errno), errstr);
+ return FALSE;
+ }
+ }
+else
+ {
+ if (Ustrcmp(dhexpanded, "none") == 0)
+ {
+ DEBUG(D_tls) debug_printf("Requested no DH parameters.\n");
+ return TRUE;
+ }
+
+ if (!(pem = std_dh_prime_named(dhexpanded)))
+ {
+ tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
+ NULL, US strerror(errno), errstr);
+ return FALSE;
+ }
+ bio = BIO_new_mem_buf(CS pem, -1);
+ }
+
+if (!(
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)
+#else
+ pkey = PEM_read_bio_Parameters_ex(bio, NULL, NULL, NULL)
+#endif
+ ) )
+ {
+ BIO_free(bio);
+ tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
+ NULL, NULL, errstr);
+ return FALSE;
+ }
+
+/* note: our default limit of 2236 is not a multiple of 8; the limit comes from
+an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with 2236.
+But older OpenSSL can only report in bytes (octets), not bits. If someone wants
+to dance at the edge, then they can raise the limit or use current libraries. */
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+# ifdef EXIM_HAVE_OPENSSL_DH_BITS
+/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022
+This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */
+dh_bitsize = DH_bits(dh);
+# else
+dh_bitsize = 8 * DH_size(dh);
+# endif
+#else /* 3.0.0 + */
+dh_bitsize = EVP_PKEY_get_bits(pkey);
+#endif
+
+/* Even if it is larger, we silently return success rather than cause things to
+fail out, so that a too-large DH will not knock out all TLS; it's a debatable
+choice. Likewise for a failing attempt to set one. */
+
+if (dh_bitsize <= tls_dh_max_bits)
+ {
+ if (
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ SSL_CTX_set_tmp_dh(sctx, dh)
+#else
+ SSL_CTX_set0_tmp_dh_pkey(sctx, pkey)
+#endif
+ == 0)
+ {
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (D-H param setting '%s'): %s",
+ dhexpanded ? dhexpanded : US"default", ssl_errstring);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ /* EVP_PKEY_free(pkey); crashes */
+#endif
+ }
+ else
+ DEBUG(D_tls)
+ debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
+ dhexpanded ? dhexpanded : US"default", dh_bitsize);
+ }
+else
+ DEBUG(D_tls)
+ debug_printf("dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n",
+ dhexpanded ? dhexpanded : US"default", dh_bitsize, tls_dh_max_bits);
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+DH_free(dh);
+#endif
+/* The EVP_PKEY ownership stays with the ctx; do not free it */
+
+BIO_free(bio);
+return TRUE;
+}
+
+
+
+
+/*************************************************
+* Initialize for ECDH *
+*************************************************/
+
+/* Load parameters for ECDH encryption. Server only.
+
+For now, we stick to NIST P-256 because: it's simple and easy to configure;
+it avoids any patent issues that might bite redistributors; despite events in
+the news and concerns over curve choices, we're not cryptographers, we're not
+pretending to be, and this is "good enough" to be better than no support,
+protecting against most adversaries. Given another year or two, there might
+be sufficient clarity about a "right" way forward to let us make an informed
+decision, instead of a knee-jerk reaction.
+
+Longer-term, we should look at supporting both various named curves and
+external files generated with "openssl ecparam", much as we do for init_dh().
+We should also support "none" as a value, to explicitly avoid initialisation.
+
+Patches welcome.
+
+Arguments:
+ sctx The current SSL CTX (inbound or outbound)
+ errstr error string pointer
+
+Returns: TRUE if OK (nothing to set up, or setup worked)
+*/
+
+static BOOL
+init_ecdh(SSL_CTX * sctx, uschar ** errstr)
+{
+#ifdef OPENSSL_NO_ECDH
+return TRUE;
+#else
+
+uschar * exp_curve;
+int nid;
+BOOL rv;
+
+# ifndef EXIM_HAVE_ECDH
+DEBUG(D_tls)
+ debug_printf("No OpenSSL API to define ECDH parameters, skipping\n");
+return TRUE;
+# else
+
+if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr))
+ return FALSE;
+if (!exp_curve || !*exp_curve)
+ return TRUE;
+
+/* "auto" needs to be handled carefully.
+ * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1
+ * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto
+ * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO)
+ * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection
+ * https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b
+ */
+if (Ustrcmp(exp_curve, "auto") == 0)
+ {
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+ DEBUG(D_tls) debug_printf(
+ "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
+ exp_curve = US"prime256v1";
+#else
+# if defined SSL_CTRL_SET_ECDH_AUTO
+ DEBUG(D_tls) debug_printf(
+ "ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n");
+ SSL_CTX_set_ecdh_auto(sctx, 1);
+ return TRUE;
+# else
+ DEBUG(D_tls) debug_printf(
+ "ECDH OpenSSL 1.1.0+: temp key parameter settings: default selection\n");
+ return TRUE;
+# endif
+#endif
+ }
+
+DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve);
+if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef
+# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID
+ && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef
+# endif
+ )
+ {
+ tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve),
+ NULL, NULL, errstr);
+ return FALSE;
+ }
+
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+ {
+ EC_KEY * ecdh;
+ if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
+ {
+ tls_error(US"Unable to create ec curve", NULL, NULL, errstr);
+ return FALSE;
+ }
+
+ /* The "tmp" in the name here refers to setting a temporary key
+ not to the stability of the interface. */
+
+ if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
+ tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), NULL, NULL, errstr);
+ else
+ DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
+ EC_KEY_free(ecdh);
+ }
+
+#else /* v 3.0.0 + */
+
+if ((rv = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0)
+ tls_error(string_sprintf("Error enabling '%s' group", exp_curve), NULL, NULL, errstr);
+else
+ DEBUG(D_tls) debug_printf("ECDH: enabled '%s' group\n", exp_curve);
+
+#endif
+
+return !rv;
+
+# endif /*EXIM_HAVE_ECDH*/
+#endif /*OPENSSL_NO_ECDH*/
+}
+
+
+
+/*************************************************
+* Expand key and cert file specs *
+*************************************************/
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+/*
+Arguments:
+ s SSL connection (not used)
+ export not used
+ keylength keylength
+
+Returns: pointer to generated key
+*/
+
+static RSA *
+rsa_callback(SSL *s, int export, int keylength)
+{
+RSA *rsa_key;
+#ifdef EXIM_HAVE_RSA_GENKEY_EX
+BIGNUM *bn = BN_new();
+#endif
+
+DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength);
+
+# ifdef EXIM_HAVE_RSA_GENKEY_EX
+if ( !BN_set_word(bn, (unsigned long)RSA_F4)
+ || !(rsa_key = RSA_new())
+ || !RSA_generate_key_ex(rsa_key, keylength, bn, NULL)
+ )
+# else
+if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL)))
+# endif
+
+ {
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
+ ssl_errstring);
+ return NULL;
+ }
+return rsa_key;
+}
+#endif /* pre-3.0.0 */
+
+
+
+/* Create and install a selfsigned certificate, for use in server mode */
+/*XXX we could arrange to call this during prelo for a null tls_certificate option.
+The normal cache inval + relo will suffice.
+Just need a timer for inval. */
+
+static int
+tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr)
+{
+X509 * x509 = NULL;
+EVP_PKEY * pkey;
+X509_NAME * name;
+uschar * where;
+
+DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n");
+where = US"allocating pkey";
+if (!(pkey = EVP_PKEY_new()))
+ goto err;
+
+where = US"allocating cert";
+if (!(x509 = X509_new()))
+ goto err;
+
+where = US"generating pkey";
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ {
+ RSA * rsa;
+ if (!(rsa = rsa_callback(NULL, 0, 2048)))
+ goto err;
+
+ where = US"assigning pkey";
+ if (!EVP_PKEY_assign_RSA(pkey, rsa))
+ goto err;
+ }
+#else
+pkey = EVP_RSA_gen(2048);
+#endif
+
+X509_set_version(x509, 2); /* N+1 - version 3 */
+ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
+X509_gmtime_adj(X509_get_notBefore(x509), 0);
+X509_gmtime_adj(X509_get_notAfter(x509), (long)2 * 60 * 60); /* 2 hour */
+X509_set_pubkey(x509, pkey);
+
+name = X509_get_subject_name(x509);
+X509_NAME_add_entry_by_txt(name, "C",
+ MBSTRING_ASC, CUS "UK", -1, -1, 0);
+X509_NAME_add_entry_by_txt(name, "O",
+ MBSTRING_ASC, CUS "Exim Developers", -1, -1, 0);
+X509_NAME_add_entry_by_txt(name, "CN",
+ MBSTRING_ASC, CUS smtp_active_hostname, -1, -1, 0);
+X509_set_issuer_name(x509, name);
+
+where = US"signing cert";
+if (!X509_sign(x509, pkey, EVP_md5()))
+ goto err;
+
+where = US"installing selfsign cert";
+if (!SSL_CTX_use_certificate(sctx, x509))
+ goto err;
+
+where = US"installing selfsign key";
+if (!SSL_CTX_use_PrivateKey(sctx, pkey))
+ goto err;
+
+return OK;
+
+err:
+ (void) tls_error(where, NULL, NULL, errstr);
+ if (x509) X509_free(x509);
+ if (pkey) EVP_PKEY_free(pkey);
+ return DEFER;
+}
+
+
+
+
+
+
+
+/*************************************************
+* Information callback *
+*************************************************/
+
+/* The SSL library functions call this from time to time to indicate what they
+are doing. We copy the string to the debugging output when TLS debugging has
+been requested.
+
+Arguments:
+ s the SSL connection
+ where
+ ret
+
+Returns: nothing
+*/
+
+static void
+info_callback(SSL *s, int where, int ret)
+{
+DEBUG(D_tls)
+ {
+ const uschar * str;
+
+ if (where & SSL_ST_CONNECT)
+ str = US"SSL_connect";
+ else if (where & SSL_ST_ACCEPT)
+ str = US"SSL_accept";
+ else
+ str = US"SSL info (undefined)";
+
+ if (where & SSL_CB_LOOP)
+ debug_printf("%s: %s\n", str, SSL_state_string_long(s));
+ else if (where & SSL_CB_ALERT)
+ debug_printf("SSL3 alert %s:%s:%s\n",
+ str = where & SSL_CB_READ ? US"read" : US"write",
+ SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
+ else if (where & SSL_CB_EXIT)
+ {
+ if (ret == 0)
+ debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s));
+ else if (ret < 0)
+ debug_printf("%s: error in %s\n", str, SSL_state_string_long(s));
+ }
+ else if (where & SSL_CB_HANDSHAKE_START)
+ debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s));
+ else if (where & SSL_CB_HANDSHAKE_DONE)
+ debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s));
+ }
+}
+
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+static void
+keylog_callback(const SSL *ssl, const char *line)
+{
+char * filename;
+FILE * fp;
+DEBUG(D_tls) debug_printf("%.200s\n", line);
+if (!(filename = getenv("SSLKEYLOGFILE"))) return;
+if (!(fp = fopen(filename, "a"))) return;
+fprintf(fp, "%s\n", line);
+fclose(fp);
+}
+#endif
+
+
+
+
+
+#ifndef DISABLE_EVENT
+static int
+verify_event(tls_support * tlsp, X509 * cert, int depth, const uschar * dn,
+ BOOL *calledp, const BOOL *optionalp, const uschar * what)
+{
+uschar * ev;