summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2019-01-05 20:40:08 +0000
committerJeremy Harris <jgh146exb@wizmail.org>2019-02-11 00:15:24 +0000
commitb53c265b5c7825d7fb6bb672547c44d080459d71 (patch)
tree41f6e133c24de5e551590239f2898dba9d6b06bd
parent37942ad83fcb6b788cceb4abe2891c92651811a7 (diff)
EXTERNAL authenticator
(cherry picked from commit c4a8c663b74a35b547d8320547079ca56b3b772e)
-rw-r--r--doc/doc-docbook/spec.xfpt153
-rw-r--r--doc/doc-txt/NewStuff2
-rw-r--r--doc/doc-txt/OptionLists.txt3
-rw-r--r--src/OS/Makefile-Base5
-rwxr-xr-xsrc/scripts/MakeLinks2
-rw-r--r--src/src/EDITME1
-rw-r--r--src/src/auths/Makefile3
-rw-r--r--src/src/auths/external.c158
-rw-r--r--src/src/auths/external.h32
-rw-r--r--src/src/config.h.defaults1
-rw-r--r--src/src/drtables.c18
-rw-r--r--test/confs/372093
l---------test/confs/37211
-rw-r--r--test/log/372011
-rw-r--r--test/log/372111
-rw-r--r--test/scripts/3720-external-auth-GnuTLS/372038
-rw-r--r--test/scripts/3720-external-auth-GnuTLS/REQUIRES2
-rw-r--r--test/scripts/3721-external-auth-OpenSSL/372138
-rw-r--r--test/scripts/3721-external-auth-OpenSSL/REQUIRES2
-rw-r--r--test/stdout/372043
-rw-r--r--test/stdout/372144
21 files changed, 652 insertions, 9 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 0358ae9d3..958e7caf6 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -26128,6 +26128,7 @@ included by setting
AUTH_CRAM_MD5=yes
AUTH_CYRUS_SASL=yes
AUTH_DOVECOT=yes
+AUTH_EXTERNAL=yes
AUTH_GSASL=yes
AUTH_HEIMDAL_GSSAPI=yes
AUTH_PLAINTEXT=yes
@@ -26139,15 +26140,20 @@ authentication mechanism (RFC 2195), and the second provides an interface to
the Cyrus SASL authentication library.
The third is an interface to Dovecot's authentication system, delegating the
work via a socket interface.
-The fourth provides an interface to the GNU SASL authentication library, which
+.new
+The fourth provides for negotiation of authentication done via non-SMTP means,
+as defined by RFC 4422 Appendix A.
+.wen
+The fifth provides an interface to the GNU SASL authentication library, which
provides mechanisms but typically not data sources.
-The fifth provides direct access to Heimdal GSSAPI, geared for Kerberos, but
+The sixth provides direct access to Heimdal GSSAPI, geared for Kerberos, but
supporting setting a server keytab.
-The sixth can be configured to support
+The seventh 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
+not formally documented, but used by several MUAs.
+The eighth authenticator
supports Microsoft's &'Secure Password Authentication'& mechanism.
-The eighth is an Exim authenticator but not an SMTP one;
+The last 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
@@ -27385,6 +27391,143 @@ msn:
. ////////////////////////////////////////////////////////////////////////////
. ////////////////////////////////////////////////////////////////////////////
+.chapter "The external authenticator" "CHAPexternauth"
+.scindex IIDexternauth1 "&(external)& authenticator"
+.scindex IIDexternauth2 "authenticators" "&(external)&"
+.cindex "authentication" "Client Certificate"
+.cindex "authentication" "X509"
+.cindex "Certificate-based authentication"
+The &(external)& authenticator provides support for
+authentication based on non-SMTP information.
+The specification is in RFC 4422 Appendix A
+(&url(https://tools.ietf.org/html/rfc4422)).
+It is only a transport and negotiation mechanism;
+the process of authentication is entirely controlled
+by the server configuration.
+
+The client presents an identity in-clear.
+It is probably wise for a server to only advertise,
+and for clients to only attempt,
+this authentication method on a secure (eg. under TLS) connection.
+
+One possible use, compatible with the
+K-9 Mail Andoid client (&url(https://k9mail.github.io/)),
+is for using X509 client certificates.
+
+It thus overlaps in function with the TLS authenticator
+(see &<<CHAPtlsauth>>&)
+but is a full SMTP SASL authenticator
+rather than being implicit for TLS-connection carried
+client certificates only.
+
+The examples and discussion in this chapter assume that
+client-certificate authentication is being done.
+
+The client must present a certificate,
+for which it must have been requested via the
+&%tls_verify_hosts%& or &%tls_try_verify_hosts%& main options
+(see &<<CHAPTLS>>&).
+For authentication to be effective the certificate should be
+verifiable against a trust-anchor certificate known to the server.
+
+.section "External options" "SECTexternsoptions"
+.cindex "options" "&(external)& authenticator (server)"
+The &(external)& authenticator has two server options:
+
+.option server_param2 external string&!! unset
+.option server_param3 external string&!! unset
+.cindex "variables (&$auth1$& &$auth2$& etc)" "in &(external)& authenticator"
+These options are expanded before the &%server_condition%& option
+and the result are placed in &$auth2$& and &$auth3$& resectively.
+If the expansion is forced to fail, authentication fails. Any other expansion
+failure causes a temporary error code to be returned.
+
+They can be used to clarify the coding of a complex &%server_condition%&.
+
+.section "Using external in a server" "SECTexternserver"
+.cindex "AUTH" "in &(external)& authenticator"
+.cindex "numerical variables (&$1$& &$2$& etc)" &&&
+ "in &(external)& authenticator"
+.vindex "&$auth1$&, &$auth2$&, etc"
+.cindex "base64 encoding" "in &(external)& authenticator"
+
+When running as a server, &(external)& performs the authentication test by
+expanding a string. The data sent by the client with the AUTH command, or in
+response to subsequent prompts, is base64 encoded, and so may contain any byte
+values when decoded. The decoded value is treated as
+an identity for authentication and
+placed in the expansion variable &$auth1$&.
+
+For compatibility with previous releases of Exim, the value is also placed in
+the expansion variable &$1$&. However, the use of this
+variable for this purpose is now deprecated, as it can lead to confusion in
+string expansions that also use them for other things.
+
+.vindex "&$authenticated_id$&"
+Once an identity has been received,
+&%server_condition%& is expanded. If the expansion is forced to fail,
+authentication fails. Any other expansion failure causes a temporary error code
+to be returned. If the result of a successful expansion is an empty string,
+&"0"&, &"no"&, or &"false"&, authentication fails. If the result of the
+expansion is &"1"&, &"yes"&, or &"true"&, authentication succeeds and the
+generic &%server_set_id%& option is expanded and saved in &$authenticated_id$&.
+For any other result, a temporary error code is returned, with the expanded
+string as the error text.
+
+Example:
+.code
+ext_ccert_san_mail:
+ driver = external
+ public_name = EXTERNAL
+
+ server_advertise_condition = $tls_in_certificate_verified
+ server_param2 = ${certextract {subj_altname,mail,>:} \
+ {$tls_in_peercert}}
+ server_condition = ${if forany {$auth2} \
+ {eq {$item}{$auth1}}}
+ server_set_id = $auth1
+.endd
+This accepts a client certificate that is verifiable against any
+of your configured trust-anchors
+(which usually means the full set of public CAs)
+and which has a mail-SAN matching the claimed identity sent by the client.
+
+Note that, up to TLS1.2, the client cert is on the wire in-clear, including the SAN,
+The account name is therefore guessable by an opponent.
+TLS 1.3 protects both server and client certificates, and is not vulnerable
+in this way.
+Likewise, a traditional plaintext SMTP AUTH done inside TLS is not.
+
+
+.section "Using external in a client" "SECTexternclient"
+.cindex "options" "&(external)& authenticator (client)"
+The &(external)& authenticator has one client option:
+
+.option client_send external string&!! unset
+This option is expanded and sent with the AUTH command as the
+identity being asserted.
+
+Example:
+.code
+ext_ccert:
+ driver = external
+ public_name = EXTERNAL
+
+ client_condition = ${if !eq{$tls_out_cipher}{}}
+ client_send = myaccount@smarthost.example.net
+.endd
+
+
+.ecindex IIDexternauth1
+.ecindex IIDexternauth2
+
+
+
+
+
+. ////////////////////////////////////////////////////////////////////////////
+. ////////////////////////////////////////////////////////////////////////////
+
.chapter "The tls authenticator" "CHAPtlsauth"
.scindex IIDtlsauth1 "&(tls)& authenticator"
.scindex IIDtlsauth2 "authenticators" "&(tls)&"
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index a3c736d76..07577ec11 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -9,7 +9,7 @@ the documentation is updated, this file is reduced to a short list.
Version 4.93
------------
- (none yet)
+ 1. An "external" authenticator, per RFC 4422 Appendix A.
Version 4.92
diff --git a/doc/doc-txt/OptionLists.txt b/doc/doc-txt/OptionLists.txt
index 0ef35f54c..069a787d6 100644
--- a/doc/doc-txt/OptionLists.txt
+++ b/doc/doc-txt/OptionLists.txt
@@ -132,6 +132,7 @@ client_ignore_invalid_base64 boolean false plaintext
client_name string* + cram_md5 3.10
client_secret string* unset cram_md5 3.10
client_send string* unset plaintext 3.10
+client_send string* unset external (auth) 4.93
command string* unset lmtp 3.20
unset pipe
unset queryprogram 4.00
@@ -501,6 +502,8 @@ server_password string unset gsasl
server_param1 string* unset tls (auth) 4.86
server_param2 string* unset tls (auth) 4.86
server_param3 string* unset tls (auth) 4.86
+server_param2 string* unset tls (auth) 4.86 (tls-only) 4.93 (external)
+server_param3 string* unset tls (auth) 4.86 (tls-only) 4.93 (external)
server_prompts string* unset plaintext 3.10
server_realm string unset cyrus_sasl,gsasl 4.43 (cyrus-only) 4.80 (others)
server_scram_iter string* unset gsasl 4.80
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index fed3134cf..79bec063b 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -135,7 +135,7 @@ OBJ_MACRO = macro_predef.o \
macro-smtp.o macro-accept.o macro-dnslookup.o macro-ipliteral.o macro-iplookup.o \
macro-manualroute.o macro-queryprogram.o macro-redirect.o \
macro-auth-spa.o macro-cram_md5.o macro-cyrus_sasl.o macro-dovecot.o macro-gsasl_exim.o \
- macro-heimdal_gssapi.o macro-plaintext.o macro-spa.o macro-authtls.o \
+ macro-heimdal_gssapi.o macro-plaintext.o macro-spa.o macro-authtls.o macro-external.o \
macro-dkim.o macro-malware.o macro-signing.o
$(OBJ_MACRO): $(MACRO_HSRC)
@@ -212,6 +212,9 @@ macro-cyrus_sasl.o : auths/cyrus_sasl.c
macro-dovecot.o: auths/dovecot.c
@echo "$(CC) -DMACRO_PREDEF auths/dovecot.c"
$(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/dovecot.c
+macro-external.o: auths/external.c
+ @echo "$(CC) -DMACRO_PREDEF auths/external.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/external.c
macro-gsasl_exim.o : auths/gsasl_exim.c
@echo "$(CC) -DMACRO_PREDEF auths/gsasl_exim.c"
$(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/gsasl_exim.c
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index 08f4ff1f8..f69bde437 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -75,7 +75,7 @@ for f in README Makefile 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 tls.c tls.h
+ spa.h tls.c tls.h external.c external.h
do
ln -s ../../src/auths/$f $f
done
diff --git a/src/src/EDITME b/src/src/EDITME
index cbb080545..15360db9a 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -643,6 +643,7 @@ FIXED_NEVER_USERS=root
# AUTH_CRAM_MD5=yes
# AUTH_CYRUS_SASL=yes
# AUTH_DOVECOT=yes
+# AUTH_EXTERNAL=yes
# AUTH_GSASL=yes
# AUTH_GSASL_PC=libgsasl
# AUTH_HEIMDAL_GSSAPI=yes
diff --git a/src/src/auths/Makefile b/src/src/auths/Makefile
index 62ce9d0a9..402f1417a 100644
--- a/src/src/auths/Makefile
+++ b/src/src/auths/Makefile
@@ -7,7 +7,7 @@
OBJ = auth-spa.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 \
+ external.o get_data.o get_no64_data.o gsasl_exim.o heimdal_gssapi.o \
md5.o plaintext.o pwcheck.o \
spa.o tls.o xtextdecode.o xtextencode.o
@@ -36,6 +36,7 @@ xtextencode.o: $(HDRS) xtextencode.c
cram_md5.o: $(HDRS) cram_md5.c cram_md5.h
cyrus_sasl.o: $(HDRS) cyrus_sasl.c cyrus_sasl.h
dovecot.o: $(HDRS) dovecot.c dovecot.h
+external.o: $(HDRS) external.c external.h
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
diff --git a/src/src/auths/external.c b/src/src/auths/external.c
new file mode 100644
index 000000000..10e1366a8
--- /dev/null
+++ b/src/src/auths/external.c
@@ -0,0 +1,158 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2019 */
+/* 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, using the EXTERNAL
+method defined in RFC 4422 Appendix A.
+*/
+
+
+#include "../exim.h"
+#include "external.h"
+
+/* Options specific to the external authentication mechanism. */
+
+optionlist auth_external_options[] = {
+ { "client_send", opt_stringptr,
+ (void *)(offsetof(auth_external_options_block, client_send)) },
+ { "server_param2", opt_stringptr,
+ (void *)(offsetof(auth_external_options_block, server_param2)) },
+ { "server_param3", opt_stringptr,
+ (void *)(offsetof(auth_external_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_external_options_count = nelem(auth_external_options);
+
+/* Default private options block for the authentication method. */
+
+auth_external_options_block auth_external_option_defaults = {
+ .server_param2 = NULL,
+ .server_param3 = NULL,
+
+ .client_send = NULL,
+};
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_external_init(auth_instance *ablock) {}
+int auth_external_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_external_client(auth_instance *ablock, void * sx,
+ int timeout, uschar *buffer, int buffsize) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+
+/*************************************************
+* 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_external_init(auth_instance *ablock)
+{
+auth_external_options_block * ob = (auth_external_options_block *)ablock->options_block;
+if (!ablock->public_name) ablock->public_name = ablock->name;
+if (ablock->server_condition) ablock->server = TRUE;
+if (ob->client_send) ablock->client = TRUE;
+}
+
+
+
+/*************************************************
+* Server entry point *
+*************************************************/
+
+/* For interface, see auths/README */
+
+int
+auth_external_server(auth_instance * ablock, uschar * data)
+{
+auth_external_options_block * ob = (auth_external_options_block *)ablock->options_block;
+int rc;
+
+/* If data was supplied on the AUTH command, decode it, and split it up into
+multiple items at binary zeros. The strings are put into $auth1, $auth2, etc,
+up to a maximum. To retain backwards compatibility, they are also put int $1,
+$2, etc. If the data consists of the string "=" it indicates a single, empty
+string. */
+
+if (*data)
+ if ((rc = auth_read_input(data)) != OK)
+ return rc;
+
+/* Now go through the list of prompt strings. Skip over any whose data has
+already been provided as part of the AUTH command. For the rest, send them
+out as prompts, and get a data item back. If the data item is "*", abandon the
+authentication attempt. Otherwise, split it into items as above. */
+
+if (expand_nmax == 0) /* skip if rxd data */
+ if ((rc = auth_prompt(CUS"")) != OK)
+ return rc;
+
+if (ob->server_param2)
+ {
+ uschar * s = expand_string(ob->server_param2);
+ auth_vars[expand_nmax] = s;
+ expand_nstring[++expand_nmax] = s;
+ expand_nlength[expand_nmax] = Ustrlen(s);
+ if (ob->server_param3)
+ {
+ s = expand_string(ob->server_param3);
+ auth_vars[expand_nmax] = s;
+ expand_nstring[++expand_nmax] = s;
+ expand_nlength[expand_nmax] = Ustrlen(s);
+ }
+ }
+
+return auth_check_serv_cond(ablock);
+}
+
+
+
+/*************************************************
+* Client entry point *
+*************************************************/
+
+/* For interface, see auths/README */
+
+int
+auth_external_client(
+ auth_instance *ablock, /* authenticator block */
+ void * sx, /* smtp connextion */
+ int timeout, /* command timeout */
+ uschar *buffer, /* buffer for reading response */
+ int buffsize) /* size of buffer */
+{
+auth_external_options_block *ob =
+ (auth_external_options_block *)(ablock->options_block);
+const uschar * text = ob->client_send;
+int rc;
+
+/* We output an AUTH command with one expanded argument, the client_send option */
+
+if ((rc = auth_client_item(sx, ablock, &text, AUTH_ITEM_FIRST | AUTH_ITEM_LAST,
+ timeout, buffer, buffsize)) != OK)
+ return rc == DEFER ? FAIL : rc;
+
+if (text) auth_vars[0] = string_copy(text);
+return OK;
+}
+
+
+
+#endif /*!MACRO_PREDEF*/
+/* End of external.c */
diff --git a/src/src/auths/external.h b/src/src/auths/external.h
new file mode 100644
index 000000000..7d43650bb
--- /dev/null
+++ b/src/src/auths/external.h
@@ -0,0 +1,32 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2019 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Private structure for the private options. */
+
+typedef struct {
+ uschar * server_param2;
+ uschar * server_param3;
+
+ uschar * client_send;
+} auth_external_options_block;
+
+/* Data for reading the private options. */
+
+extern optionlist auth_external_options[];
+extern int auth_external_options_count;
+
+/* Block containing default values. */
+
+extern auth_external_options_block auth_external_option_defaults;
+
+/* The entry points for the mechanism */
+
+extern void auth_external_init(auth_instance *);
+extern int auth_external_server(auth_instance *, uschar *);
+extern int auth_external_client(auth_instance *, void *, int, uschar *, int);
+
+/* End of external.h */
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 7c2e534f3..74ec3f4df 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -24,6 +24,7 @@ Do not put spaces between # and the 'define'.
#define AUTH_CRAM_MD5
#define AUTH_CYRUS_SASL
#define AUTH_DOVECOT
+#define AUTH_EXTERNAL
#define AUTH_GSASL
#define AUTH_HEIMDAL_GSSAPI
#define AUTH_PLAINTEXT
diff --git a/src/src/drtables.c b/src/src/drtables.c
index cd12dd1da..54d03edab 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -35,6 +35,10 @@ set to NULL for those that are not compiled into the binary. */
#include "auths/dovecot.h"
#endif
+#ifdef AUTH_EXTERNAL
+#include "auths/external.h"
+#endif
+
#ifdef AUTH_GSASL
#include "auths/gsasl_exim.h"
#endif
@@ -101,6 +105,20 @@ auth_info auths_available[] = {
},
#endif
+#ifdef AUTH_EXTERNAL
+ {
+ .driver_name = US"external",
+ .options = auth_external_options,
+ .options_count = &auth_external_options_count,
+ .options_block = &auth_external_option_defaults,
+ .options_len = sizeof(auth_external_options_block),
+ .init = auth_external_init,
+ .servercode = auth_external_server,
+ .clientcode = auth_external_client,
+ .version_report = NULL
+ },
+#endif
+
#ifdef AUTH_GSASL
{
.driver_name = US"gsasl",
diff --git a/test/confs/3720 b/test/confs/3720
new file mode 100644
index 000000000..6d8c46730
--- /dev/null
+++ b/test/confs/3720
@@ -0,0 +1,93 @@
+# Exim test configuration 3720
+
+SERVER=
+
+.include DIR/aux-var/tls_conf_prefix
+
+primary_hostname = myhost.test.ex
+log_selector = +received_recipients +outgoing_port
+
+# ----- Main settings -----
+
+acl_smtp_auth = log_call
+acl_smtp_mail = check_authd
+acl_smtp_rcpt = check_authd
+acl_smtp_data = ar_header
+
+queue_only
+queue_run_in_order
+trusted_users = CALLER
+
+tls_advertise_hosts = *
+tls_certificate = DIR/aux-fixed/cert1
+
+tls_verify_hosts = *
+tls_verify_certificates = DIR/aux-fixed/cert2
+
+
+# ----- ACL -----
+
+begin acl
+
+log_call:
+ accept logwrite = Auth ACL called, after smtp cmd "$smtp_command"
+
+check_authd:
+ deny message = authentication required
+ !authenticated = *
+ accept
+
+ar_header:
+ accept add_header = :at_start:${authresults {$primary_hostname}}
+
+# ----- Authentication -----
+
+begin authenticators
+
+ext_ccert_cn:
+ driver = external
+ public_name = EXTERNAL
+
+ server_advertise_condition = ${if eq{$tls_in_cipher}{}{no}{yes}}
+ server_param2 = ${certextract {subject,CN} {$tls_in_peercert}}
+ server_condition = ${if eq {$auth2}{$auth1}}
+ server_set_id = $auth1
+ server_debug_print = +++TLS \$auth1="$auth1"
+
+ client_send = "Phil Pennock"
+
+
+# ----- Routers -----
+
+begin routers
+
+server_r:
+ driver = accept
+ condition = ${if eq {server}{SERVER}}
+ transport = file
+
+client_r1:
+ driver = accept
+ transport = 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 = :
+ hosts_try_auth = *
+
+file:
+ driver = appendfile
+ file = DIR/test-mail/$local_part
+ user = CALLER
+
+# End
diff --git a/test/confs/3721 b/test/confs/3721
new file mode 120000
index 000000000..e4011014e
--- /dev/null
+++ b/test/confs/3721
@@ -0,0 +1 @@
+3720 \ No newline at end of file
diff --git a/test/log/3720 b/test/log/3720
new file mode 100644
index 000000000..f79e7562f
--- /dev/null
+++ b/test/log/3720
@@ -0,0 +1,11 @@
+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 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => x@y R=client_r1 T=t1 H=127.0.0.1 [127.0.0.1]:1225 X=TLS_proto_and_cipher CV=yes A=ext_ccert_cn C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-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
+1999-03-02 09:44:33 Auth ACL called, after smtp cmd "AUTH EXTERNAL UGhpbCBQZW5ub2Nr"
+1999-03-02 09:44:33 Auth ACL called, after smtp cmd "AUTH EXTERNAL UGhpbCBQZW5ub2Nr"
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= ok@test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtpsa X=TLS_proto_and_cipher CV=yes A=ext_ccert_cn:Phil Pennock S=sss id=E10HmaX-0005vi-00@myhost.test.ex for x@y
diff --git a/test/log/3721 b/test/log/3721
new file mode 100644
index 000000000..f79e7562f
--- /dev/null
+++ b/test/log/3721
@@ -0,0 +1,11 @@
+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 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmaX-0005vi-00 => x@y R=client_r1 T=t1 H=127.0.0.1 [127.0.0.1]:1225 X=TLS_proto_and_cipher CV=yes A=ext_ccert_cn C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-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
+1999-03-02 09:44:33 Auth ACL called, after smtp cmd "AUTH EXTERNAL UGhpbCBQZW5ub2Nr"
+1999-03-02 09:44:33 Auth ACL called, after smtp cmd "AUTH EXTERNAL UGhpbCBQZW5ub2Nr"
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= ok@test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtpsa X=TLS_proto_and_cipher CV=yes A=ext_ccert_cn:Phil Pennock S=sss id=E10HmaX-0005vi-00@myhost.test.ex for x@y
diff --git a/test/scripts/3720-external-auth-GnuTLS/3720 b/test/scripts/3720-external-auth-GnuTLS/3720
new file mode 100644
index 000000000..49d95205e
--- /dev/null
+++ b/test/scripts/3720-external-auth-GnuTLS/3720
@@ -0,0 +1,38 @@
+# External authentication (server & client)
+munge tls_anycipher
+#
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+#
+client-gnutls 127.0.0.1 PORT_D 127.0.0.1 DIR/aux-fixed/cert2 DIR/aux-fixed/cert2
+??? 220
+EHLO tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-STARTTLS
+??? 250 HELP
+STARTTLS
+??? 220
+EHLO tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-AUTH EXTERNAL
+??? 250 HELP
+AUTH EXTERNAL UGhpbCBQZW5ub2Nr
+??? 235
+quit
+??? 221
+****
+#
+exim -f ok@test.ex x@y
+****
+exim -q
+****
+#
+killdaemon
+no_msglog_check
diff --git a/test/scripts/3720-external-auth-GnuTLS/REQUIRES b/test/scripts/3720-external-auth-GnuTLS/REQUIRES
new file mode 100644
index 000000000..9e358e2ac
--- /dev/null
+++ b/test/scripts/3720-external-auth-GnuTLS/REQUIRES
@@ -0,0 +1,2 @@
+authenticator external
+support GnuTLS
diff --git a/test/scripts/3721-external-auth-OpenSSL/3721 b/test/scripts/3721-external-auth-OpenSSL/3721
new file mode 100644
index 000000000..310b8d2ae
--- /dev/null
+++ b/test/scripts/3721-external-auth-OpenSSL/3721
@@ -0,0 +1,38 @@
+# External authentication (server & client)
+munge tls_anycipher
+#
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+#
+client-ssl 127.0.0.1 PORT_D 127.0.0.1 DIR/aux-fixed/cert2 DIR/aux-fixed/cert2
+??? 220
+EHLO tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-STARTTLS
+??? 250 HELP
+STARTTLS
+??? 220
+EHLO tester
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250-AUTH EXTERNAL
+??? 250 HELP
+AUTH EXTERNAL UGhpbCBQZW5ub2Nr
+??? 235
+quit
+??? 221
+****
+#
+exim -f ok@test.ex x@y
+****
+exim -q
+****
+#
+killdaemon
+no_msglog_check
diff --git a/test/scripts/3721-external-auth-OpenSSL/REQUIRES b/test/scripts/3721-external-auth-OpenSSL/REQUIRES
new file mode 100644
index 000000000..c0a56a293
--- /dev/null
+++ b/test/scripts/3721-external-auth-OpenSSL/REQUIRES
@@ -0,0 +1,2 @@
+authenticator external
+support OpenSSL
diff --git a/test/stdout/3720 b/test/stdout/3720
new file mode 100644
index 000000000..049c87d36
--- /dev/null
+++ b/test/stdout/3720
@@ -0,0 +1,43 @@
+Connecting to 127.0.0.1 port 1225 ... connected
+Certificate file = TESTSUITE/aux-fixed/cert2
+Key file = TESTSUITE/aux-fixed/cert2
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-STARTTLS
+<<< 250-STARTTLS
+??? 250 HELP
+<<< 250 HELP
+>>> STARTTLS
+??? 220
+<<< 220 TLS go ahead
+Attempting to start TLS
+Succeeded in starting TLS
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-AUTH EXTERNAL
+<<< 250-AUTH EXTERNAL
+??? 250 HELP
+<<< 250 HELP
+>>> AUTH EXTERNAL UGhpbCBQZW5ub2Nr
+??? 235
+<<< 235 Authentication succeeded
+>>> quit
+??? 221
+<<< 221 myhost.test.ex closing connection
+End of script
diff --git a/test/stdout/3721 b/test/stdout/3721
new file mode 100644
index 000000000..81878f98e
--- /dev/null
+++ b/test/stdout/3721
@@ -0,0 +1,44 @@
+Connecting to 127.0.0.1 port 1225 ... connected
+Certificate file = TESTSUITE/aux-fixed/cert2
+Key file = TESTSUITE/aux-fixed/cert2
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-STARTTLS
+<<< 250-STARTTLS
+??? 250 HELP
+<<< 250 HELP
+>>> STARTTLS
+??? 220
+<<< 220 TLS go ahead
+Attempting to start TLS
+SSL connection using ke-RSA-AES256-SHA
+Succeeded in starting TLS
+>>> EHLO tester
+??? 250-
+<<< 250-myhost.test.ex Hello tester [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250-AUTH EXTERNAL
+<<< 250-AUTH EXTERNAL
+??? 250 HELP
+<<< 250 HELP
+>>> AUTH EXTERNAL UGhpbCBQZW5ub2Nr
+??? 235
+<<< 235 Authentication succeeded
+>>> quit
+??? 221
+<<< 221 myhost.test.ex closing connection
+End of script