Avoid modifying global errno when raising event
[exim.git] / src / src / tls-openssl.c
index c02e42bbf3b5d71da80455820dca226286a3338d..5130455fe6698f7abaf5d6926d5979293bb9be65 100644 (file)
@@ -1009,7 +1009,7 @@ if (ev)
   old_cert = tlsp->peercert;
   tlsp->peercert = X509_dup(cert);
   /* NB we do not bother setting peerdn */
-  if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth))))
+  if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth), &errno)))
     {
     log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: "
                "depth=%d cert=%s: %s",
@@ -1642,11 +1642,24 @@ return OK;
 * One-time init credentials for server and client *
 **************************************************/
 
+static void
+normalise_ciphers(uschar ** ciphers, const uschar * pre_expansion_ciphers)
+{
+uschar * s = *ciphers;
+
+if (!s || !Ustrchr(s, '_')) return;    /* no change needed */
+
+if (s == pre_expansion_ciphers)
+  s = string_copy(s);                  /* get writable copy */
+
+for (uschar * t = s; *t; t++) if (*t == '_') *t = '-';
+*ciphers = s;
+}
+
 static int
 server_load_ciphers(SSL_CTX * ctx, exim_openssl_state_st * state,
   uschar * ciphers, uschar ** errstr)
 {
-for (uschar * s = ciphers; *s; s++ ) if (*s == '_') *s = '-';
 DEBUG(D_tls) debug_printf("required ciphers: %s\n", ciphers);
 if (!SSL_CTX_set_cipher_list(ctx, CS ciphers))
   return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr);
@@ -1798,6 +1811,7 @@ else
 if (opt_set_and_noexpand(tls_require_ciphers))
   {
   DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server\n");
+  normalise_ciphers(&tls_require_ciphers, tls_require_ciphers);
   if (server_load_ciphers(ctx, &state_server, tls_require_ciphers,
                          &dummy_errstr) == OK)
     state_server.lib_state.pri_string = TRUE;
@@ -1953,7 +1967,11 @@ typedef struct {                 /* Session ticket encryption key */
 
   const EVP_CIPHER *   aes_cipher;
   uschar               aes_key[32];    /* size needed depends on cipher. aes_128 implies 128/8 = 16? */
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
   const EVP_MD *       hmac_hash;
+# else
+  const uschar *       hmac_hashname;
+# endif
   uschar               hmac_key[16];
   time_t               renew;
   time_t               expire;
@@ -1982,7 +2000,11 @@ if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return;
 
 exim_tk.name[0] = 'E';
 exim_tk.aes_cipher = EVP_aes_256_cbc();
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
 exim_tk.hmac_hash = EVP_sha256();
+# else
+exim_tk.hmac_hashname = US "sha256";
+# endif
 exim_tk.expire = t + ssl_session_timeout;
 exim_tk.renew = t + ssl_session_timeout/2;
 }
@@ -2002,10 +2024,49 @@ return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk
   : NULL;
 }
 
+
+static int
+tk_hmac_init(
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+  HMAC_CTX * hctx,
+#else
+  EVP_MAC_CTX * hctx,
+#endif
+  exim_stek * key
+  )
+{
+/*XXX will want these dependent on the ssl session strength */
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+  HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
+               key->hmac_hash, NULL);
+#else
+ {
+  OSSL_PARAM params[3];
+  uschar * hk = string_copy(key->hmac_hashname);       /* need nonconst */
+  params[0] = OSSL_PARAM_construct_octet_string("key", key->hmac_key, sizeof(key->hmac_key));
+  params[1] = OSSL_PARAM_construct_utf8_string("digest", CS hk, 0);
+  params[2] = OSSL_PARAM_construct_end();
+  if (EVP_MAC_CTX_set_params(hctx, params) == 0)
+    {
+    DEBUG(D_tls) debug_printf("EVP_MAC_CTX_set_params: %s\n",
+      ERR_reason_error_string(ERR_get_error()));
+    return 0; /* error in mac initialisation */
+    }
+}
+#endif
+return 1;
+}
+
 /* Callback for session tickets, on server */
 static int
 ticket_key_callback(SSL * ssl, uschar key_name[16],
-  uschar * iv, EVP_CIPHER_CTX * c_ctx, HMAC_CTX * hctx, int enc)
+  uschar * iv, EVP_CIPHER_CTX * c_ctx,
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+  HMAC_CTX * hctx,
+#else
+  EVP_MAC_CTX * hctx,
+#endif
+  int enc)
 {
 tls_support * tlsp = state_server.tlsp;
 exim_stek * key;
@@ -2023,9 +2084,7 @@ if (enc)
   memcpy(key_name, key->name, 16);
   DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL));
 
