TLS authenticator
authorJeremy Harris <jgh146exb@wizmail.org>
Thu, 4 Jun 2015 19:28:25 +0000 (20:28 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Thu, 4 Jun 2015 20:54:52 +0000 (21:54 +0100)
19 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/scripts/MakeLinks
src/src/EDITME
src/src/auths/Makefile
src/src/auths/tls.c [new file with mode: 0644]
src/src/auths/tls.h [new file with mode: 0644]
src/src/config.h.defaults
src/src/drtables.c
src/src/exim.c
src/src/smtp_in.c
src/src/tls-openssl.c
src/src/tlscert-openssl.c
test/confs/3700 [new file with mode: 0644]
test/log/3700 [new file with mode: 0644]
test/runtest
test/scripts/3700-TLS-auth/3700 [new file with mode: 0644]
test/scripts/3700-TLS-auth/REQUIRES [new file with mode: 0644]

index 667857a..4e561f2 100644 (file)
@@ -24866,6 +24866,7 @@ AUTH_GSASL=yes
 AUTH_HEIMDAL_GSSAPI=yes
 AUTH_PLAINTEXT=yes
 AUTH_SPA=yes
+AUTH_TLS=yes
 .endd
 in &_Local/Makefile_&, respectively. The first of these supports the CRAM-MD5
 authentication mechanism (RFC 2195), and the second provides an interface to
@@ -24880,6 +24881,8 @@ The sixth can be configured to support
 the PLAIN authentication mechanism (RFC 2595) or the LOGIN mechanism, which is
 not formally documented, but used by several MUAs. The seventh authenticator
 supports Microsoft's &'Secure Password Authentication'& mechanism.
+The eighth is an Exim authenticator but not an SMTP one;
+instead it can use information from a TLS negotiation.
 
 The authenticators are configured using the same syntax as other drivers (see
 section &<<SECTfordricon>>&). If no authenticators are required, no
@@ -26089,6 +26092,81 @@ msn:
 . ////////////////////////////////////////////////////////////////////////////
 . ////////////////////////////////////////////////////////////////////////////
 
+.new
+.chapter "The tls authenticator" "CHAPtlsauth"
+.scindex IIDtlsauth1 "&(tls)& authenticator"
+.scindex IIDtlsauth2 "authenticators" "&(tls)&"
+.cindex "authentication" "Client Certificate"
+.cindex "authentication" "X509"
+.cindex "Certificate-based authentication"
+The &(tls)& authenticator provides server support for
+authentication based on client certificates.
+
+It is not an SMTP authentication mechanism and is not
+advertised by the server as part of the SMTP EHLO response.
+It is an Exim authenticator in the sense that it affects
+the protocol element of the log line, can be tested for
+by the &%authenticated%& ACL condition, and can set
+the &$authenticated_id$& variable.
+
+The client must present a verifiable certificate,
+for which it must have been requested via the
+&%tls_verify_hosts%& or &%tls_try_verify_hosts%& main options
+(see &<<CHAPTLS>>&).
+
+If an authenticator of this type is configured it is
+run before any SMTP-level communication is done,
+and can authenticate the connection.
+If it does, SMTP suthentication is not offered.
+
+A maximum of one authenticator of this type may be present.
+
+
+.cindex "options" "&(tls)& authenticator (server)"
+The &(tls)& authenticator has three server options:
+
+.option server_param1 tls string&!! unset
+.cindex "variables (&$auth1$& &$auth2$& etc)" "in &(tls)& authenticator"
+This option is expanded after the TLS negotiation and
+the result is placed in &$auth1$&.
+If the expansion is forced to fail, authentication fails. Any other expansion
+failure causes a temporary error code to be returned.
+
+.option server_param2 tls string&!! unset
+.option server_param3 tls string&!! unset
+As above, for &$auth2$& and &$auth3$&.
+
+&%server_param1%& may also be spelled &%server_param%&.
+
+
+Example:
+.code
+tls:
+  driver = tls
+  server_param1 =     ${certextract {subj_altname,mail,>:} \
+                                    {$tls_in_peercert}}
+  server_condition =  ${if forany {$auth1} \
+                            {!= {0} \
+                                {${lookup ldap{ldap:///\
+                         mailname=${quote_ldap_dn:${lc:$item}},\
+                         ou=users,LDAP_DC?mailid} {$value}{0} \
+                       }    }   } }
+  server_set_id =     ${if = {1}{${listcount:$auth1}} {$auth1}{}}
+.endd
+.ecindex IIDtlsauth1
+.ecindex IIDtlsauth2
+.wen
+
+
+Note that because authentication is traditionally an SMTP operation,
+the &%authenticated%& ACL condition cannot be used in
+a connect- or helo-ACL.
+
+
+
+. ////////////////////////////////////////////////////////////////////////////
+. ////////////////////////////////////////////////////////////////////////////
+
 .chapter "Encrypted SMTP connections using TLS/SSL" "CHAPTLS" &&&
          "Encrypted SMTP connections"
 .scindex IIDencsmtp1 "encryption" "on SMTP connection"
index 09f5c60..a5b4904 100644 (file)
@@ -13,7 +13,7 @@ JH/03 The smtp transport now requests PRDR by default, if the server offers
       it.
 
 JH/04 Certificate name checking on server certificates, when exim is a client,
-      is now done by default.  The transport option tls_verify_cert_hostname
+      is now done by default.  The transport option tls_verify_cert_hostnames
       can be used to disable this per-host.  The build option
       EXPERIMENTAL_CERTNAMES is withdrawn.
 
index 3fe3325..e10a32b 100644 (file)
@@ -31,10 +31,12 @@ Version 4.86
  9. If built with EXPERIMENTAL_INTERNATIONAL, an expansion item for a commonly
     used encoding of Maildir folder names.
 
-10. A logging option for slow DNS lookups,
+10. A logging option for slow DNS lookups.
 
 11. New ${env {<variable>}} expansion.
 
+12. A non-SMTP authenticator using information from TLS client certificates.
+
 
 Version 4.85
 ------------
index d8c3f1c..ea62650 100755 (executable)
@@ -73,7 +73,7 @@ for f in README Makefile b64encode.c b64decode.c call_pam.c call_pwcheck.c \
   gsasl_exim.h get_data.c get_no64_data.c heimdal_gssapi.c heimdal_gssapi.h \
   md5.c xtextencode.c xtextdecode.c cram_md5.c cram_md5.h plaintext.c plaintext.h \
   pwcheck.c pwcheck.h auth-spa.c auth-spa.h dovecot.c dovecot.h sha1.c spa.c \
-  spa.h
+  spa.h tls.c tls.h
 do
   ln -s ../../src/auths/$f $f
 done
index 784c677..d481122 100644 (file)
@@ -637,6 +637,7 @@ FIXED_NEVER_USERS=root
 # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi
 # AUTH_PLAINTEXT=yes
 # AUTH_SPA=yes
+# AUTH_TLS=yes
 
 
 #------------------------------------------------------------------------------
index c6ef218..45d2949 100644 (file)
@@ -9,7 +9,7 @@ OBJ = auth-spa.o b64decode.o b64encode.o call_pam.o call_pwcheck.o \
       call_radius.o check_serv_cond.o cram_md5.o cyrus_sasl.o dovecot.o \
       get_data.o get_no64_data.o gsasl_exim.o heimdal_gssapi.o \
       md5.o plaintext.o pwcheck.o sha1.o \
-      spa.o xtextdecode.o xtextencode.o
+      spa.o tls.o xtextdecode.o xtextencode.o
 
 auths.a:         $(OBJ)
                 @$(RM_COMMAND) -f auths.a
@@ -43,5 +43,6 @@ gsasl_exim.o:       $(HDRS) gsasl_exim.c gsasl_exim.h
 heimdal_gssapi.o:   $(HDRS) heimdal_gssapi.c heimdal_gssapi.h
 plaintext.o:        $(HDRS) plaintext.c plaintext.h
 spa.o:              $(HDRS) spa.c spa.h
+tls.o:              $(HDRS) tls.c tls.h
 
 # End
diff --git a/src/src/auths/tls.c b/src/src/auths/tls.c
new file mode 100644 (file)
index 0000000..51c096c
--- /dev/null
@@ -0,0 +1,80 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2015 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* This file provides an Exim authenticator driver for
+a server to verify a client SSL certificate
+*/
+
+
+#include "../exim.h"
+#include "tls.h"
+
+/* Options specific to the tls authentication mechanism. */
+
+optionlist auth_tls_options[] = {
+  { "server_param",     opt_stringptr,
+      (void *)(offsetof(auth_tls_options_block, server_param1)) },
+  { "server_param1",    opt_stringptr,
+      (void *)(offsetof(auth_tls_options_block, server_param1)) },
+  { "server_param2",    opt_stringptr,
+      (void *)(offsetof(auth_tls_options_block, server_param2)) },
+  { "server_param3",    opt_stringptr,
+      (void *)(offsetof(auth_tls_options_block, server_param3)) },
+};
+
+/* Size of the options list. An extern variable has to be used so that its
+address can appear in the tables drtables.c. */
+
+int auth_tls_options_count = nelem(auth_tls_options);
+
+/* Default private options block for the authentication method. */
+
+auth_tls_options_block auth_tls_option_defaults = {
+    NULL,      /* server_param1 */
+    NULL,      /* server_param2 */
+    NULL,      /* server_param3 */
+};
+
+
+/*************************************************
+*          Initialization entry point            *
+*************************************************/
+
+/* Called for each instance, after its options have been read, to
+enable consistency checks to be done, or anything else that needs
+to be set up. */
+
+void
+auth_tls_init(auth_instance *ablock)
+{
+ablock->public_name = ablock->name;    /* needed for core code */
+}
+
+
+
+/*************************************************
+*             Server entry point                 *
+*************************************************/
+
+/* For interface, see auths/README */
+
+int
+auth_tls_server(auth_instance *ablock, uschar *data)
+{
+auth_tls_options_block * ob = (auth_tls_options_block *)ablock->options_block;
+
+if (ob->server_param1)
+  auth_vars[expand_nmax++] = expand_string(ob->server_param1);
+if (ob->server_param2)
+  auth_vars[expand_nmax++] = expand_string(ob->server_param2);
+if (ob->server_param2)
+  auth_vars[expand_nmax++] = expand_string(ob->server_param3);
+return auth_check_serv_cond(ablock);
+}
+
+
+/* End of tls.c */
diff --git a/src/src/auths/tls.h b/src/src/auths/tls.h
new file mode 100644 (file)
index 0000000..bf2a2a1
--- /dev/null
@@ -0,0 +1,30 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2015 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Private structure for the private options. */
+
+typedef struct {
+  uschar * server_param1;
+  uschar * server_param2;
+  uschar * server_param3;
+} auth_tls_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist auth_tls_options[];
+extern int auth_tls_options_count;
+
+/* Block containing default values. */
+
+extern auth_tls_options_block auth_tls_option_defaults;
+
+/* The entry points for the mechanism */
+
+extern void auth_tls_init(auth_instance *);
+extern int auth_tls_server(auth_instance *, uschar *);
+
+/* End of sa.h */
index d31f115..ec53518 100644 (file)
@@ -24,6 +24,7 @@ it's a default value. */
 #define AUTH_HEIMDAL_GSSAPI
 #define AUTH_PLAINTEXT
 #define AUTH_SPA
+#define AUTH_TLS
 
 #define AUTH_VARS                     3
 
index c2d8668..5758a92 100644 (file)
@@ -53,6 +53,10 @@ set to NULL for those that are not compiled into the binary. */
 #include "auths/spa.h"
 #endif
 
+#ifdef AUTH_TLS
+#include "auths/tls.h"
+#endif
+
 auth_info auths_available[] = {
 
 /* Checking by an expansion condition on plain text */
@@ -155,6 +159,20 @@ auth_info auths_available[] = {
   },
 #endif
 
+#ifdef AUTH_TLS
+  {
+  US"tls",                                   /* lookup name */
+  auth_tls_options,
+  &auth_tls_options_count,
+  &auth_tls_option_defaults,
+  sizeof(auth_tls_options_block),
+  auth_tls_init,                             /* init function */
+  auth_tls_server,                           /* server function */
+  NULL,                                      /* client function */
+  NULL                                       /* diagnostic function */
+  },
+#endif
+
 { US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL  }
 };
 
index 3eca43b..81bc51e 100644 (file)
@@ -937,6 +937,9 @@ fprintf(f, "Authenticators:");
 #ifdef AUTH_SPA
   fprintf(f, " spa");
 #endif
+#ifdef AUTH_TLS
+  fprintf(f, " tls");
+#endif
 fprintf(f, "\n");
 
 fprintf(f, "Routers:");
index b451c48..fc3d34c 100644 (file)
@@ -71,6 +71,7 @@ enum {
   VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */
   ETRN_CMD,                     /* This by analogy with TURN from the RFC */
   STARTTLS_CMD,                 /* Required by the STARTTLS RFC */
+  TLS_AUTH_CMD,                        /* auto-command at start of SSL */
 
   /* This is a dummy to identify the non-sync commands when pipelining */
 
@@ -169,6 +170,7 @@ static smtp_cmd_list cmd_list[] = {
   { "auth",       sizeof("auth")-1,       AUTH_CMD, TRUE,  TRUE  },
   #ifdef SUPPORT_TLS
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
+  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, TRUE },
   #endif
 
 /* If you change anything above here, also fix the definitions below. */
@@ -192,6 +194,7 @@ static smtp_cmd_list *cmd_list_end =
 #define CMD_LIST_EHLO      2
 #define CMD_LIST_AUTH      3
 #define CMD_LIST_STARTTLS  4
+#define CMD_LIST_TLS_AUTH  5
 
 /* This list of names is used for performing the smtp_no_mail logging action.
 It must be kept in step with the SCH_xxx enumerations. */
@@ -3094,6 +3097,113 @@ smtp_respond(code, len, TRUE, user_msg);
 
 
 
+static int
+smtp_in_auth(auth_instance *au, uschar ** s, uschar ** ss)
+{
+const uschar *set_id = NULL;
+int rc, i;
+
+/* Run the checking code, passing the remainder of the command line as
+data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
+it as the only set numerical variable. The authenticator may set $auth<n>
+and also set other numeric variables. The $auth<n> variables are preferred
+nowadays; the numerical variables remain for backwards compatibility.
+
+Afterwards, have a go at expanding the set_id string, even if
+authentication failed - for bad passwords it can be useful to log the
+userid. On success, require set_id to expand and exist, and put it in
+authenticated_id. Save this in permanent store, as the working store gets
+reset at HELO, RSET, etc. */
+
+for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
+expand_nmax = 0;
+expand_nlength[0] = 0;   /* $0 contains nothing */
+
+rc = (au->info->servercode)(au, smtp_cmd_data);
+if (au->set_id) set_id = expand_string(au->set_id);
+expand_nmax = -1;        /* Reset numeric variables */
+for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
+
+/* The value of authenticated_id is stored in the spool file and printed in
+log lines. It must not contain binary zeros or newline characters. In
+normal use, it never will, but when playing around or testing, this error
+can (did) happen. To guard against this, ensure that the id contains only
+printing characters. */
+
+if (set_id) set_id = string_printing(set_id);
+
+/* For the non-OK cases, set up additional logging data if set_id
+is not empty. */
+
+if (rc != OK)
+  set_id = set_id && *set_id
+    ? string_sprintf(" (set_id=%s)", set_id) : US"";
+
+/* Switch on the result */
+
+switch(rc)
+  {
+  case OK:
+  if (!au->set_id || set_id)    /* Complete success */
+    {
+    if (set_id) authenticated_id = string_copy_malloc(set_id);
+    sender_host_authenticated = au->name;
+    authentication_failed = FALSE;
+    authenticated_fail_id = NULL;   /* Impossible to already be set? */
+
+    received_protocol =
+      (sender_host_address ? protocols : protocols_local)
+       [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
+    *s = *ss = US"235 Authentication succeeded";
+    authenticated_by = au;
+    break;
+    }
+
+  /* Authentication succeeded, but we failed to expand the set_id string.
+  Treat this as a temporary error. */
+
+  auth_defer_msg = expand_string_message;
+  /* Fall through */
+
+  case DEFER:
+  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+  *s = string_sprintf("435 Unable to authenticate at present%s",
+    auth_defer_user_msg);
+  *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
+    set_id, auth_defer_msg);
+  break;
+
+  case BAD64:
+  *s = *ss = US"501 Invalid base64 data";
+  break;
+
+  case CANCELLED:
+  *s = *ss = US"501 Authentication cancelled";
+  break;
+
+  case UNEXPECTED:
+  *s = *ss = US"553 Initial data not expected";
+  break;
+
+  case FAIL:
+  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+  *s = US"535 Incorrect authentication data";
+  *ss = string_sprintf("535 Incorrect authentication data%s", set_id);
+  break;
+
+  default:
+  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+  *s = US"435 Internal error";
+  *ss = string_sprintf("435 Internal error%s: return %d from authentication "
+    "check", set_id, rc);
+  break;
+  }
+
+return rc;
+}
+
+
+
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
@@ -3145,6 +3255,7 @@ cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
 #ifdef SUPPORT_TLS
 cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
+cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
 #endif
 
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
@@ -3168,7 +3279,6 @@ while (done <= 0)
   uschar *user_msg = NULL;
   uschar *recipient = NULL;
   uschar *hello = NULL;
-  const uschar *set_id = NULL;
   uschar *s, *ss;
   BOOL was_rej_mail = FALSE;
   BOOL was_rcpt = FALSE;
@@ -3181,6 +3291,41 @@ while (done <= 0)
   uschar *orcpt = NULL;
   int flags;
 
+#if defined(SUPPORT_TLS) && defined(AUTH_TLS)
+  /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
+  if (  tls_in.active >= 0
+     && tls_in.peercert
+     && tls_in.certificate_verified
+     && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
+     )
+    {
+    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
+    if (acl_smtp_auth)
+      {
+      rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg);
+      if (rc != OK)
+        {
+        done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
+        continue;
+        }
+      }
+
+    for (au = auths; au; au = au->next)
+      if (strcmpic(US"tls", au->driver_name) == 0)
+       {
+       smtp_cmd_data = NULL;
+
+       if ((c = smtp_in_auth(au, &s, &ss)) != OK)
+         log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+           au->name, host_and_ident(FALSE), ss);
+       else
+         DEBUG(D_auth) debug_printf("tls auth succeeded\n");
+
+       break;
+       }
+    }
+#endif
+
   switch(smtp_read_command(TRUE))
     {
     /* The AUTH command is not permitted to occur inside a transaction, and may
@@ -3208,13 +3353,13 @@ while (done <= 0)
         US"AUTH command used when not advertised");
       break;
       }
-    if (sender_host_authenticated != NULL)
+    if (sender_host_authenticated)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
         US"already authenticated");
       break;
       }
-    if (sender_address != NULL)
+    if (sender_address)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
         US"not permitted in mail transaction");
@@ -3223,7 +3368,7 @@ while (done <= 0)
 
     /* Check the ACL */
 
-    if (acl_smtp_auth != NULL)
+    if (acl_smtp_auth)
       {
       rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg);
       if (rc != OK)
@@ -3260,122 +3405,23 @@ while (done <= 0)
     as a server and which has been advertised (unless, sigh, allow_auth_
     unadvertised is set). */
 
-    for (au = auths; au != NULL; au = au->next)
-      {
+    for (au = auths; au; au = au->next)
       if (strcmpic(s, au->public_name) == 0 && au->server &&
-          (au->advertised || allow_auth_unadvertised)) break;
-      }
-
-    if (au == NULL)
-      {
-      done = synprot_error(L_smtp_protocol_error, 504, NULL,
-        string_sprintf("%s authentication mechanism not supported", s));
-      break;
-      }
-
-    /* Run the checking code, passing the remainder of the command line as
-    data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
-    it as the only set numerical variable. The authenticator may set $auth<n>
-    and also set other numeric variables. The $auth<n> variables are preferred
-    nowadays; the numerical variables remain for backwards compatibility.
-
-    Afterwards, have a go at expanding the set_id string, even if
-    authentication failed - for bad passwords it can be useful to log the
-    userid. On success, require set_id to expand and exist, and put it in
-    authenticated_id. Save this in permanent store, as the working store gets
-    reset at HELO, RSET, etc. */
-
-    for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
-    expand_nmax = 0;
-    expand_nlength[0] = 0;   /* $0 contains nothing */
-
-    c = (au->info->servercode)(au, smtp_cmd_data);
-    if (au->set_id != NULL) set_id = expand_string(au->set_id);
-    expand_nmax = -1;        /* Reset numeric variables */
-    for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
-
-    /* The value of authenticated_id is stored in the spool file and printed in
-    log lines. It must not contain binary zeros or newline characters. In
-    normal use, it never will, but when playing around or testing, this error
-    can (did) happen. To guard against this, ensure that the id contains only
-    printing characters. */
-
-    if (set_id != NULL) set_id = string_printing(set_id);
-
-    /* For the non-OK cases, set up additional logging data if set_id
-    is not empty. */
-
-    if (c != OK)
-      {
-      if (set_id != NULL && *set_id != 0)
-        set_id = string_sprintf(" (set_id=%s)", set_id);
-      else set_id = US"";
-      }
-
-    /* Switch on the result */
+          (au->advertised || allow_auth_unadvertised))
+       break;
 
