Fix build with older TLS library versions
[exim.git] / src / src / tls-openssl.c
index 7a625a8ba10ca2c8ce1ae516372f21bdcc2d983a..c97dc1bff2f48a19d4bd1ebe7ce3c85c9d68db03 100644 (file)
@@ -76,6 +76,9 @@ change this guard and punt the issue for a while longer. */
 #  define EXIM_HAVE_SESSION_TICKET
 #  define EXIM_HAVE_OPESSL_TRACE
 #  define EXIM_HAVE_OPESSL_GET0_SERIAL
+#  ifndef DISABLE_OCSP
+#   define EXIM_HAVE_OCSP
+#  endif
 # else
 #  define EXIM_NEED_OPENSSL_INIT
 # endif
@@ -102,6 +105,8 @@ change this guard and punt the issue for a while longer. */
 #  define OPENSSL_HAVE_KEYLOG_CB
 #  define OPENSSL_HAVE_NUM_TICKETS
 #  define EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# else
+#  define OPENSSL_BAD_SRVR_OURCERT
 # endif
 #endif
 
@@ -146,6 +151,11 @@ This list is current as of:
   ==>  1.0.1b  <==
 Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
 Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev
+Plus SSL_OP_NO_RENEGOTIATION for 1.1.1
+
+XXX could we autobuild this list, as with predefined-macros?
+Seems just parsing ssl.h for SSL_OP_.* would be enough.
+Also allow a numeric literal?
 */
 static exim_openssl_option exim_openssl_options[] = {
 /* KEEP SORTED ALPHABETICALLY! */
@@ -185,6 +195,9 @@ static exim_openssl_option exim_openssl_options[] = {
 #ifdef SSL_OP_NO_COMPRESSION
   { US"no_compression", SSL_OP_NO_COMPRESSION },
 #endif
+#ifdef SSL_OP_NO_RENEGOTIATION
+  { US"no_renegotiation", SSL_OP_NO_RENEGOTIATION },
+#endif
 #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
   { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
 #endif
@@ -266,6 +279,13 @@ builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING );
 # ifdef SSL_OP_NO_TLSv1_3
 builtin_macro_create(US"_HAVE_TLS1_3");
 # endif
+# ifdef OPENSSL_BAD_SRVR_OURCERT
+builtin_macro_create(US"_TLS_BAD_MULTICERT_IN_OURCERT");
+# endif
+# ifdef EXIM_HAVE_OCSP
+builtin_macro_create(US"_HAVE_TLS_OCSP");
+builtin_macro_create(US"_HAVE_TLS_OCSP_LIST");
+# endif
 }
 #else
 
@@ -1841,13 +1861,13 @@ OCSP_RESPONSE * rsp;
 OCSP_BASICRESP * bs;
 int i;
 
-DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):\n");
+DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n");
 len = SSL_get_tlsext_status_ocsp_resp(s, &p);
 if(!p)
  {
   /* Expect this when we requested ocsp but got none */
   if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher))
-    log_write(0, LOG_MAIN, "Received TLS status callback, null content");
+    log_write(0, LOG_MAIN, "Required TLS certificate status not received");
   else
     DEBUG(D_tls) debug_printf(" null\n");
   return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
@@ -2261,14 +2281,13 @@ Returns:    pointer to allocated string in perm-pool
 */
 
 static uschar *
-construct_cipher_name(SSL * ssl, int * bits)
+construct_cipher_name(SSL * ssl, const uschar * ver, int * bits)
 {
 int pool = store_pool;
 /* With OpenSSL 1.0.0a, 'c' needs to be const but the documentation doesn't
 yet reflect that.  It should be a safe change anyway, even 0.9.8 versions have
 the accessor functions use const in the prototype. */
 
-const uschar * ver = CUS SSL_get_version(ssl);
 const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
 uschar * s;
 
@@ -2299,6 +2318,21 @@ return cipher_stdname(id >> 8, id & 0xff);
 }
 
 
+static const uschar *
+tlsver_name(SSL * ssl)
+{
+uschar * s, * p;
+int pool = store_pool;
+
+store_pool = POOL_PERM;
+s = string_copy(US SSL_get_version(ssl));
+store_pool = pool;
+if ((p = Ustrchr(s, 'v')))     /* TLSv1.2 -> TLS1.2 */
+  for (;; p++) if (!(*p = p[1])) break;
+return CUS s;
+}
+
+
 static void
 peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned siz)
 {
@@ -2335,7 +2369,11 @@ if (tlsp->peercert)
     for resumption next to the TLS session, and used here. */
 
     if (!tlsp->verify_override)
-      tlsp->certificate_verified = SSL_get_verify_result(ssl) == X509_V_OK;
+      tlsp->certificate_verified =
+#ifdef SUPPORT_DANE
+       tlsp->dane_verified ||
+#endif
+       SSL_get_verify_result(ssl) == X509_V_OK;
     }
 }
 
@@ -2706,8 +2744,14 @@ if (rc <= 0)
 
     /* Handle genuine errors */
     case SSL_ERROR_SSL:
