Support REQUIRETLS
[exim.git] / src / src / transports / smtp.c
index ae4385a052162eb2120406828a667139e4db223e..08d1810d6990dc8e7bbf71f8c0485b05ae313816 100644 (file)
@@ -1336,8 +1336,8 @@ return Ustrcmp(current_local_identity, message_local_identity) == 0;
 
 
 
-static uschar
-ehlo_response(uschar * buf, uschar checks)
+static unsigned
+ehlo_response(uschar * buf, unsigned checks)
 {
 size_t bsize = Ustrlen(buf);
 
@@ -1345,6 +1345,12 @@ size_t bsize = Ustrlen(buf);
 if (  checks & OPTION_TLS
    && pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
   checks &= ~OPTION_TLS;
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if (  checks & OPTION_REQUIRETLS
+   && pcre_exec(regex_REQUIRETLS, NULL, CS buf,bsize, 0, PCRE_EOPT, NULL,0) < 0)
+  checks &= ~OPTION_REQUIRETLS;
+# endif
 #endif
 
 if (  checks & OPTION_IGNQ
@@ -1533,7 +1539,8 @@ sx->utf8_needed = FALSE;
 sx->dsn_all_lasthop = TRUE;
 #if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
 sx->dane = FALSE;
-sx->dane_required = verify_check_given_host(&sx->ob->hosts_require_dane, sx->host) == OK;
+sx->dane_required =
+  verify_check_given_host(&sx->ob->hosts_require_dane, sx->host) == OK;
 #endif
 
 if ((sx->max_rcpt = sx->tblock->max_addresses) == 0) sx->max_rcpt = 999999;
@@ -2077,11 +2084,18 @@ have one. */
 else if (  sx->smtps
 # ifdef SUPPORT_DANE
        || sx->dane
+# endif
+# ifdef EXPERIMENTAL_REQUIRETLS
+       || tls_requiretls & REQUIRETLS_MSG
 # endif
        || verify_check_given_host(&sx->ob->hosts_require_tls, sx->host) == OK
        )
   {
-  errno = ERRNO_TLSREQUIRED;
+  errno =
+# ifdef EXPERIMENTAL_REQUIRETLS
+      tls_requiretls & REQUIRETLS_MSG ? ERRNO_REQUIRETLS :
+# endif
+      ERRNO_TLSREQUIRED;
   message = string_sprintf("a TLS session is required, but %s",
     smtp_peer_options & OPTION_TLS
     ? "an attempt to start TLS failed" : "the server did not offer TLS support");
@@ -2122,6 +2136,9 @@ if (continue_hostname == NULL
        | OPTION_DSN
        | OPTION_PIPE
        | (sx->ob->size_addition >= 0 ? OPTION_SIZE : 0)
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+       | (tls_requiretls & REQUIRETLS_MSG ? OPTION_REQUIRETLS : 0)
+#endif
       );
 
     /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
@@ -2166,6 +2183,16 @@ if (continue_hostname == NULL
     DEBUG(D_transport) debug_printf("%susing DSN\n",
                        sx->peer_offered & OPTION_DSN ? "" : "not ");
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+    if (sx->peer_offered & OPTION_REQUIRETLS)
+      {
+      smtp_peer_options |= OPTION_REQUIRETLS;
+      DEBUG(D_transport) debug_printf(
+       tls_requiretls & REQUIRETLS_MSG
+       ? "using REQUIRETLS\n" : "REQUIRETLS offered\n");
+      }
+#endif
+
     /* Note if the response to EHLO specifies support for the AUTH extension.
     If it has, check that this host is one we want to authenticate to, and do
     the business. The host name and address must be available when the
@@ -2206,6 +2233,22 @@ if (sx->utf8_needed && !(sx->peer_offered & OPTION_UTF8))
   }
 #endif
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+  /*XXX should tls_requiretls actually be per-addr? */
+
+if (  tls_requiretls & REQUIRETLS_MSG
+   && !(sx->peer_offered & OPTION_REQUIRETLS)
+   )
+  {
+  sx->setting_up = TRUE;
+  errno = ERRNO_REQUIRETLS;
+  message = US"REQUIRETLS support is required from the server"
+    " but it was not offered";
+  DEBUG(D_transport) debug_printf("%s\n", message);
+  goto TLS_FAILED;
+  }
+#endif
+
 return OK;
 
 
@@ -2216,6 +2259,7 @@ return OK;
     message = NULL;
     sx->send_quit = check_response(sx->host, &errno, sx->addrlist->more_errno,
       sx->buffer, &code, &message, &pass_message);
+    yield = DEFER;
     goto FAILED;
 
   SEND_FAILED:
@@ -2223,6 +2267,7 @@ return OK;
     message = US string_sprintf("send() to %s [%s] failed: %s",
       sx->host->name, sx->host->address, strerror(errno));
     sx->send_quit = FALSE;
+    yield = DEFER;
     goto FAILED;
 
   EHLOHELO_FAILED:
@@ -2230,6 +2275,7 @@ return OK;
     message = string_sprintf("Remote host closed connection in response to %s"
       " (EHLO response was: %s)", smtp_command, sx->buffer);
     sx->send_quit = FALSE;
+    yield = DEFER;
     goto FAILED;
 
   /* This label is jumped to directly when a TLS negotiation has failed,
@@ -2239,7 +2285,13 @@ return OK;
 
 #ifdef SUPPORT_TLS
   TLS_FAILED:
-    code = '4';
+# ifdef EXPERIMENTAL_REQUIRETLS
+    if (errno == ERRNO_REQUIRETLS)
+      code = '5', yield = FAIL;
+      /*XXX DSN will be labelled 500; prefer 530 5.7.4 */
+    else
+# endif
+      code = '4', yield = DEFER;
     goto FAILED;
 #endif
 
@@ -2272,7 +2324,6 @@ FAILED:
            , sx->smtp_greeting, sx->helo_response
 #endif
            );
-  yield = DEFER;
   }
 
 
@@ -2378,6 +2429,11 @@ if (  sx->peer_offered & OPTION_UTF8
   Ustrcpy(p, " SMTPUTF8"), p += 9;
 #endif
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+  Ustrcpy(p, " REQUIRETLS") , p += 11;
+#endif
+
 /* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
 for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
      addr && address_count < sx->max_rcpt;
@@ -3862,6 +3918,12 @@ same one in order to be passed to a single transport - or if the transport has
 a host list with hosts_override set, use the host list supplied with the
 transport. It is an error for this not to exist. */
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+  ob->tls_tempfail_tryclear = FALSE;   /*XXX surely we should have a local for this
+                                       rather than modifying the transport? */
+#endif
+
 if (!hostlist || (ob->hosts_override && ob->hosts))
   {
   if (!ob->hosts)