fix TLS SNI segfault case
[exim.git] / src / src / tls-gnu.c
index f0e391f9713ef53c7edd7940138a411e3a4ea16c..8391914b6bf97f3fd897e9134a73663272ebaecd 100644 (file)
@@ -140,12 +140,6 @@ static const char * const exim_default_gnutls_priority = "NORMAL";
 static BOOL exim_gnutls_base_init_done = FALSE;
 
 
-/* ------------------------------------------------------------------------ */
-/* Callback declarations */
-
-static void exim_gnutls_logger_cb(int level, const char *message);
-static int exim_sni_handling_cb(gnutls_session_t session);
-
 /* ------------------------------------------------------------------------ */
 /* macros */
 
@@ -158,20 +152,37 @@ callbacks. */
 
 #define EXIM_CLIENT_DH_MIN_BITS 1024
 
+/* With GnuTLS 2.12.x+ we have gnutls_sec_param_to_pk_bits() with which we
+can ask for a bit-strength.  Without that, we stick to the constant we had
+before, for now. */
+#define EXIM_SERVER_DH_BITS_PRE2_12 1024
+
 #define exim_gnutls_err_check(Label) do { \
   if (rc != GNUTLS_E_SUCCESS) { return tls_error((Label), gnutls_strerror(rc), host); } } while (0)
 
-#define exim_gnutls_err_debugreturn0(Label) do { \
-  if (rc != GNUTLS_E_SUCCESS) { \
-    DEBUG(D_tls) debug_printf("TLS failure: %s: %s", (Label), gnutls_strerror(rc)); \
-    return 0; } } while (0)
-
 #define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname)
 
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 #define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
+#define HAVE_GNUTLS_SEC_PARAM_CONSTANTS
+#define HAVE_GNUTLS_RND
 #endif
 
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Callback declarations */
+
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void exim_gnutls_logger_cb(int level, const char *message);
+#endif
+
+static int exim_sni_handling_cb(gnutls_session_t session);
+
+
+
+
 /* ------------------------------------------------------------------------ */
 /* Static functions */
 
@@ -380,21 +391,30 @@ gnutls_datum m;
 uschar filename[PATH_MAX];
 size_t sz;
 host_item *host = NULL; /* dummy for macros */
-const char * const dh_param_fn_ext = "normal"; /* change as dh_bits changes */
 
 DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
 
 rc = gnutls_dh_params_init(&dh_server_params);
 exim_gnutls_err_check(US"gnutls_dh_params_init");
 
-/* If you change this, also change dh_param_fn_ext so that we can use a
+#ifdef HAVE_GNUTLS_SEC_PARAM_CONSTANTS
+/* If you change this constant, also change dh_param_fn_ext so that we can use a
 different filename and ensure we have sufficient bits. */
 dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL);
 if (!dh_bits)
   return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL);