-      (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
+      {
+      uschar * s = US"SSL_accept";
+      unsigned long e = ERR_peek_error();
+      if (ERR_GET_REASON(e) == SSL_R_WRONG_VERSION_NUMBER)
+       s = string_sprintf("%s (%s)", s, SSL_get_version(server_ssl));
+      (void) tls_error(s, NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
       return FAIL;
+      }
 
     default:
       DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
@@ -2737,12 +2781,16 @@ if (SSL_session_reused(server_ssl))
   }
 #endif
 
-/* TLS has been set up. Adjust the input functions to read via TLS,
-and initialize things. */
+/* TLS has been set up. Record data for the connection,
+adjust the input functions to read via TLS, and initialize things. */
 
+#ifdef SSL_get_extms_support
+tls_in.ext_master_secret = SSL_get_extms_support(server_ssl) == 1;
+#endif
 peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn));
 
-tls_in.cipher = construct_cipher_name(server_ssl, &tls_in.bits);
+tls_in.ver = tlsver_name(server_ssl);
+tls_in.cipher = construct_cipher_name(server_ssl, tls_in.ver, &tls_in.bits);
 tls_in.cipher_stdname = cipher_stdname_ssl(server_ssl);
 
 DEBUG(D_tls)
@@ -2775,6 +2823,20 @@ DEBUG(D_tls)
   tls_in.ourcert = crt ? X509_dup(crt) : NULL;
   }
 
+/* Channel-binding info for authenticators
+See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/ */
+  {
+  uschar c, * s;
+  size_t len = SSL_get_peer_finished(server_ssl, &c, 0);
+  int old_pool = store_pool;
+  
+  SSL_get_peer_finished(server_ssl, s = store_get((int)len, FALSE), len);
+  store_pool = POOL_PERM;
+    tls_in.channelbinding = b64encode_taint(CUS s, (int)len, FALSE);
+  store_pool = old_pool;
+  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p\n", tls_in.channelbinding);
+  }
+
 /* Only used by the server-side tls (tls_in), including tls_getc.
    Client-side (tls_out) reads (seem to?) go via
    smtp_read_response()/ip_recv().
@@ -3325,9 +3387,13 @@ DEBUG(D_tls)
 tls_client_resume_posthandshake(exim_client_ctx, tlsp);
 #endif
 
+#ifdef SSL_get_extms_support
+tlsp->ext_master_secret = SSL_get_extms_support(exim_client_ctx->ssl) == 1;
+#endif
 peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
 
-tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, &tlsp->bits);
+tlsp->ver = tlsver_name(exim_client_ctx->ssl);
+tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, tlsp->ver, &tlsp->bits);
 tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl);
 
 /* Record the certificate we presented */
@@ -3336,6 +3402,20 @@ tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl);
   tlsp->ourcert = crt ? X509_dup(crt) : NULL;
   }
 
+/*XXX will this work with continued-TLS? */
+/* Channel-binding info for authenticators */
+  {
+  uschar c, * s;
+  size_t len = SSL_get_finished(exim_client_ctx->ssl, &c, 0);
+  int old_pool = store_pool;
+  
+  SSL_get_finished(exim_client_ctx->ssl, s = store_get((int)len, TRUE), len);
+  store_pool = POOL_PERM;
+    tlsp->channelbinding = b64encode_taint(CUS s, (int)len, TRUE);
+  store_pool = old_pool;
+  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp);
+  }
+
 tlsp->active.sock = cctx->sock;
 tlsp->active.tls_ctx = exim_client_ctx;
 cctx->tls_ctx = exim_client_ctx;
@@ -3536,11 +3616,12 @@ Arguments:
 Returns:    the number of bytes after a successful write,
             -1 after a failed write
 
-Used by both server-side and client-side TLS.
+Used by both server-side and client-side TLS.  Calling with len zero and more unset
+will flush buffered writes; buff can be null for this case.
 */
 
 int
-tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
+tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more)
 {
 size_t olen = len;
 int outbytes, error;
@@ -3566,14 +3647,16 @@ a store reset there, so use POOL_PERM. */
 
 if ((more || corked))
   {
-#ifdef SUPPORT_PIPE_CONNECT
+  if (!len) buff = US &error;  /* dummy just so that string_catn is ok */
+
+#ifndef DISABLE_PIPE_CONNECT
   int save_pool = store_pool;
   store_pool = POOL_PERM;
 #endif
 
   corked = string_catn(corked, buff, len);
 
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
   store_pool = save_pool;
 #endif
 
@@ -3595,16 +3678,16 @@ for (int left = len; left > 0;)
   DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
   switch (error)
     {
+    case SSL_ERROR_NONE:       /* the usual case */
+      left -= outbytes;
+      buff += outbytes;
+      break;
+
     case SSL_ERROR_SSL:
       ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
       log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
       return -1;
 
-    case SSL_ERROR_NONE:
-      left -= outbytes;
-      buff += outbytes;
-      break;
-
     case SSL_ERROR_ZERO_RETURN:
       log_write(0, LOG_MAIN, "SSL channel closed on write");
       return -1;
@@ -3965,6 +4048,9 @@ result |= SSL_OP_NO_SSLv3;
 #ifdef SSL_OP_SINGLE_DH_USE
 result |= SSL_OP_SINGLE_DH_USE;
 #endif
+#ifdef SSL_OP_NO_RENEGOTIATION
+result |= SSL_OP_NO_RENEGOTIATION;
+#endif
 
 if (!option_spec)
   {
@@ -3997,7 +4083,7 @@ for (uschar * s = exp; *s; /**/)
     DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);
     return FALSE;
     }
-  DEBUG(D_tls) debug_printf("openssl option, %s %8lx: %lx (%s)\n",
+  DEBUG(D_tls) debug_printf("openssl option, %s %08lx: %08lx (%s)\n",
       adding ? "adding to    " : "removing from", result, item, s);
   if (adding)
     result |= item;