-    switch(c)
+    if (au)
       {
-      case OK:
-      if (au->set_id == NULL || set_id != NULL)    /* Complete success */
-        {
-        if (set_id != NULL) authenticated_id = string_copy_malloc(set_id);
-        sender_host_authenticated = au->name;
-        authentication_failed = FALSE;
-        authenticated_fail_id = NULL;   /* Impossible to already be set? */
-
-        received_protocol =
-         (sender_host_address ? protocols : protocols_local)
-           [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
-        s = ss = US"235 Authentication succeeded";
-        authenticated_by = au;
-        break;
-        }
-
-      /* Authentication succeeded, but we failed to expand the set_id string.
-      Treat this as a temporary error. */
-
-      auth_defer_msg = expand_string_message;
-      /* Fall through */
-
-      case DEFER:
-      if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
-      s = string_sprintf("435 Unable to authenticate at present%s",
-        auth_defer_user_msg);
-      ss = string_sprintf("435 Unable to authenticate at present%s: %s",
-        set_id, auth_defer_msg);
-      break;
-
-      case BAD64:
-      s = ss = US"501 Invalid base64 data";
-      break;
-
-      case CANCELLED:
-      s = ss = US"501 Authentication cancelled";
-      break;
-
-      case UNEXPECTED:
-      s = ss = US"553 Initial data not expected";
-      break;
-
-      case FAIL:
-      if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
-      s = US"535 Incorrect authentication data";
-      ss = string_sprintf("535 Incorrect authentication data%s", set_id);
-      break;
+      c = smtp_in_auth(au, &s, &ss);
 
-      default:
-      if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
-      s = US"435 Internal error";
-      ss = string_sprintf("435 Internal error%s: return %d from authentication "
-        "check", set_id, c);
-      break;
+      smtp_printf("%s\r\n", s);
+      if (c != OK)
+       log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+         au->name, host_and_ident(FALSE), ss);
       }