+DEBUG(D_tls)
+  debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits.\n",
+      dh_bits);
+#else
+dh_bits = EXIM_SERVER_DH_BITS_PRE2_12;
+DEBUG(D_tls)
+  debug_printf("GnuTLS lacks gnutls_sec_param_to_pk_bits(), using %d bits.\n",
+      dh_bits);
+#endif
 
 if (!string_format(filename, sizeof(filename),
-      "%s/gnutls-params-%s", spool_directory, dh_param_fn_ext))
+      "%s/gnutls-params-%d", spool_directory, dh_bits))
   return tls_error(US"overlong filename", NULL, NULL);
 
 /* Open the cache file for reading and if successful, read it and set up the
@@ -634,12 +654,12 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
     if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) &&
         (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
       {
-      DEBUG(D_tls) debug_printf("cert and key unchanged with SNI.\n");
+      DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
       setit = FALSE;
       }
     else
       {
-      DEBUG(D_tls) debug_printf("SNI changed cert/key pair.\n");
+      DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
       }
     }
 
@@ -651,8 +671,9 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
     exim_gnutls_err_check(
         string_sprintf("cert/key setup: cert=%s key=%s",
           state->exp_tls_certificate, state->exp_tls_privatekey));
+    DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
     }
-  }
+  } /* tls_certificate */
 
 /* Set the trusted CAs file if one is provided, and then add the CRL if one is
 provided. Experiment shows that, if the certificate file is empty, an unhelpful
@@ -673,15 +694,32 @@ if (state->tls_verify_certificates && *state->tls_verify_certificates)
 
   if (state->received_sni)
     {
-    if (Ustrcmp(state->exp_tls_verify_certificates, saved_tls_verify_certificates) == 0)
-      setit_vc = FALSE;
-    if (Ustrcmp(state->exp_tls_crl, saved_tls_crl) == 0)
-      setit_crl = FALSE;
+    state->exp_tls_verify_certificates, state->exp_tls_verify_certificates,
+    saved_tls_verify_certificates, saved_tls_verify_certificates);
+    if (!(state->exp_tls_verify_certificates || saved_tls_verify_certificates))
+      setit_vc = FALSE; /* never was set */
+    else if (!state->exp_tls_verify_certificates || !saved_tls_verify_certificates)
+      setit_vc = TRUE; /* changed whether set */
+    else if (Ustrcmp(state->exp_tls_verify_certificates, saved_tls_verify_certificates) == 0)
+      setit_vc = FALSE; /* not changed value */
+
+    state->exp_tls_crl, state->exp_tls_crl,
+    saved_tls_crl, saved_tls_crl);
+    if (!(state->exp_tls_crl || saved_tls_crl))
+      setit_crl = FALSE; /* never was set */
+    else if (!state->exp_tls_crl || !saved_tls_crl)
+      setit_crl = TRUE; /* changed whether set */
+    else if (Ustrcmp(state->exp_tls_crl, saved_tls_crl) == 0)
+      setit_crl = FALSE; /* not changed value */
     }
 
   /* nb: early exit; change if add more expansions to this function */
   if (!(setit_vc || setit_crl))
+    {
+    DEBUG(D_tls)
+      debug_printf("TLS SNI: no change to tls_crl or tls_verify_certificates\n");
     return OK;
+    }
 
   if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
     {
@@ -724,6 +762,10 @@ if (state->tls_verify_certificates && *state->tls_verify_certificates)
         }
       DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
       }
+    else
+      {
+      DEBUG(D_tls) debug_printf("TLS SNI: tls_verify_certificates unchanged\n");
+      }
 
     if (setit_crl && state->tls_crl && *state->tls_crl)
       {
@@ -735,6 +777,8 @@ if (state->tls_verify_certificates && *state->tls_verify_certificates)
         exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
         }
       }
+    DEBUG(D_tls)
+      if (!setit_crl) debug_printf("TLS SNI: tls_crl unchanged\n");
     } /* statbuf.st_size */
   } /* tls_verify_certificates */
 
@@ -1095,11 +1139,13 @@ return TRUE;
  *   gnutls_global_set_log_function()
  *   gnutls_global_set_log_level() 0..9
  */
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
 static void
 exim_gnutls_logger_cb(int level, const char *message)
 {
   DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s\n", level, message);
 }
+#endif
 
 
 /* Called after client hello, should handle SNI work.
@@ -1127,7 +1173,18 @@ unsigned int sni_type;
 int rc, old_pool;
 
 rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
-exim_gnutls_err_debugreturn0("gnutls_server_name_get()");
+if (rc != GNUTLS_E_SUCCESS)
+  {
+  DEBUG(D_tls) {
+    if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+      debug_printf("TLS: no SNI presented in handshake.\n");
+    else
+      debug_printf("TLS failure: gnutls_server_name_get(): %s [%d]\n",
+        gnutls_strerror(rc), rc);
+  };
+  return 0;
+  }
+
 if (sni_type != GNUTLS_NAME_DNS)
   {
   DEBUG(D_tls) debug_printf("TLS: ignoring SNI of unhandled type %u\n", sni_type);
@@ -1667,6 +1724,7 @@ Arguments:
 Returns     a random number in range [0, max-1]
 */
 
+#ifdef HAVE_GNUTLS_RND
 int
 vaguely_random_number(int max)
 {
@@ -1704,6 +1762,13 @@ for (p = smallbuf; needed_len; --needed_len, ++p)
  * smooth distribution and cares enough then they should submit a patch then. */
 return r % max;
 }
+#else /* HAVE_GNUTLS_RND */
+int
+vaguely_random_number(int max)
+{
+  return vaguely_random_number_fallback(max);
+}
+#endif /* HAVE_GNUTLS_RND */