Add comment on GnuTLS library debugging facility
[exim.git] / src / src / tls-gnu.c
index d623d8e4aae0caf046104d577a0c5c2ce50d2c3a..527ad28b216333c9f95ceb595891b9c40c9d68f7 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
@@ -60,6 +60,9 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #if GNUTLS_VERSION_NUMBER >= 0x030014
 # define SUPPORT_SYSDEFAULT_CABUNDLE
 #endif
+#if GNUTLS_VERSION_NUMBER >= 0x030109
+# define SUPPORT_CORK
+#endif
 
 #ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
@@ -136,16 +139,45 @@ typedef struct exim_gnutls_state {
 } exim_gnutls_state_st;
 
 static const exim_gnutls_state_st exim_gnutls_state_init = {
-  NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE,
-  NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL,
+  .session =           NULL,
+  .x509_cred =         NULL,
+  .priority_cache =    NULL,
+  .verify_requirement =        VERIFY_NONE,
+  .fd_in =             -1,
+  .fd_out =            -1,
+  .peer_cert_verified =        FALSE,
+  .trigger_sni_changes =FALSE,
+  .have_set_peerdn =   FALSE,
+  .host =              NULL,
+  .peercert =          NULL,
+  .peerdn =            NULL,
+  .ciphersuite =       NULL,
+  .received_sni =      NULL,
+
+  .tls_certificate =   NULL,
+  .tls_privatekey =    NULL,
+  .tls_sni =           NULL,
+  .tls_verify_certificates = NULL,
+  .tls_crl =           NULL,
+  .tls_require_ciphers =NULL,
+
+  .exp_tls_certificate = NULL,
+  .exp_tls_privatekey =        NULL,
+  .exp_tls_verify_certificates = NULL,
+  .exp_tls_crl =       NULL,
+  .exp_tls_require_ciphers = NULL,
+  .exp_tls_ocsp_file = NULL,
+  .exp_tls_verify_cert_hostnames = NULL,
 #ifndef DISABLE_EVENT
-                                            NULL,
+  .event_action =      NULL,
 #endif
-  NULL,
-  NULL, 0, 0, 0, 0,
+  .tlsp =              NULL,
+
+  .xfer_buffer =       NULL,
+  .xfer_buffer_lwm =   0,
+  .xfer_buffer_hwm =   0,
+  .xfer_eof =          0,
+  .xfer_error =                0,
 };
 
 /* Not only do we have our own APIs which don't pass around state, assuming
@@ -189,7 +221,8 @@ static BOOL gnutls_buggy_ocsp = FALSE;
 
 /* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
 the library logging; a value less than 0 disables the calls to set up logging
-callbacks. */
+callbacks.  Possibly GNuTLS also looks for an environment variable
+"GNUTLS_DEBUG_LEVEL". */
 #ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
 # define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
 #endif
@@ -772,6 +805,18 @@ err:
 
 
 
+static int
+tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
+  uschar * certfile, uschar * keyfile, uschar ** errstr)
+{
+int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
+    CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM);
+exim_gnutls_err_check(
+    string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile));
+return OK;
+}
+
+
 /*************************************************
 *       Variables re-expanded post-SNI           *
 *************************************************/
@@ -792,7 +837,7 @@ Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_expand_session_files(exim_gnutls_state_st *state, uschar ** errstr)
+tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr)
 {
 struct stat statbuf;
 int rc;
@@ -807,11 +852,11 @@ int cert_count;
 if (!host)     /* server */
   if (!state->received_sni)
     {
-    if (state->tls_certificate &&
-        (Ustrstr(state->tls_certificate, US"tls_sni") ||
-         Ustrstr(state->tls_certificate, US"tls_in_sni") ||
-         Ustrstr(state->tls_certificate, US"tls_out_sni")
-       ))
+    if (  state->tls_certificate
+       && (  Ustrstr(state->tls_certificate, US"tls_sni")
+         || Ustrstr(state->tls_certificate, US"tls_in_sni")
+         || Ustrstr(state->tls_certificate, US"tls_out_sni")
+       )  )
       {
       DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
       state->trigger_sni_changes = TRUE;
@@ -878,13 +923,29 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
       DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
       }
 
-  rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
-      CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
-      GNUTLS_X509_FMT_PEM);
-  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");
+  if (!host)   /* server */
+    {
+    const uschar * clist = state->exp_tls_certificate;
+    const uschar * klist = state->exp_tls_privatekey;
+    int csep = 0, ksep = 0;
+    uschar * cfile, * kfile;
+
+    while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
+      if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0)))
+       return tls_error(US"cert/key setup: out of keys", NULL, host, errstr);
+      else if ((rc = tls_add_certfile(state, host, cfile, kfile, errstr)))
+       return rc;
+      else
+       DEBUG(D_tls) debug_printf("TLS: cert/key %s registered\n", cfile);
+    }
+  else
+    {
+    if ((rc = tls_add_certfile(state, host,
+               state->exp_tls_certificate, state->exp_tls_privatekey, errstr)))
+      return rc;
+    DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
+    }
+
   } /* tls_certificate */
 
 
@@ -1244,7 +1305,7 @@ if (host)
   }
 else if (state->tls_sni)
   DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
-      "have an SNI set for a client [%s]\n", state->tls_sni);
+      "have an SNI set for a server [%s]\n", state->tls_sni);
 
 /* This is the priority string support,
 http://www.gnutls.org/manual/html_node/Priority-Strings.html
@@ -1648,7 +1709,7 @@ int ret;
 if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
   {
   DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
-                             (char *)ptr);
+                             CS ptr);
   tls_in.ocsp = OCSP_NOT_RESP;
   return GNUTLS_E_NO_CERTIFICATE_STATUS;
   }
@@ -1745,7 +1806,7 @@ exim_gnutls_state_st * state = NULL;
 if (tls_in.active >= 0)
   {
   tls_error(US"STARTTLS received after TLS started", "", NULL, errstr);
-  smtp_printf("554 Already in TLS\r\n");
+  smtp_printf("554 Already in TLS\r\n", FALSE);
   return FAIL;
   }
 
@@ -1806,7 +1867,7 @@ mode, the fflush() happens when smtp_getc() is called. */
 
 if (!state->tlsp->on_connect)
   {
-  smtp_printf("220 TLS go ahead\r\n");
+  smtp_printf("220 TLS go ahead\r\n", FALSE);
   fflush(smtp_out);
   }
 
@@ -2288,6 +2349,14 @@ if (n > 0)
 }
 
 
+BOOL
+tls_could_read(void)
+{
+return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
+ || gnutls_record_check_pending(state_server.session) > 0;
+}
+
+
 
 
 /*************************************************
@@ -2347,19 +2416,27 @@ Arguments:
   is_server channel specifier
   buff      buffer of data
   len       number of bytes
+  more     more data expected soon
 
 Returns:    the number of bytes after a successful write,
             -1 after a failed write
 */
 
 int
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more)
 {
 ssize_t outbytes;
 size_t left = len;
 exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+#ifdef SUPPORT_CORK
+static BOOL corked = FALSE;
+
+if (more && !corked) gnutls_record_cork(state->session);
+#endif
+
+DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
+  buff, left, more ? ", more" : "");
 
-DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left);
 while (left > 0)
   {
   DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
@@ -2390,6 +2467,14 @@ if (len > INT_MAX)
   len = INT_MAX;
   }
 
+#ifdef SUPPORT_CORK
+if (more != corked)
+  {
+  if (!more) (void) gnutls_record_uncork(state->session, 0);
+  corked = more;
+  }
+#endif
+
 return (int) len;
 }