-  /*XXX will want these dependent on the ssl session strength */
-  HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
-               key->hmac_hash, NULL);
+  if (tk_hmac_init(hctx, key) == 0) return 0;
   EVP_EncryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv);
 
   DEBUG(D_tls) debug_printf("ticket created\n");
@@ -2048,8 +2107,7 @@ else
     return 0;
     }
 
-  HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
-               key->hmac_hash, NULL);
+  if (tk_hmac_init(hctx, key) == 0) return 0;
   EVP_DecryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv);
 
   DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now);
@@ -2062,7 +2120,7 @@ else
   return key->renew < now ? 2 : 1;
   }
 }
-#endif
+#endif /* !DISABLE_TLS_RESUME */
 
 
 
@@ -2464,7 +2522,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
        {
        tls_out.ocsp = OCSP_FAILED;
        DEBUG(D_tls) ERR_print_errors(bp);
-       log_write(0, LOG_MAIN, "Server OSCP dates invalid");
+       log_write(0, LOG_MAIN, "OCSP dates invalid");
        goto failed;
        }
 
@@ -3134,9 +3192,12 @@ else
   if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr))
     return FAIL;
 
-  if (  expciphers
-     && (rc = server_load_ciphers(ctx, &state_server, expciphers, errstr)) != OK)
-    return rc;
+  if (expciphers)
+    {
+    normalise_ciphers(&expciphers, tls_require_ciphers);
+    if ((rc = server_load_ciphers(ctx, &state_server, expciphers, errstr)) != OK)
+      return rc;
+    }
   }
 
 /* If this is a host for which certificate verification is mandatory or
@@ -3174,9 +3235,15 @@ else
 skip_certs: ;
 
 #ifndef DISABLE_TLS_RESUME
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
 SSL_CTX_set_tlsext_ticket_key_cb(ctx, ticket_key_callback);
 /* despite working, appears to always return failure, so ignoring */
+# else
+SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ticket_key_callback);
+/* despite working, appears to always return failure, so ignoring */
+# endif
 #endif
+
 #ifdef OPENSSL_HAVE_NUM_TICKETS
 # ifndef DISABLE_TLS_RESUME
 SSL_CTX_set_num_tickets(ctx, tls_in.host_resumable ? 1 : 0);
@@ -3244,6 +3311,7 @@ if (rc <= 0)
     case SSL_ERROR_ZERO_RETURN:
       DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
       (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
+      (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
 
       if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN)
            SSL_shutdown(ssl);
@@ -3261,8 +3329,9 @@ if (rc <= 0)
          || r == SSL_R_VERSION_TOO_LOW
 #endif
          || r == SSL_R_UNKNOWN_PROTOCOL || r == SSL_R_UNSUPPORTED_PROTOCOL)
-       s = string_sprintf("%s (%s)", s, SSL_get_version(ssl));
+       s = string_sprintf("(%s)", SSL_get_version(ssl));
       (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : s, errstr);
+      (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
       return FAIL;
       }
 
@@ -3273,6 +3342,7 @@ if (rc <= 0)
        if (!errno)
          {
          *errstr = US"SSL_accept: TCP connection closed by peer";
+         (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
          return FAIL;
          }
        DEBUG(D_tls) debug_printf(" - syscall %s\n", strerror(errno));
@@ -3281,6 +3351,7 @@ if (rc <= 0)
                      sigalrm_seen ? US"timed out"
                      : ERR_peek_error() ? NULL : string_sprintf("ret %d", error),
                      errstr);
+      (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
       return FAIL;
     }
   }
@@ -3831,21 +3902,25 @@ if (conn_args->dane)
     return FALSE;
   if (expciphers && *expciphers == '\0')
     expciphers = NULL;
+
+  normalise_ciphers(&expciphers, ob->dane_require_tls_ciphers);
   }
 #endif
-if (!expciphers &&
-    !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
+if (!expciphers)
+  {
+  if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
       &expciphers, errstr))
-  return FALSE;
+    return FALSE;
 
-/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
-are separated by underscores. So that I can use either form in my tests, and
-also for general convenience, we turn underscores into hyphens here. */
+  /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
+  are separated by underscores. So that I can use either form in my tests, and
+  also for general convenience, we turn underscores into hyphens here. */
+
+  normalise_ciphers(&expciphers, ob->tls_require_ciphers);
+  }
 
 if (expciphers)
   {
-  uschar *s = expciphers;
-  while (*s) { if (*s == '_') *s = '-'; s++; }
   DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
   if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers))
     {
@@ -4508,12 +4583,9 @@ if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers,
 if (!(expciphers && *expciphers))
   return NULL;
 
-/* normalisation ripped from above */
-s = expciphers;
-while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+normalise_ciphers(&expciphers, tls_require_ciphers);
 
 err = NULL;
-
 if (lib_ctx_new(&ctx, NULL, &err) == OK)
   {
   DEBUG(D_tls)