diff options
author | Jeremy Harris <jgh146exb@wizmail.org> | 2016-09-04 14:54:18 +0100 |
---|---|---|
committer | Jeremy Harris <jgh146exb@wizmail.org> | 2016-09-05 13:18:45 +0100 |
commit | ff5929e3b91747e2ecb600711d17a7d0e21749ad (patch) | |
tree | 282e53de3b3706296c00a94917626f3dab052c38 /src | |
parent | 3e4baf04df1783842277f95a2bd9e09205eba265 (diff) |
Cutthrough: option to reflect 4xx errors from target to initiator
Diffstat (limited to 'src')
-rw-r--r-- | src/src/acl.c | 80 | ||||
-rw-r--r-- | src/src/globals.c | 1 | ||||
-rw-r--r-- | src/src/globals.h | 3 | ||||
-rw-r--r-- | src/src/receive.c | 41 | ||||
-rw-r--r-- | src/src/smtp_in.c | 19 | ||||
-rw-r--r-- | src/src/verify.c | 51 |
6 files changed, 125 insertions, 70 deletions
diff --git a/src/src/acl.c b/src/src/acl.c index 492e9bf17..eff698b34 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -744,7 +744,7 @@ static control_def controls_list[] = { { US"fakereject", CONTROL_FAKEREJECT, TRUE }, { US"submission", CONTROL_SUBMISSION, TRUE }, { US"suppress_local_fixups", CONTROL_SUPPRESS_LOCAL_FIXUPS, FALSE }, - { US"cutthrough_delivery", CONTROL_CUTTHROUGH_DELIVERY, FALSE }, + { US"cutthrough_delivery", CONTROL_CUTTHROUGH_DELIVERY, TRUE }, #ifdef SUPPORT_I18N { US"utf8_downconvert", CONTROL_UTF8_DOWNCONVERT, TRUE } #endif @@ -3398,7 +3398,23 @@ for (; cb != NULL; cb = cb->next) *log_msgptr = US"fakereject"; else { - if (rcpt_count == 1) cutthrough.delivery = TRUE; + if (rcpt_count == 1) + { + cutthrough.delivery = TRUE; + while (*p == '/') + { + const uschar * pp = p+1; + if (Ustrncmp(pp, "defer=", 6) == 0) + { + pp += 6; + if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE; + /* else if (Ustrncmp(pp, "spool") == 0) ; default */ + } + else + while (*pp && *pp != '/') pp++; + p = pp; + } + } break; } *log_msgptr = string_sprintf("\"control=%s\" on %s item", @@ -4502,32 +4518,50 @@ If temp-reject, close the conn (and keep the spooled copy). If conn-failure, no action (and keep the spooled copy). */ switch (where) -{ -case ACL_WHERE_RCPT: + { + case ACL_WHERE_RCPT: #ifndef DISABLE_PRDR -case ACL_WHERE_PRDR: + case ACL_WHERE_PRDR: #endif - if (host_checking_callout) /* -bhc mode */ - cancel_cutthrough_connection("host-checking mode"); - else if (rc == OK && cutthrough.delivery && rcpt_count > cutthrough.nrcpt) - rc = open_cutthrough_connection(addr); - break; + if (host_checking_callout) /* -bhc mode */ + cancel_cutthrough_connection("host-checking mode"); + + else if ( rc == OK + && cutthrough.delivery + && rcpt_count > cutthrough.nrcpt + && (rc = open_cutthrough_connection(addr)) == DEFER + ) + if (cutthrough.defer_pass) + { + uschar * s = addr->message; + /* Horrid kludge to recover target's SMTP message */ + while (*s) s++; + do --s; while (!isdigit(*s)); + if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s; + acl_temp_details = TRUE; + } + else + { + HDEBUG(D_acl) debug_printf("cutthrough defer; will spool\n"); + rc = OK; + } + break; -case ACL_WHERE_PREDATA: - if (rc == OK) - cutthrough_predata(); - else - cancel_cutthrough_connection("predata acl not ok"); - break; + case ACL_WHERE_PREDATA: + if (rc == OK) + cutthrough_predata(); + else + cancel_cutthrough_connection("predata acl not ok"); + break; -case ACL_WHERE_QUIT: -case ACL_WHERE_NOTQUIT: - cancel_cutthrough_connection("quit or notquit"); - break; + case ACL_WHERE_QUIT: + case ACL_WHERE_NOTQUIT: + cancel_cutthrough_connection("quit or notquit"); + break; -default: - break; -} + default: + break; + } deliver_domain = deliver_localpart = deliver_address_data = sender_address_data = NULL; diff --git a/src/src/globals.c b/src/src/globals.c index 9e6f9d347..8d2010273 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -532,6 +532,7 @@ uschar *continue_transport = NULL; uschar *csa_status = NULL; cut_t cutthrough = { FALSE, /* delivery: when to attempt */ + FALSE, /* on defer: spool locally */ -1, /* fd: open connection */ 0, /* nrcpt: number of addresses */ }; diff --git a/src/src/globals.h b/src/src/globals.h index b2bfca64c..0faf713bf 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -301,7 +301,8 @@ extern uschar *continue_transport; /* Transport for continued delivery */ extern uschar *csa_status; /* Client SMTP Authorization result */ typedef struct { - BOOL delivery; /* When to attempt */ + unsigned delivery:1; /* When to attempt */ + unsigned defer_pass:1; /* Pass 4xx to caller rather than spoolling */ int fd; /* Open connection */ int nrcpt; /* Count of addresses */ uschar * interface; /* (address of) */ diff --git a/src/src/receive.c b/src/src/receive.c index 4d30419f0..51ce2844e 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -4052,7 +4052,8 @@ for this message. */ Send dot onward. If accepted, wipe the spooled files, log as delivered and accept the sender's dot (below). If rejected: copy response to sender, wipe the spooled files, log approriately. - If temp-reject: accept to sender, keep the spooled files. + If temp-reject: normally accept to sender, keep the spooled file - unless defer=pass + in which case pass temp-reject back to initiator and dump the files. Having the normal spool files lets us do data-filtering, and store/forward on temp-reject. @@ -4068,13 +4069,17 @@ if(cutthrough.fd >= 0) cutthrough_done = ACCEPTED; break; /* message_id needed for SMTP accept below */ + case '4': /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode. + ... for which, pass back the exact error */ + if (cutthrough.defer_pass) smtp_reply = string_copy_malloc(msg); + /*FALLTRHOUGH*/ + default: /* Unknown response, or error. Treat as temp-reject. */ - case '4': /* Temp-reject. Keep spoolfiles and accept. */ cutthrough_done = TMP_REJ; /* Avoid the usual immediate delivery attempt */ break; /* message_id needed for SMTP accept below */ case '5': /* Perm-reject. Do the same to the source. Dump any spoolfiles */ - smtp_reply= msg; /* Pass on the exact error */ + smtp_reply = string_copy_malloc(msg); /* Pass on the exact error */ cutthrough_done = PERM_REJ; break; } @@ -4188,27 +4193,37 @@ if (smtp_input) /* smtp_reply is set non-empty */ else if (smtp_reply[0] != 0) - { if (fake_response != OK && (smtp_reply[0] == '2')) smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE, fake_response_text); else smtp_printf("%.1024s\r\n", smtp_reply); - } switch (cutthrough_done) { - case ACCEPTED: log_write(0, LOG_MAIN, "Completed");/* Delivery was done */ + case ACCEPTED: + log_write(0, LOG_MAIN, "Completed");/* Delivery was done */ case PERM_REJ: - { /* Delete spool files */ - Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D")); - Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H")); - Uunlink(spool_fname(US"msglog", message_subdir, message_id, US"")); - } - case TMP_REJ: message_id[0] = 0; /* Prevent a delivery from starting */ - default:break; + /* Delete spool files */ + Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D")); + Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H")); + Uunlink(spool_fname(US"msglog", message_subdir, message_id, US"")); + message_id[0] = 0; /* Prevent a delivery from starting */ + break; + + case TMP_REJ: + if (cutthrough.defer_pass) + { + Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D")); + Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H")); + Uunlink(spool_fname(US"msglog", message_subdir, message_id, US"")); + } + message_id[0] = 0; /* Prevent a delivery from starting */ + default: + break; } cutthrough.delivery = FALSE; + cutthrough.defer_pass = FALSE; } /* For batched SMTP, generate an error message on failure, and do diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 597b88e38..387affaf3 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -2961,8 +2961,9 @@ if (lognl != NULL) *lognl = 0; always a 5xx one - see comments at the start of this function. If the original rc was FAIL_DROP we drop the connection and yield 2. */ -if (rc == FAIL) smtp_respond(smtp_code, codelen, TRUE, (user_msg == NULL)? - US"Administrative prohibition" : user_msg); +if (rc == FAIL) + smtp_respond(smtp_code, codelen, TRUE, + user_msg ? user_msg : US"Administrative prohibition"); /* Send temporary failure response to the command. Don't give any details, unless acl_temp_details is set. This is TRUE for a callout defer, a "defer" @@ -2973,21 +2974,19 @@ interactions between temp_details and return_error_details. One day it should be re-implemented in a tidier fashion. */ else - { - if (acl_temp_details && user_msg != NULL) + if (acl_temp_details && user_msg) { - if (smtp_return_error_details && - sender_verified_failed != NULL && - sender_verified_failed->message != NULL) - { + if ( smtp_return_error_details + && sender_verified_failed + && sender_verified_failed->message + ) smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message); - } + smtp_respond(smtp_code, codelen, TRUE, user_msg); } else smtp_respond(smtp_code, codelen, TRUE, US"Temporary local problem - please try later"); - } /* Log the incident to the logs that are specified by log_reject_target (default main, reject). This can be empty to suppress logging of rejections. If diff --git a/src/src/verify.c b/src/src/verify.c index d890f5fc6..865a01d07 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -226,7 +226,7 @@ else if ((dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE)) == NULL) /* If a cache database is available see if we can avoid the need to do an actual callout by making use of previously-obtained data. */ -if (dbm_file != NULL) +if (dbm_file) { dbdata_callout_cache_address *cache_address_record; dbdata_callout_cache *cache_record = get_callout_cache_record(dbm_file, @@ -237,7 +237,7 @@ if (dbm_file != NULL) /* If an unexpired cache record was found for this domain, see if the callout process can be short-circuited. */ - if (cache_record != NULL) + if (cache_record) { /* In most cases, if an early command (up to and including MAIL FROM:<>) was rejected, there is no point carrying on. The callout fails. However, if @@ -297,7 +297,7 @@ if (dbm_file != NULL) but has not been done before, we are going to have to do a callout, so skip remaining cache processing. */ - if (pm_mailfrom != NULL) + if (pm_mailfrom) { if (cache_record->postmaster_result == ccache_reject) { @@ -343,7 +343,7 @@ if (dbm_file != NULL) callout_cache_positive_expire, callout_cache_negative_expire); - if (cache_address_record != NULL) + if (cache_address_record) { if (cache_address_record->result == ccache_accept) { @@ -404,7 +404,7 @@ else and cause the client to time out. So in this case we forgo the PIPELINING optimization. */ - if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush(); + if (smtp_out && !disable_callout_flush) mac_smtp_fflush(); /* cutthrough-multi: if a nonfirst rcpt has the same routing as the first, and we are holding a cutthrough conn open, we can just append the rcpt to @@ -516,7 +516,7 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. /* Now make connections to the hosts and do real callouts. The list of hosts is passed in as an argument. */ - for (host = host_list; host != NULL && !done; host = host->next) + for (host = host_list; host && !done; host = host->next) { smtp_inblock inblock; smtp_outblock outblock; @@ -544,7 +544,7 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. /* Skip this host if we don't have an IP address for it. */ - if (host->address == NULL) + if (!host->address) { DEBUG(D_verify) debug_printf("no IP address for host name %s: skipping\n", host->name); @@ -561,7 +561,7 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. /* Set IPv4 or IPv6 */ - host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6; + host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6; /* Expand and interpret the interface and port strings. The latter will not be used if there is a host-specific port (e.g. from a manualroute router). @@ -659,10 +659,10 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. /* Expand the helo_data string to find the host name to use. */ - if (tf->helo_data != NULL) + if (tf->helo_data) { - uschar *s = expand_string(tf->helo_data); - if (s == NULL) + uschar * s = expand_string(tf->helo_data); + if (!s) log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's " "helo_data value for callout: %s", addr->address, expand_string_message); @@ -1299,8 +1299,9 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. { /* Ensure no cutthrough on multiple address verifies */ if (options & vopt_callout_recipsender) - cancel_cutthrough_connection("multiple verify calls"); - if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n"); + cancel_cutthrough_connection("not usable for cutthrough"); + if (send_quit) + (void) smtp_write_command(&outblock, FALSE, "QUIT\r\n"); #ifdef SUPPORT_TLS tls_close(FALSE, TRUE); @@ -1353,9 +1354,9 @@ if (done) if ( !(options & vopt_callout_no_cache) && new_address_record.result != ccache_unknown) { - if (dbm_file == NULL) + if (!dbm_file) dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE); - if (dbm_file == NULL) + if (!dbm_file) { HDEBUG(D_verify) debug_printf("no callout cache available\n"); } @@ -1376,22 +1377,24 @@ it alone if supplying details. Otherwise, give a generic response. */ else /* !done */ { - uschar *dullmsg = string_sprintf("Could not complete %s verify callout", + uschar * dullmsg = string_sprintf("Could not complete %s verify callout", options & vopt_is_recipient ? "recipient" : "sender"); yield = DEFER; - if (host_list->next != NULL || addr->message == NULL) addr->message = dullmsg; + if (host_list->next || !addr->message) + addr->message = dullmsg; - addr->user_message = (!smtp_return_error_details)? dullmsg : - string_sprintf("%s for <%s>.\n" + addr->user_message = smtp_return_error_details + ? string_sprintf("%s for <%s>.\n" "The mail server(s) for the domain may be temporarily unreachable, or\n" "they may be permanently unreachable from this server. In the latter case,\n%s", dullmsg, addr->address, options & vopt_is_recipient - ? "the address will never be accepted." + ? "the address will never be accepted." : "you need to change the address or create an MX record for its domain\n" "if it is supposed to be generally accessible from the Internet.\n" - "Talk to your mail administrator for details."); + "Talk to your mail administrator for details.") + : dullmsg; /* Force a specific error code */ @@ -1401,7 +1404,7 @@ else /* !done */ /* Come here from within the cache-reading code on fast-track exit. */ END_CALLOUT: -if (dbm_file != NULL) dbfn_close(dbm_file); +if (dbm_file) dbfn_close(dbm_file); return yield; } @@ -1423,10 +1426,12 @@ get rewritten. */ addr2 = *addr; HDEBUG(D_acl) debug_printf("----------- %s cutthrough setup ------------\n", rcpt_count > 1 ? "more" : "start"); -rc= verify_address(&addr2, NULL, +rc = verify_address(&addr2, NULL, vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache, CUTTHROUGH_CMD_TIMEOUT, -1, -1, NULL, NULL, NULL); +addr->message = addr2.message; +addr->user_message = addr2.user_message; HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n"); return rc; } |