-
-    smtp_printf("%s\r\n", s);
-    if (c != OK)
-      log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
-        au->name, host_and_ident(FALSE), ss);
+    else
+      done = synprot_error(L_smtp_protocol_error, 504, NULL,
+        string_sprintf("%s authentication mechanism not supported", s));
 
     break;  /* AUTH_CMD */
 
@@ -3661,38 +3707,40 @@ while (done <= 0)
       letters, so output the names in upper case, though we actually recognize
       them in either case in the AUTH command. */
 
-      if (auths != NULL)
-        {
-        if (verify_check_host(&auth_advertise_hosts) == OK)
-          {
-          auth_instance *au;
-          BOOL first = TRUE;
-          for (au = auths; au != NULL; au = au->next)
-            {
-            if (au->server && (au->advertise_condition == NULL ||
-                expand_check_condition(au->advertise_condition, au->name,
-                US"authenticator")))
-              {
-              int saveptr;
-              if (first)
-                {
-                s = string_cat(s, &size, &ptr, smtp_code, 3);
-                s = string_cat(s, &size, &ptr, US"-AUTH", 5);
-                first = FALSE;
-                auth_advertised = TRUE;
-                }
-              saveptr = ptr;
-              s = string_cat(s, &size, &ptr, US" ", 1);
-              s = string_cat(s, &size, &ptr, au->public_name,
-                Ustrlen(au->public_name));
-              while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
-              au->advertised = TRUE;
-              }
-            else au->advertised = FALSE;
-            }
-          if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2);
-          }
-        }
+      if (  auths
+#if defined(SUPPORT_TLS) && defined(AUTH_TLS)
+        && !sender_host_authenticated
+#endif
+         && verify_check_host(&auth_advertise_hosts) == OK
+        )
+       {
+       auth_instance *au;
+       BOOL first = TRUE;
+       for (au = auths; au; au = au->next)
+         if (au->server && (au->advertise_condition == NULL ||
+             expand_check_condition(au->advertise_condition, au->name,
+             US"authenticator")))
+           {
+           int saveptr;
+           if (first)
+             {
+             s = string_cat(s, &size, &ptr, smtp_code, 3);
+             s = string_cat(s, &size, &ptr, US"-AUTH", 5);
+             first = FALSE;
+             auth_advertised = TRUE;
+             }
+           saveptr = ptr;
+           s = string_cat(s, &size, &ptr, US" ", 1);
+           s = string_cat(s, &size, &ptr, au->public_name,
+             Ustrlen(au->public_name));
+           while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
+           au->advertised = TRUE;
+           }
+         else
+           au->advertised = FALSE;
+
+       if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2);
+       }
 
       /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
       if it has been included in the binary, and the host matches
