TLS resumption: support Outlook hosts-behind-loadbalancer
[exim.git] / src / src / tls.c
index c088c7d85ff25214c21976d91d28915695b3ff76..e80dd9aaf299e9fff2f713d7e9b25362d6b8fc2e 100644 (file)
@@ -3,7 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is
@@ -25,6 +25,11 @@ functions from the OpenSSL or GNU TLS libraries. */
 #endif
 
 
+/* Forward decl. */
+static void tls_client_resmption_key(tls_support *, smtp_connect_args *,
+  smtp_transport_options_block *);
+
+
 #if defined(MACRO_PREDEF) && !defined(DISABLE_TLS)
 # include "macro_predef.h"
 # ifdef USE_GNUTLS
@@ -158,11 +163,12 @@ return FALSE;
 # endif
 # ifdef EXIM_HAVE_KEVENT
 {
-uschar * s;
-int fd1, fd2, i, cnt = 0;
+uschar * s, * t;
+int fd1, fd2, i, j, cnt = 0;
 struct stat sb;
 #ifdef OpenBSD
 struct kevent k_dummy;
+struct timespec ts = {0};
 #endif
 
 errno = 0;
@@ -184,8 +190,8 @@ for (;;)
     {
     if ((fd1 = open(CCS filename, O_RDONLY | O_NOFOLLOW)) < 0)
       { s = US"open file"; goto bad; }
-    DEBUG(D_tls) debug_printf("watch file '%s'\n", filename);
-    EV_SET(&kev[++kev_used],
+    DEBUG(D_tls) debug_printf("watch file '%s':\t%d\n", filename, fd1);
+    EV_SET(&kev[kev_used++],
        (uintptr_t)fd1,
        EVFILT_VNODE,
        EV_ADD | EV_ENABLE | EV_ONESHOT,
@@ -195,8 +201,8 @@ for (;;)
        NULL);
     cnt++;
     }
-  DEBUG(D_tls) debug_printf("watch dir  '%s'\n", s);
-  EV_SET(&kev[++kev_used],
+  DEBUG(D_tls) debug_printf("watch dir  '%s':\t%d\n", s, fd2);
+  EV_SET(&kev[kev_used++],
        (uintptr_t)fd2,
        EVFILT_VNODE,
        EV_ADD | EV_ENABLE | EV_ONESHOT,
@@ -208,21 +214,23 @@ for (;;)
 
   if (!(S_ISLNK(sb.st_mode))) break;
 
-  s = store_get(1024, FALSE);
-  if ((i = readlink(CCS filename, (void *)s, 1024)) < 0) { s = US"readlink"; goto bad; }
-  filename = s;
-  *(s += i) = '\0';
-  store_release_above(s+1);
+  t = store_get(1024, GET_UNTAINTED);
+  Ustrncpy(t, s, 1022);
+  j = Ustrlen(s);
+  t[j++] = '/';
+  if ((i = readlink(CCS filename, (void *)(t+j), 1023-j)) < 0) { s = US"readlink"; goto bad; }
+  filename = t;
+  *(t += i+j) = '\0';
+  store_release_above(t+1);
   }
 
-if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt,
 #ifdef OpenBSD
-           &k_dummy, 1,
+if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, &k_dummy, 1, &ts) >= 0)
+  return TRUE;
 #else
-           NULL, 0,
-#endif
-           NULL) >= 0)
+if (kevent(tls_watch_fd, &kev[kev_used-cnt], cnt, NULL, 0, NULL) >= 0)
   return TRUE;
+#endif
 s = US"kevent";
 
 bad:
@@ -317,6 +325,7 @@ if (tls_watch_fd < 0) return;
 /* Close the files we had open for kevent */
 for (int i = 0; i < kev_used; i++)
   {
+  DEBUG(D_tls) debug_printf("closing watch fd: %d\n", (int) kev[i].ident);
   (void) close((int) kev[i].ident);
   kev[i].ident = (uintptr_t)-1;
   }
@@ -356,11 +365,18 @@ opt_unset_or_noexpand(const uschar * opt)
 
 
 
-/* Called every time round the daemon loop */
+/* Called every time round the daemon loop.
 
-void
+If we reloaded fd-watcher, return the old watch fd
+having modified the global for the new one. Otherwise
+return -1.
+*/
+
+int
 tls_daemon_tick(void)
 {
+int old_watch_fd = tls_watch_fd;
+
 tls_per_lib_daemon_tick();
 #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 if (tls_creds_expire && time(NULL) >= tls_creds_expire)
@@ -372,6 +388,7 @@ if (tls_creds_expire && time(NULL) >= tls_creds_expire)
   DEBUG(D_tls) debug_printf("selfsign cert rotate\n");
   tls_creds_expire = 0;
   tls_daemon_creds_reload();
+  return old_watch_fd;
   }
 else if (tls_watch_trigger_time && time(NULL) >= tls_watch_trigger_time + 5)
   {
@@ -383,8 +400,10 @@ else if (tls_watch_trigger_time && time(NULL) >= tls_watch_trigger_time + 5)
   DEBUG(D_tls) debug_printf("watch triggered\n");
   tls_watch_trigger_time = tls_creds_expire = 0;
   tls_daemon_creds_reload();
+  return old_watch_fd;
   }
 #endif
+return -1;
 }
 
 /* Called once at daemon startup */
@@ -457,6 +476,9 @@ Returns:       the character
 int
 tls_ungetc(int ch)
 {
+if (ssl_xfer_buffer_lwm <= 0)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in tls_ungetc");
+
 ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch;
 return ch;
 }
@@ -669,7 +691,6 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
 return FALSE;
 }
 
-
 /* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment
 and writes a file by that name.  Our OpenSSL code does the same, using keying
 info from the library API.
@@ -775,6 +796,43 @@ return status == 0;
 
 
 
+static void
+tls_client_resmption_key(tls_support * tlsp, smtp_connect_args * conn_args,
+  smtp_transport_options_block * ob)
+{
+hctx * h = &tlsp->resume_hctx;
+blob b;
+gstring * g;
+
+DEBUG(D_tls) if (conn_args->host_lbserver)
+  debug_printf("TLS: lbserver '%s'\n", conn_args->host_lbserver);
+
+#ifdef EXIM_HAVE_SHA2
+exim_sha_init(h, HASH_SHA2_256);
+#else
+exim_sha_init(h, HASH_SHA1);
+#endif
+exim_sha_update_string(h, conn_args->host_lbserver);
+#ifdef SUPPORT_DANE
+if (conn_args->dane)
+  exim_sha_update(h,  CUS &conn_args->tlsa_dnsa, sizeof(dns_answer));
+#endif
+exim_sha_update_string(h, conn_args->host->address);
+exim_sha_update(h,   CUS &conn_args->host->port, sizeof(conn_args->host->port));
+exim_sha_update_string(h, conn_args->sending_ip_address);
+exim_sha_update_string(h, openssl_options);
+exim_sha_update_string(h, ob->tls_require_ciphers);
+exim_sha_update_string(h, tlsp->sni);
+#ifdef EXIM_HAVE_ALPN
+exim_sha_update_string(h, ob->tls_alpn);
+#endif
+exim_sha_finish(h, &b);
+for (g = string_get(b.len*2+1); b.len-- > 0; )
+  g = string_fmt_append(g, "%02x", *b.data++);
+tlsp->resume_index = string_from_gstring(g);
+DEBUG(D_tls) debug_printf("TLS: resume session index %s\n", tlsp->resume_index);
+}
+
 #endif /*!DISABLE_TLS*/
 #endif /*!MACRO_PREDEF*/