deliver
[exim.git] / src / src / tls-gnu.c
index 46547d7327cf03595d73495e24727b6599c684b3..2d7041f3e4077e41a4cee7e8f0061c2b3266f0cb 100644 (file)
@@ -119,6 +119,12 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 # endif
 #endif
 
+#if GNUTLS_VERSION_NUMBER >= 0x030200
+# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE
+#  define EXIM_HAVE_ALPN
+# endif
+#endif
+
 #ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
 #endif
@@ -278,10 +284,14 @@ static BOOL gnutls_buggy_ocsp = FALSE;
 static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
 #endif
 
+#ifdef EXIM_HAVE_ALPN
+static BOOL server_seen_alpn = FALSE;
+#endif
 #ifdef EXIM_HAVE_TLS_RESUME
 static gnutls_datum_t server_sessticket_key;
 #endif
 
+
 /* ------------------------------------------------------------------------ */
 /* macros */
 
@@ -851,7 +861,7 @@ if (rc < 0)
     return tls_error(US"Filename too long to generate replacement",
         filename, NULL, errstr);
 
-  temp_fn = string_copy(US"%s.XXXXXXX");
+  temp_fn = string_copy(US"exim-dh.XXXXXXX");
   if ((fd = mkstemp(CS temp_fn)) < 0)  /* modifies temp_fn */
     return tls_error_sys(US"Unable to open temp file", errno, NULL, errstr);
   (void)exim_chown(temp_fn, exim_uid, exim_gid);   /* Probably not necessary */
@@ -1062,10 +1072,18 @@ tls_server_clienthello_ext(void * ctx, unsigned tls_id,
   const unsigned char *data, unsigned size)
 {
 /* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
-if (tls_id == 5)       /* status_request */
+switch (tls_id)
   {
-  DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
-  tls_in.ocsp = OCSP_NOT_RESP;
+  case 5:      /* status_request */
+    DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
+    tls_in.ocsp = OCSP_NOT_RESP;
+    break;
+#ifdef EXIM_HAVE_ALPN
+  case 16:     /* Application Layer Protocol Notification */
+    DEBUG(D_tls) debug_printf("Seen ALPN extension from client\n");
+    server_seen_alpn = TRUE;
+    break;
+#endif
   }
 return 0;
 }
@@ -1126,12 +1144,12 @@ tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype,
   unsigned when, unsigned int incoming, const gnutls_datum_t * msg)
 {
 DEBUG(D_tls) debug_printf("Sending certificate-status\n");             /*XXX we get this for tls1.2 but not for 1.3 */
-#ifdef SUPPORT_SRV_OCSP_STACK
+# ifdef SUPPORT_SRV_OCSP_STACK
 tls_in.ocsp = exim_testharness_disable_ocsp_validity_check
   ? OCSP_VFY_NOT_TRIED : OCSP_VFIED;   /* We know that GnuTLS verifies responses */
-#else
+# else
 tls_in.ocsp = OCSP_VFY_NOT_TRIED;
-#endif
+# endif
 return 0;
 }
 
@@ -2791,6 +2809,26 @@ if (gnutls_session_is_resumed(state->session))
   }
 }
 #endif
+
+
+#ifdef EXIM_HAVE_ALPN
+static void
+tls_server_set_acceptable_alpns(exim_gnutls_state_st * state)
+{
+int rc;
+gnutls_datum_t protocols[2] = {[0] = {.data = US"smtp", .size = 4},
+                              [1] = {.data = US"esmtp", .size = 5}};
+
+/* Set non-mandatory set of protocol names */
+if (!(rc = gnutls_alpn_set_protocols(state->session, protocols, 2, 0)))
+  gnutls_handshake_set_hook_function(state->session,
+    GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
+else
+  DEBUG(D_tls)
+    debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
+}
+#endif
+
 /* ------------------------------------------------------------------------ */
 /* Exported functions */
 
@@ -2847,6 +2885,10 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
 #endif
   }
 
+#ifdef EXIM_HAVE_ALPN
+tls_server_set_acceptable_alpns(state);
+#endif
+
 #ifdef EXIM_HAVE_TLS_RESUME
 tls_server_resume_prehandshake(state);
 #endif
@@ -2962,6 +3004,32 @@ tls_server_resume_posthandshake(state);
 
 DEBUG(D_tls) post_handshake_debug(state);
 
+#ifdef EXIM_HAVE_ALPN
+if (server_seen_alpn)
+  {
+  /* The client offered ALPN.  We were set up with a nonmandatory list;
+  see what was negotiated.  We require a match now, given that something
+  was offered. */
+  gnutls_datum_t p = {.size = 0};
+  int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
+  if (!rc || rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+    {
+    if (p.size == 0)
+      {
+      *errstr = US"ALPN rejected";
+      return FAIL;
+      }
+    else
+      DEBUG(D_tls)
+       debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data);
+    }
+  else
+    DEBUG(D_tls)
+      debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc));
+
+  }
+#endif
+
 /* Verify after the fact */
 
 if (!verify_certificate(state, errstr))
@@ -3489,6 +3557,25 @@ return TRUE;
 
 
 
+/*
+Arguments:
+  ct_ctx       client TLS context pointer, or NULL for the one global server context
+*/
+
+void
+tls_shutdown_wr(void * ct_ctx)
+{
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
+tls_support * tlsp = state->tlsp;
+
+if (!tlsp || tlsp->active.sock < 0) return;  /* TLS was not active */
+
+tls_write(ct_ctx, NULL, 0, FALSE);     /* flush write buffer */
+
+HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent("  SMTP(TLS shutdown)>>\n");
+gnutls_bye(state->session, GNUTLS_SHUT_WR);
+}
+
 /*************************************************
 *         Close down a TLS session               *
 *************************************************/
@@ -3499,29 +3586,30 @@ would tamper with the TLS session in the parent process).
 
 Arguments:
   ct_ctx       client context pointer, or NULL for the one global server context
-  shutdown     1 if TLS close-alert is to be sent,
-               2 if also response to be waited for
+  do_shutdown  0 no data-flush or TLS close-alert
+               1 if TLS close-alert is to be sent,
+               2 if also response to be waited for (2s timeout)
 
 Returns:     nothing
 */
 
 void
-tls_close(void * ct_ctx, int shutdown)
+tls_close(void * ct_ctx, int do_shutdown)
 {
 exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
 tls_support * tlsp = state->tlsp;
 
 if (!tlsp || tlsp->active.sock < 0) return;  /* TLS was not active */
 
-tls_write(ct_ctx, NULL, 0, FALSE);     /* flush write buffer */
-
-if (shutdown)
+if (do_shutdown)
   {
   DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
-    shutdown > 1 ? " (with response-wait)" : "");
+    do_shutdown > 1 ? " (with response-wait)" : "");
+
+  tls_write(ct_ctx, NULL, 0, FALSE);   /* flush write buffer */
 
   ALARM(2);
-  gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+  gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
   ALARM_CLR(0);
   }
 
@@ -3669,7 +3757,7 @@ return buf;
 
 
 void
-tls_get_cache()
+tls_get_cache(void)
 {
 #ifndef DISABLE_DKIM
 exim_gnutls_state_st * state = &state_server;
@@ -3688,8 +3776,6 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
 }
 
 
-
-
 /*************************************************
 *          Read bytes from TLS channel           *
 *************************************************/