From b3ef41c94af9aefec7b6855cf2ce73ffeaba9d9a Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Thu, 4 Jun 2015 20:28:25 +0100 Subject: TLS authenticator --- doc/doc-docbook/spec.xfpt | 78 ++++++++ doc/doc-txt/ChangeLog | 2 +- doc/doc-txt/NewStuff | 4 +- src/scripts/MakeLinks | 2 +- src/src/EDITME | 1 + src/src/auths/Makefile | 3 +- src/src/auths/tls.c | 80 +++++++++ src/src/auths/tls.h | 30 ++++ src/src/config.h.defaults | 1 + src/src/drtables.c | 18 ++ src/src/exim.c | 3 + src/src/smtp_in.c | 343 ++++++++++++++++++++---------------- src/src/tls-openssl.c | 2 +- src/src/tlscert-openssl.c | 19 +- test/confs/3700 | 86 +++++++++ test/log/3700 | 13 ++ test/runtest | 3 + test/scripts/3700-TLS-auth/3700 | 13 ++ test/scripts/3700-TLS-auth/REQUIRES | 2 + 19 files changed, 541 insertions(+), 162 deletions(-) create mode 100644 src/src/auths/tls.c create mode 100644 src/src/auths/tls.h create mode 100644 test/confs/3700 create mode 100644 test/log/3700 create mode 100644 test/scripts/3700-TLS-auth/3700 create mode 100644 test/scripts/3700-TLS-auth/REQUIRES diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 667857a99..4e561f2b9 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -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 &<>&). 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 &<>&). + +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. + + + . //////////////////////////////////////////////////////////////////////////// . //////////////////////////////////////////////////////////////////////////// diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 09f5c6049..a5b490417 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -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. diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 3fe332556..e10a32b3f 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -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 {}} expansion. +12. A non-SMTP authenticator using information from TLS client certificates. + Version 4.85 ------------ diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index d8c3f1c36..ea6265002 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -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 diff --git a/src/src/EDITME b/src/src/EDITME index 784c67797..d4811225a 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -637,6 +637,7 @@ FIXED_NEVER_USERS=root # AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi # AUTH_PLAINTEXT=yes # AUTH_SPA=yes +# AUTH_TLS=yes #------------------------------------------------------------------------------ diff --git a/src/src/auths/Makefile b/src/src/auths/Makefile index c6ef218b2..45d294932 100644 --- a/src/src/auths/Makefile +++ b/src/src/auths/Makefile @@ -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 index 000000000..51c096cd0 --- /dev/null +++ b/src/src/auths/tls.c @@ -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 index 000000000..bf2a2a1c6 --- /dev/null +++ b/src/src/auths/tls.h @@ -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 */ diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index d31f11548..ec5351859 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -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 diff --git a/src/src/drtables.c b/src/src/drtables.c index c2d866850..5758a92ac 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -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 } }; diff --git a/src/src/exim.c b/src/src/exim.c index 3eca43b49..81bc51ec7 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -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:"); diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index b451c48f5..fc3d34c40 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -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 variables as empty. Initialize $0 empty and set +it as the only set numerical variable. The authenticator may set $auth +and also set other numeric variables. The $auth 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 */ + +/* 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 variables as empty. Initialize $0 empty and set - it as the only set numerical variable. The authenticator may set $auth - and also set other numeric variables. The $auth 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 */ - - /* 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); diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 456ca8142..9c1bf8632 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -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; diff --git a/src/src/tlscert-openssl.c b/src/src/tlscert-openssl.c index b100e222b..726303313 100644 --- a/src/src/tlscert-openssl.c +++ b/src/src/tlscert-openssl.c @@ -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 index 000000000..1565b5f79 --- /dev/null +++ b/test/confs/3700 @@ -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 index 000000000..0558c7f38 --- /dev/null +++ b/test/log/3700 @@ -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 diff --git a/test/runtest b/test/runtest index c95e5a021..616ded37c 100755 --- a/test/runtest +++ b/test/runtest @@ -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 index 000000000..e4b68607a --- /dev/null +++ b/test/scripts/3700-TLS-auth/3700 @@ -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 index 000000000..1ce59ac37 --- /dev/null +++ b/test/scripts/3700-TLS-auth/REQUIRES @@ -0,0 +1,2 @@ +authenticator tls +running IPv4 -- cgit v1.2.3