From 4f6ae5c314e5c3e462313f3b53c917f36b131bf4 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sat, 16 Jan 2016 22:17:33 +0000 Subject: [PATCH] VRFY: Permit an ACL to override the default 252 response, to support verify-by-ACL instead of the more usual verify-by-routers. Bug 1769 --- doc/doc-docbook/spec.xfpt | 11 ++- doc/doc-txt/ChangeLog | 3 + src/src/acl.c | 6 +- src/src/functions.h | 2 +- src/src/receive.c | 10 ++- src/src/smtp_in.c | 161 +++++++++++++++++------------------ test/confs/0041 | 5 ++ test/scripts/0000-Basic/0041 | 1 + test/stderr/0041 | 15 +++- test/stdout/0041 | 1 + 10 files changed, 122 insertions(+), 93 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index aa1e67712..cb913d6f1 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -34046,13 +34046,20 @@ specific badly-behaved hosts that you have to live with. When Exim receives a VRFY or EXPN command on a TCP/IP connection, it runs the ACL specified by &%acl_smtp_vrfy%& or &%acl_smtp_expn%& (as appropriate) in order to decide whether the command should be accepted or not. -If no ACL is defined, the command is rejected. +.new .cindex "VRFY" "processing" +When no ACL is defined for VRFY, or if it rejects without +setting an explicit response code, the command is accepted +(with a 252 SMTP response code) +in order to support awkward clients that do a VRFY before every RCPT. +.wen When VRFY is accepted, it runs exactly the same code as when Exim is -called with the &%-bv%& option. +called with the &%-bv%& option, and returns 250/451/550 +SMTP response codes. .cindex "EXPN" "processing" +If no ACL for EXPN is defined, the command is rejected. When EXPN is accepted, a single-level expansion of the address is done. EXPN is treated as an &"address test"& (similar to the &%-bt%& option) rather than a verification (the &%-bv%& option). If an unqualified local part is given diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 28fe75447..9f275a9db 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -153,6 +153,9 @@ JH/36 Fix a longstanding bug in memory use by the ${run } expansion: A fresh concluded, but leaving the global pointer active for it. Possibly involved in Bug 1778. +JH/37 Bug 1769: Permit a VRFY ACL to override the default 252 response, + and to use the domains and local_parts ACL conditions. + Exim version 4.86 ----------------- diff --git a/src/src/acl.c b/src/src/acl.c index 1456cc724..684b93bbb 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -482,6 +482,7 @@ static unsigned int cond_forbids[] = { (unsigned int) ~((1< 4) { esc = code + 4; @@ -2626,23 +2626,24 @@ Arguments: codelen length of smtp code; if > 4 there's an ESC msg message text log_msg optional log message, to be adjusted with the new SMTP code + check_valid if true, verify the response code Returns: nothing */ void -smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg) +smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg, + BOOL check_valid) { int n; int ovector[3]; -if (msg == NULL || *msg == NULL) return; +if (!msg || !*msg) return; -n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0, - PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)); -if (n < 0) return; +if ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0, + PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return; -if ((*msg)[0] != (*code)[0]) +if (check_valid && (*msg)[0] != (*code)[0]) { log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with " "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg); @@ -2677,18 +2678,19 @@ defaults disabled in Exim. However, discussion in connection with RFC 821bis (aka RFC 2821) has concluded that the response should be 252 in the disabled state, because there are broken clients that try VRFY before RCPT. A 5xx response should be given only when the address is positively known to be -undeliverable. Sigh. Also, for ETRN, 458 is given on refusal, and for AUTH, -503. +undeliverable. Sigh. We return 252 if there is no VRFY ACL or it provides +no explicit code, but if there is one we let it know best. +Also, for ETRN, 458 is given on refusal, and for AUTH, 503. From Exim 4.63, it is possible to override the response code details by providing a suitable response code string at the start of the message provided in user_msg. The code's first digit is checked for validity. Arguments: - where where the ACL was called from - rc the failure code - user_msg a message that can be included in an SMTP response - log_msg a message for logging + where where the ACL was called from + rc the failure code + user_msg a message that can be included in an SMTP response + log_msg a message for logging Returns: 0 in most cases 2 if the failure code was FAIL_DROP, in which case the @@ -2721,8 +2723,9 @@ if (drop) rc = FAIL; /* Set the default SMTP code, and allow a user message to change it. */ -smtp_code = (rc != FAIL)? US"451" : acl_wherecodes[where]; -smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg); +smtp_code = rc == FAIL ? acl_wherecodes[where] : US"451"; +smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg, + where != ACL_WHERE_VRFY); /* We used to have sender_address here; however, there was a bug that was not updating sender_address after a rewrite during a verify. When this bug was @@ -3096,7 +3099,7 @@ static void smtp_user_msg(uschar *code, uschar *user_msg) { int len = 3; -smtp_message_code(&code, &len, &user_msg, NULL); +smtp_message_code(&code, &len, &user_msg, NULL, TRUE); smtp_respond(code, len, TRUE, user_msg); } @@ -3305,14 +3308,13 @@ while (done <= 0) ) { cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE; - if (acl_smtp_auth) + if ( acl_smtp_auth + && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, + &user_msg, &log_msg)) != OK + ) { - 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; - } + done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg); + continue; } for (au = auths; au; au = au->next) @@ -3371,14 +3373,13 @@ while (done <= 0) /* Check the ACL */ - if (acl_smtp_auth) + if ( acl_smtp_auth + && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, + &user_msg, &log_msg)) != OK + ) { - 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); - break; - } + done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg); + break; } /* Find the name of the requested authentication mechanism. */ @@ -3550,10 +3551,9 @@ while (done <= 0) /* Apply an ACL check if one is defined; afterwards, recheck synchronization in case the client started sending in a delay. */ - if (acl_smtp_helo != NULL) - { - rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo, &user_msg, &log_msg); - if (rc != OK) + if (acl_smtp_helo) + if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo, + &user_msg, &log_msg)) != OK) { done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg); sender_helo_name = NULL; @@ -3561,7 +3561,6 @@ while (done <= 0) break; } else if (!check_sync()) goto SYNC_FAILURE; - } /* Generate an OK reply. The default string includes the ident if present, and also the IP address if present. Reflecting back the ident is intended @@ -3609,7 +3608,7 @@ while (done <= 0) { char *ss; int codelen = 4; - smtp_message_code(&smtp_code, &codelen, &user_msg, NULL); + smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE); s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg); if ((ss = strpbrk(CS s, "\r\n")) != NULL) { @@ -4581,51 +4580,49 @@ while (done <= 0) case VRFY_CMD: - HAD(SCH_VRFY); - rc = acl_check(ACL_WHERE_VRFY, NULL, acl_smtp_vrfy, &user_msg, &log_msg); - if (rc != OK) - done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg); - else { - uschar *address; - uschar *s = NULL; + uschar * address; - /* rfc821_domains = TRUE; << no longer needed */ - address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end, - &recipient_domain, FALSE); - /* rfc821_domains = FALSE; << no longer needed */ + HAD(SCH_VRFY); - if (address == NULL) - s = string_sprintf("501 %s", errmess); + if(!(address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end, + &recipient_domain, FALSE))) + smtp_printf("501 %s\r\n", errmess); + + else if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy, + &user_msg, &log_msg)) != OK) + done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg); else - { - address_item *addr = deliver_make_addr(address, FALSE); - switch(verify_address(addr, NULL, vopt_is_recipient | vopt_qualify, -1, - -1, -1, NULL, NULL, NULL)) - { - case OK: - s = string_sprintf("250 <%s> is deliverable", address); - break; + { + uschar *s = NULL; - case DEFER: - s = (addr->user_message != NULL)? - string_sprintf("451 <%s> %s", address, addr->user_message) : - string_sprintf("451 Cannot resolve <%s> at this time", address); - break; + address_item *addr = deliver_make_addr(address, FALSE); + switch(verify_address(addr, NULL, vopt_is_recipient | vopt_qualify, -1, + -1, -1, NULL, NULL, NULL)) + { + case OK: + s = string_sprintf("250 <%s> is deliverable", address); + break; - case FAIL: - s = (addr->user_message != NULL)? - string_sprintf("550 <%s> %s", address, addr->user_message) : - string_sprintf("550 <%s> is not deliverable", address); - log_write(0, LOG_MAIN, "VRFY failed for %s %s", - smtp_cmd_argument, host_and_ident(TRUE)); - break; - } - } + case DEFER: + s = (addr->user_message != NULL)? + string_sprintf("451 <%s> %s", address, addr->user_message) : + string_sprintf("451 Cannot resolve <%s> at this time", address); + break; - smtp_printf("%s\r\n", s); + case FAIL: + s = (addr->user_message != NULL)? + string_sprintf("550 <%s> %s", address, addr->user_message) : + string_sprintf("550 <%s> is not deliverable", address); + log_write(0, LOG_MAIN, "VRFY failed for %s %s", + smtp_cmd_argument, host_and_ident(TRUE)); + break; + } + + smtp_printf("%s\r\n", s); + } + break; } - break; case EXPN_CMD: @@ -4659,15 +4656,13 @@ while (done <= 0) /* Apply an ACL check if one is defined */ - if (acl_smtp_starttls != NULL) + if ( acl_smtp_starttls + && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls, + &user_msg, &log_msg)) != OK + ) { - rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls, &user_msg, - &log_msg); - if (rc != OK) - { - done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg); - break; - } + done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg); + break; } /* RFC 2487 is not clear on when this command may be sent, though it @@ -4910,8 +4905,8 @@ while (done <= 0) log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument, host_and_ident(FALSE)); - rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, &user_msg, &log_msg); - if (rc != OK) + if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, + &user_msg, &log_msg)) != OK) { done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg); break; diff --git a/test/confs/0041 b/test/confs/0041 index 51cba04ac..ec1067584 100644 --- a/test/confs/0041 +++ b/test/confs/0041 @@ -12,6 +12,7 @@ tls_advertise_hosts = domainlist local_domains = test.ex +acl_smtp_vrfy = check_vrfy acl_smtp_expn = check_expn qualify_domain = test.ex no_write_rejectlog @@ -21,6 +22,10 @@ no_write_rejectlog begin acl +check_vrfy: + deny local_parts = hardfail + message = 599 custom reject + check_expn: accept hosts = 2.2.2.2 diff --git a/test/scripts/0000-Basic/0041 b/test/scripts/0000-Basic/0041 index 5601d6506..3495375cb 100644 --- a/test/scripts/0000-Basic/0041 +++ b/test/scripts/0000-Basic/0041 @@ -1,6 +1,7 @@ # VRFY & EXPN blocking exim -bh 1.1.1.1 vrfy userx@test.ex +vrfy hardfail@test.ex expn postmaster quit **** diff --git a/test/stderr/0041 b/test/stderr/0041 index 4aacae883..b00305212 100644 --- a/test/stderr/0041 +++ b/test/stderr/0041 @@ -7,8 +7,21 @@ >>> host in helo_try_verify_hosts? no (option unset) >>> host in helo_accept_junk_hosts? no (option unset) >>> host in smtp_accept_max_nonmail_hosts? yes (matched "*") ->>> ACL is NULL: implicit DENY +>>> using ACL "check_vrfy" +>>> processing "deny" +>>> check local_parts = hardfail +>>> userx in "hardfail"? no (end of list) +>>> deny: condition test failed in ACL "check_vrfy" +>>> end of ACL "check_vrfy": implicit DENY LOG: H=[1.1.1.1] rejected VRFY userx@test.ex +>>> using ACL "check_vrfy" +>>> processing "deny" +>>> check local_parts = hardfail +>>> hardfail in "hardfail"? yes (matched "hardfail") +>>> message: 599 custom reject +>>> deny: condition test succeeded in ACL "check_vrfy" +>>> end of ACL "check_vrfy": DENY +LOG: H=[1.1.1.1] rejected VRFY hardfail@test.ex: 599 custom reject >>> using ACL "check_expn" >>> processing "accept" >>> check hosts = 2.2.2.2 diff --git a/test/stdout/0041 b/test/stdout/0041 index d43b4501a..b88c93ac7 100644 --- a/test/stdout/0041 +++ b/test/stdout/0041 @@ -5,6 +5,7 @@ 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 252 Administrative prohibition +599 custom reject 550 Administrative prohibition 221 the.local.host.name closing connection -- 2.30.2