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 667857a996daf38f6d3a7c90919220ab0eda6013..4e561f2b9571a0b050f54ce6c613fd29ce887dc5 100644 (file)
@@ -24866,6 +24866,7 @@ AUTH_GSASL=yes
 AUTH_HEIMDAL_GSSAPI=yes
 AUTH_PLAINTEXT=yes
 AUTH_SPA=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
 .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 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
 
 The authenticators are configured using the same syntax as other drivers (see
 section &<<SECTfordricon>>&). If no authenticators are required, no
@@ -26086,6 +26089,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.
+
+
+
 . ////////////////////////////////////////////////////////////////////////////
 . ////////////////////////////////////////////////////////////////////////////
 
 . ////////////////////////////////////////////////////////////////////////////
 . ////////////////////////////////////////////////////////////////////////////
 
index 09f5c60490c354e684c39c4fde60d79e185e48a0..a5b4904173fe13253a6bb4351d224c7fb651c8a8 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,
       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.
 
       can be used to disable this per-host.  The build option
       EXPERIMENTAL_CERTNAMES is withdrawn.
 
index 3fe33255621ddfa6229dcaa433f4f55d627dc6f3..e10a32b3f55c5419ee69d9c8ea14acbb0399a1cc 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.
 
  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.
 
 
 11. New ${env {<variable>}} expansion.
 
+12. A non-SMTP authenticator using information from TLS client certificates.
+
 
 Version 4.85
 ------------
 
 Version 4.85
 ------------
index d8c3f1c36f99913d9d020521a1d7c44a00bc7d48..ea6265002607fc06e02c62e2d71a4dacab06bfc1 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 \
   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
 do
   ln -s ../../src/auths/$f $f
 done
index 784c67797749935dcc60008d25e8110a5119ab76..d4811225a322000821c9ec24a4a628c3c52e27e3 100644 (file)
@@ -637,6 +637,7 @@ FIXED_NEVER_USERS=root
 # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi
 # AUTH_PLAINTEXT=yes
 # AUTH_SPA=yes
 # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi
 # AUTH_PLAINTEXT=yes
 # AUTH_SPA=yes
+# AUTH_TLS=yes
 
 
 #------------------------------------------------------------------------------
 
 
 #------------------------------------------------------------------------------
index c6ef218b27b4695e27ccb7ef71159c9c43a4caa3..45d29493247cb3b9c89c4e93fb8cc30e7dab0418 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 \
       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
 
 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
 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
 
 # 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 d31f1154888d18694fd02696af4451e0629a3f75..ec53518594b811b79b817ed8b1b774997684b1da 100644 (file)
@@ -24,6 +24,7 @@ it's a default value. */
 #define AUTH_HEIMDAL_GSSAPI
 #define AUTH_PLAINTEXT
 #define AUTH_SPA
 #define AUTH_HEIMDAL_GSSAPI
 #define AUTH_PLAINTEXT
 #define AUTH_SPA
+#define AUTH_TLS
 
 #define AUTH_VARS                     3
 
 
 #define AUTH_VARS                     3
 
index c2d866850893d53de4c3f5013224b59bfe53a116..5758a92ac28b8f6b81706fdf455cdf0cbba181f4 100644 (file)
@@ -53,6 +53,10 @@ set to NULL for those that are not compiled into the binary. */
 #include "auths/spa.h"
 #endif
 
 #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 */
 auth_info auths_available[] = {
 
 /* Checking by an expansion condition on plain text */
@@ -155,6 +159,20 @@ auth_info auths_available[] = {
   },
 #endif
 
   },
 #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  }
 };
 
 { US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL  }
 };
 
index 3eca43b4960ee7ca9c56c697444e8aace0c434b5..81bc51ec77354f4d612aa64635a67306e4ada3c5 100644 (file)
@@ -937,6 +937,9 @@ fprintf(f, "Authenticators:");
 #ifdef AUTH_SPA
   fprintf(f, " spa");
 #endif
 #ifdef AUTH_SPA
   fprintf(f, " spa");
 #endif
+#ifdef AUTH_TLS
+  fprintf(f, " tls");
+#endif
 fprintf(f, "\n");
 
 fprintf(f, "Routers:");
 fprintf(f, "\n");
 
 fprintf(f, "Routers:");
index b451c48f586b6cce17e5c9f8a5ee3e25924405ac..fc3d34c40327e2356fe68cc7196ac178eebbeaa2 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 */
   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 */
 
 
   /* 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 },
   { "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. */
   #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_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. */
 
 /* 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     *
 *************************************************/
 /*************************************************
 *       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_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 */
 #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;
   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;
   uschar *s, *ss;
   BOOL was_rej_mail = FALSE;
   BOOL was_rcpt = FALSE;
@@ -3181,6 +3291,41 @@ while (done <= 0)
   uschar *orcpt = NULL;
   int flags;
 
   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
   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;
       }
         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;
       }
       {
       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");
       {
       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 */
 
 
     /* 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)
       {
       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). */
 
     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 &&
       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 */
 
 
     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. */
 
       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
 
       /* 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;
         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);
       if (sender_helo_name != NULL)
         {
         store_free(sender_helo_name);
index 456ca8142d36b0ce56e18651109a0ed30ff8c403..9c1bf8632c3e0c0632b459ce68ad6107d87adfcc 100644 (file)
@@ -1024,7 +1024,7 @@ uschar *response_der;
 int response_der_len;
 
 DEBUG(D_tls)
 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;
     cbinfo->u_ocsp.server.response ? "have" : "lack");
 
 tls_in.ocsp = OCSP_NOT_RESP;
index b100e222bc4eaaaef700bebde9072ffe52a9098a..7263033137e4c98859ff5c399d37c3ad7f73c860 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 * 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;
 uschar * tag = US"";
 uschar * ele;
 int match = -1;
@@ -339,16 +339,15 @@ int len;
 
 if (!san) return NULL;
 
 
 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)
   }
 
 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 */
     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);
   }
 
          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 c95e5a0214f6cdfd8898d94e0382da07770eb002..616ded37cc4a96f83f8fd5cd761664cdbb118dd8 100755 (executable)
@@ -1375,6 +1375,9 @@ $munges =
     'delay_1500' =>
     { 'stderr' => 's/(1[5-9]|23\d)\d\d msec/ssss msec/' },
 
     '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