@@ -4667,6 +4715,7 @@ while (done <= 0)
         helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
       cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
       cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
+      cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
       if (sender_helo_name != NULL)
         {
         store_free(sender_helo_name);
index 456ca81..9c1bf86 100644 (file)
@@ -1024,7 +1024,7 @@ uschar *response_der;
 int response_der_len;
 
 DEBUG(D_tls)
-  debug_printf("Received TLS status request (OCSP stapling); %s response.",
+  debug_printf("Received TLS status request (OCSP stapling); %s response\n",
     cbinfo->u_ocsp.server.response ? "have" : "lack");
 
 tls_in.ocsp = OCSP_NOT_RESP;
index b100e22..7263033 100644 (file)
@@ -331,7 +331,7 @@ tls_cert_subject_altname(void * cert, uschar * mod)
 uschar * list = NULL;
 STACK_OF(GENERAL_NAME) * san = (STACK_OF(GENERAL_NAME) *)
   X509_get_ext_d2i((X509 *)cert, NID_subject_alt_name, NULL, NULL);
-uschar sep = '\n';
+uschar osep = '\n';
 uschar * tag = US"";
 uschar * ele;
 int match = -1;
@@ -339,16 +339,15 @@ int len;
 
 if (!san) return NULL;
 
-while (mod)
+while (mod && *mod)
   {
-  if (*mod == '>' && *++mod) sep = *mod++;
-  else if (Ustrcmp(mod, "dns")==0) { match = GEN_DNS; mod += 3; }
-  else if (Ustrcmp(mod, "uri")==0) { match = GEN_URI; mod += 3; }
-  else if (Ustrcmp(mod, "mail")==0) { match = GEN_EMAIL; mod += 4; }
-  else continue;
+  if (*mod == '>' && *++mod) osep = *mod++;
+  else if (Ustrncmp(mod,"dns",3)==0) { match = GEN_DNS; mod += 3; }
+  else if (Ustrncmp(mod,"uri",3)==0) { match = GEN_URI; mod += 3; }
+  else if (Ustrncmp(mod,"mail",4)==0) { match = GEN_EMAIL; mod += 4; }
+  else mod++;
 
-  if (*mod++ != ',')
-    break;
+  if (*mod == ',') mod++;
   }
 
 while (sk_GENERAL_NAME_num(san) > 0)
@@ -380,7 +379,7 @@ while (sk_GENERAL_NAME_num(san) > 0)
     ele = string_copyn(ele, len);
 
   if (Ustrlen(ele) == len)     /* ignore any with embedded nul */
-    list = string_append_listele(list, sep,
+    list = string_append_listele(list, osep,
          match == -1 ? string_sprintf("%s=%s", tag, ele) : ele);
   }
 
diff --git a/test/confs/3700 b/test/confs/3700
new file mode 100644 (file)
index 0000000..1565b5f
--- /dev/null
@@ -0,0 +1,86 @@
+# Exim test configuration 3700
+
+SERVER=
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/SERVER%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+log_selector = +received_recipients +outgoing_port
+
+# ----- Main settings -----
+
+acl_smtp_mail = check_authd
+acl_smtp_rcpt = check_authd
+queue_only
+queue_run_in_order
+trusted_users = CALLER
+
+tls_on_connect_ports = PORT_S
+tls_advertise_hosts = *
+tls_certificate = DIR/aux-fixed/cert1
+
+tls_verify_hosts = *
+tls_verify_certificates = DIR/aux-fixed/cert2
+
+
+# ----- ACL -----
+
+begin acl
+
+check_authd:
+  deny     message = authentication required
+          !authenticated = *
+  accept
+
+
+# ----- Authentication -----
+
+begin authenticators
+
+tls:
+  driver = tls
+  server_debug_print = +++TLS \$auth1="$auth1"
+  server_param1 =    ${quote:${certextract {subject,CN,>:} \
+                                  {$tls_in_peercert}}}
+  server_condition = ${if def:auth1}
+  server_set_id =    $auth1
+
+
+# ----- Routers -----
+
+begin routers
+
+r1:
+  driver = accept
+  transport = ${if eq {$local_part}{smtps} {t2}{t1}}
+
+
+# ----- Transports -----
+
+begin transports
+
+t1:
+  driver = smtp
+  hosts = 127.0.0.1
+  port = PORT_D
+  allow_localhost
+  tls_certificate =         DIR/aux-fixed/cert2
+  tls_verify_certificates = DIR/aux-fixed/cert1
+  tls_verify_cert_hostnames = :
+
+t2:
+  driver = smtp
+  hosts = 127.0.0.1
+  port = PORT_S
+  protocol = smtps
+  allow_localhost
+  tls_certificate =         DIR/aux-fixed/cert2
+  tls_verify_certificates = DIR/aux-fixed/cert1
+  tls_verify_cert_hostnames = :
+
+# End
diff --git a/test/log/3700 b/test/log/3700
new file mode 100644 (file)
index 0000000..0558c7f
--- /dev/null
@@ -0,0 +1,13 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= ok@test.ex U=CALLER P=local S=sss for x@y
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= ok@test.ex U=CALLER P=local S=sss for smtps@y
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => x@y R=r1 T=t1 H=127.0.0.1 [127.0.0.1]:1225 X=TLS_proto_and_cipher CV=yes C="250 OK id=10HmaZ-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 => smtps@y R=r1 T=t2 H=127.0.0.1 [127.0.0.1]:1224 X=TLS_proto_and_cipher CV=yes C="250 OK id=10HmbA-0005vi-00"
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 and for SMTPS on port 1224
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= ok@test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtpsa X=TLS_proto_and_cipher CV=yes A=tls:"Phil Pennock" S=sss id=E10HmaX-0005vi-00@myhost.test.ex for x@y
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= ok@test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtpsa X=TLS_proto_and_cipher CV=yes A=tls:"Phil Pennock" S=sss id=E10HmaY-0005vi-00@myhost.test.ex for smtps@y
index c95e5a0..616ded3 100755 (executable)
@@ -1375,6 +1375,9 @@ $munges =
     'delay_1500' =>
     { 'stderr' => 's/(1[5-9]|23\d)\d\d msec/ssss msec/' },
 
+    'tls_anycipher' =>
+    { 'mainlog' => 's/ X=TLS\S+ / X=TLS_proto_and_cipher /' },
+
   };
 
 
diff --git a/test/scripts/3700-TLS-auth/3700 b/test/scripts/3700-TLS-auth/3700
new file mode 100644 (file)
index 0000000..e4b6860
--- /dev/null
@@ -0,0 +1,13 @@
+# TLS authentication (server only)
+munge tls_anycipher
+#
+exim -DSERVER=server -bd -oX PORT_D:PORT_S
+****
+exim -f ok@test.ex x@y
+****
+exim -f ok@test.ex smtps@y
+****
+exim -q
+****
+killdaemon
+no_msglog_check
diff --git a/test/scripts/3700-TLS-auth/REQUIRES b/test/scripts/3700-TLS-auth/REQUIRES
new file mode 100644 (file)
index 0000000..1ce59ac
--- /dev/null
@@ -0,0 +1,2 @@
+authenticator tls
+running IPv4