-/* $Cambridge: exim/src/src/transports/smtp.c,v 1.1 2004/10/07 13:10:02 ph10 Exp $ */
+/* $Cambridge: exim/src/src/transports/smtp.c,v 1.11 2005/04/28 13:06:32 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
(void *)offsetof(smtp_transport_options_block, data_timeout) },
{ "delay_after_cutoff", opt_bool,
(void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ { "dk_canon", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dk_canon) },
+ { "dk_domain", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dk_domain) },
+ { "dk_headers", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dk_headers) },
+ { "dk_private_key", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dk_private_key) },
+ { "dk_selector", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dk_selector) },
+ { "dk_strict", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dk_strict) },
+#endif
{ "dns_qualify_single", opt_bool,
(void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
{ "dns_search_parents", opt_bool,
#endif
{ "hosts_max_try", opt_int,
(void *)offsetof(smtp_transport_options_block, hosts_max_try) },
+ { "hosts_max_try_hardlimit", opt_int,
+ (void *)offsetof(smtp_transport_options_block, hosts_max_try_hardlimit) },
#ifdef SUPPORT_TLS
{ "hosts_nopass_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
10*60, /* final timeout */
1024, /* size_addition */
5, /* hosts_max_try */
+ 50, /* hosts_max_try_hardlimit */
FALSE, /* allow_localhost */
FALSE, /* gethostbyname */
TRUE, /* dns_qualify_single */
NULL, /* tls_verify_certificates */
TRUE /* tls_tempfail_tryclear */
#endif
+ #ifdef EXPERIMENTAL_DOMAINKEYS
+ ,NULL, /* dk_canon */
+ NULL, /* dk_domain */
+ NULL, /* dk_headers */
+ NULL, /* dk_private_key */
+ NULL, /* dk_selector */
+ NULL /* dk_strict */
+ #endif
};
status means that an address is not currently being processed.
Arguments:
- addrlist points to a chain of addresses
- errno_value to put in each address's errno field
- msg to put in each address's message field
- rc to put in each address's transport_return field
+ addrlist points to a chain of addresses
+ errno_value to put in each address's errno field
+ msg to put in each address's message field
+ rc to put in each address's transport_return field
+ pass_message if TRUE, set the "pass message" flag in the address
If errno_value has the special value ERRNO_CONNECTTIMEOUT, ETIMEDOUT is put in
the errno field, and RTEF_CTOUT is ORed into the more_errno field, to indicate
*/
static
-void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc)
+void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
+ BOOL pass_message)
{
address_item *addr;
int orvalue = 0;
if (addr->transport_return < PENDING) continue;
addr->basic_errno = errno_value;
addr->more_errno |= orvalue;
- if (msg != NULL) addr->message = msg;
+ if (msg != NULL)
+ {
+ addr->message = msg;
+ if (pass_message) setflag(addr, af_pass_message);
+ }
addr->transport_return = rc;
}
}
chosen.
Arguments:
- host the current host, to get its name for messages
- errno_value pointer to the errno value
- more_errno from the top address for use with ERRNO_FILTER_FAIL
- buffer the SMTP response buffer
- yield where to put a one-digit SMTP response code
- message where to put an errror message
-
-Returns: TRUE if an SMTP "QUIT" command should be sent, else FALSE
+ host the current host, to get its name for messages
+ errno_value pointer to the errno value
+ more_errno from the top address for use with ERRNO_FILTER_FAIL
+ buffer the SMTP response buffer
+ yield where to put a one-digit SMTP response code
+ message where to put an errror message
+ pass_message set TRUE if message is an SMTP response
+
+Returns: TRUE if an SMTP "QUIT" command should be sent, else FALSE
*/
static BOOL check_response(host_item *host, int *errno_value, int more_errno,
- uschar *buffer, int *yield, uschar **message)
+ uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
{
uschar *pl = US"";
if (*errno_value == ERRNO_FILTER_FAIL)
{
- *message = US string_sprintf("transport filter process failed (%d)",
- more_errno);
+ *message = US string_sprintf("transport filter process failed (%d)%s",
+ more_errno,
+ (more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
return FALSE;
}
if (buffer[0] != 0)
{
uschar *s = string_printing(buffer);
- *message = US string_sprintf("SMTP error from remote mailer after %s%s: "
+ *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
"host %s [%s]: %s", pl, smtp_command, host->name, host->address, s);
+ *pass_message = TRUE;
*yield = buffer[0];
return TRUE;
}
uschar *message = string_sprintf("SMTP timeout while connected to %s [%s] "
"after RCPT TO:<%s>", host->name, host->address,
transport_rcpt_address(addr, include_affixes));
- set_errno(addrlist, save_errno, message, DEFER);
+ set_errno(addrlist, save_errno, message, DEFER, FALSE);
retry_add_item(addr, addr->address_retry_key, 0);
host->update_waiting = FALSE;
return -1;
else
{
addr->message =
- string_sprintf("SMTP error from remote mailer after RCPT TO:<%s>: "
+ string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
"host %s [%s]: %s", transport_rcpt_address(addr, include_affixes),
host->name, host->address, string_printing(buffer));
+ setflag(addr, af_pass_message);
deliver_msglog("%s %s\n", tod_stamp(tod_log), addr->message);
/* The response was 5xx */
{
int code;
uschar *msg;
+ BOOL pass_message;
if (pending_DATA > 0 || (yield & 1) != 0) return -3;
- (void)check_response(host, &errno, 0, buffer, &code, &msg);
+ (void)check_response(host, &errno, 0, buffer, &code, &msg, &pass_message);
DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
"is in use and there were no good recipients\n", msg);
}
BOOL completed_address = FALSE;
BOOL esmtp = TRUE;
BOOL pending_MAIL;
+BOOL pass_message = FALSE;
smtp_inblock inblock;
smtp_outblock outblock;
int max_rcpt = tblock->max_addresses;
{
uschar *message = string_sprintf("failed to expand helo_data: %s",
expand_string_message);
- set_errno(addrlist, 0, message, DEFER);
+ set_errno(addrlist, 0, message, DEFER, FALSE);
return ERROR;
}
{
uschar *message = string_sprintf("failed to expand "
"authenticated_sender: %s", expand_string_message);
- set_errno(addrlist, 0, message, DEFER);
+ set_errno(addrlist, 0, message, DEFER, FALSE);
return ERROR;
}
}
if (inblock.sock < 0)
{
set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno,
- NULL, DEFER);
+ NULL, DEFER, FALSE);
return DEFER;
}
for (addr = addrlist; addr != NULL; addr = addr->next)
{
- addr->cipher = tls_cipher;
- addr->peerdn = tls_peerdn;
+ if (addr->transport_return == PENDING_DEFER)
+ {
+ addr->cipher = tls_cipher;
+ addr->peerdn = tls_peerdn;
+ }
}
}
}
case ERROR:
yield = ERROR;
- set_errno(addrlist, 0, string_copy(buffer), DEFER);
+ set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
goto SEND_QUIT;
}
{
yield = DEFER;
set_errno(addrlist, ERRNO_AUTHFAIL,
- string_sprintf("authentication required but %s", fail_reason), DEFER);
+ string_sprintf("authentication required but %s", fail_reason), DEFER,
+ FALSE);
goto SEND_QUIT;
}
}
if (!rc)
{
- set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER);
+ set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
+ FALSE);
yield = ERROR;
goto SEND_QUIT;
}
}
if (badaddr != NULL)
{
- set_errno(addrlist, 0, badaddr->message, FAIL);
+ set_errno(addrlist, 0, badaddr->message, FAIL,
+ testflag(badaddr, af_pass_message));
ok = FALSE;
}
}
DEBUG(D_transport|D_v)
debug_printf(" SMTP>> writing message and terminating \".\"\n");
transport_count = 0;
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ if ( (ob->dk_private_key != NULL) && (ob->dk_selector != NULL) )
+ ok = dk_transport_write_message(addrlist, inblock.sock,
+ topt_use_crlf | topt_end_dot | topt_escape_headers |
+ (tblock->body_only? topt_no_headers : 0) |
+ (tblock->headers_only? topt_no_body : 0) |
+ (tblock->return_path_add? topt_add_return_path : 0) |
+ (tblock->delivery_date_add? topt_add_delivery_date : 0) |
+ (tblock->envelope_to_add? topt_add_envelope_to : 0),
+ 0, /* No size limit */
+ tblock->add_headers, tblock->remove_headers,
+ US".", US"..", /* Escaping strings */
+ tblock->rewrite_rules, tblock->rewrite_existflags,
+ ob->dk_private_key, ob->dk_domain, ob->dk_selector,
+ ob->dk_canon, ob->dk_headers, ob->dk_strict);
+ else
+#endif
ok = transport_write_message(addrlist, inblock.sock,
topt_use_crlf | topt_end_dot | topt_escape_headers |
(tblock->body_only? topt_no_headers : 0) |
save_errno = errno;
message = NULL;
send_quit = check_response(host, &save_errno, addrlist->more_errno,
- buffer, &code, &message);
+ buffer, &code, &message, &pass_message);
goto FAILED;
SEND_FAILED:
{
if (code == '5')
{
- set_errno(addrlist, save_errno, message, FAIL);
+ set_errno(addrlist, save_errno, message, FAIL, pass_message);
}
else
{
- set_errno(addrlist, save_errno, message, DEFER);
+ set_errno(addrlist, save_errno, message, DEFER, pass_message);
yield = DEFER;
}
}
{
yield = (save_errno == ERRNO_CHHEADER_FAIL ||
save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
- set_errno(addrlist, save_errno, message, DEFER);
+ set_errno(addrlist, save_errno, message, DEFER, pass_message);
}
/* Otherwise we have a message-specific error response from the remote
{
if (mua_wrapper) code = '5'; /* Force hard failure in wrapper mode */
- set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER);
+ set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER,
+ pass_message);
/* If there's an errno, the message contains just the identity of
the host. */
))
{
uschar *msg;
+ BOOL pass_message;
if (send_rset)
{
ob->command_timeout)))
{
int code;
- send_quit = check_response(host, &errno, 0, buffer, &code, &msg);
+ send_quit = check_response(host, &errno, 0, buffer, &code, &msg,
+ &pass_message);
if (!send_quit)
{
DEBUG(D_transport) debug_printf("%s\n", msg);
/* If RSET failed and there are addresses left, they get deferred. */
- else set_errno(first_addr, errno, msg, DEFER);
+ else set_errno(first_addr, errno, msg, DEFER, FALSE);
}
}
int hosts_retry = 0;
int hosts_serial = 0;
int hosts_total = 0;
+int total_hosts_tried = 0;
address_item *addr;
BOOL expired = TRUE;
BOOL continuing = continue_hostname != NULL;
. If there are any addresses whose status is still DEFER, carry on to the
next host/IPaddress, unless we have tried the number of hosts given
- by hosts_max_try; otherwise return.
+ by hosts_max_try or hosts_max_try_hardlimit; otherwise return. Note that
+ there is some fancy logic for hosts_max_try that means its limit can be
+ overstepped in some circumstances.
If we get to the end of the list, all hosts have deferred at least one address,
or not reached their retry times. If delay_after_cutoff is unset, it requests a
int unexpired_hosts_tried = 0;
for (host = hostlist;
- host != NULL && unexpired_hosts_tried < ob->hosts_max_try;
+ host != NULL &&
+ unexpired_hosts_tried < ob->hosts_max_try &&
+ total_hosts_tried < ob->hosts_max_try_hardlimit;
host = nexthost)
{
int rc;
uschar *serialize_key = NULL;
/* Default next host is next host. :-) But this can vary if the
- hosts_max_try limit is hit (see below). */
+ hosts_max_try limit is hit (see below). It may also be reset if a host
+ address is looked up here (in case the host was multihomed). */
nexthost = host->next;
/* Find by name if so configured, or if it's an IP address. We don't
just copy the IP address, because we need the test-for-local to happen. */
- if (ob->gethostbyname || string_is_ip_address(host->name, NULL))
+ if (ob->gethostbyname || string_is_ip_address(host->name, NULL) > 0)
rc = host_find_byname(host, NULL, &canonical_name, TRUE);
else
{
continue; /* With next host */
}
+ /* Reset the default next host in case a multihomed host whose addresses
+ are not looked up till just above added to the host list. */
+
+ nexthost = host->next;
+
/* If queue_smtp is set (-odqs or the first part of a 2-stage run), or the
domain is in queue_smtp_domains, we don't actually want to attempt any
deliveries. When doing a queue run, queue_smtp_domains is always unset. If
if (dont_deliver)
{
host_item *host2;
- set_errno(addrlist, 0, NULL, OK);
+ set_errno(addrlist, 0, NULL, OK, FALSE);
for (addr = addrlist; addr != NULL; addr = addr->next)
{
addr->host_used = host;
/* This is for real. If the host is expired, we don't count it for
hosts_max_retry. This ensures that all hosts must expire before an address
- is timed out. Otherwise, if we are about to hit the hosts_max_retry limit,
- check to see if there is a subsequent hosts with a different MX value. If
- so, make that the next host, and don't count this one. This is a heuristic
- to make sure that different MXs do get tried. With a normal kind of retry
- rule, they would get tried anyway when the earlier hosts were delayed, but
- if the domain has a "retry every time" type of rule - as is often used for
- the the very large ISPs, that won't happen. */
+ is timed out, unless hosts_max_try_hardlimit (which protects against
+ lunatic DNS configurations) is reached.
+
+ If the host is not expired and we are about to hit the hosts_max_retry
+ limit, check to see if there is a subsequent hosts with a different MX
+ value. If so, make that the next host, and don't count this one. This is a
+ heuristic to make sure that different MXs do get tried. With a normal kind
+ of retry rule, they would get tried anyway when the earlier hosts were
+ delayed, but if the domain has a "retry every time" type of rule - as is
+ often used for the the very large ISPs, that won't happen. */
else
{
/* Attempt the delivery. */
+ total_hosts_tried++;
rc = smtp_deliver(addrlist, host, host_af, port, interface, tblock,
expanded_hosts != NULL, &message_defer, FALSE);
maximum retry time for this host. This means we may try try all hosts,
ignoring the limit, when messages have been around for some time. This is
important because if we don't try all hosts, the address will never time
- out. */
+ out. NOTE: this does not apply to hosts_max_try_hardlimit. */
if ((rc == DEFER || some_deferred) && nexthost != NULL)
{
for (addr = addrlist; addr != NULL; addr = addr->next)
{
/* If host is not NULL, it means that we stopped processing the host list
- because of hosts_max_try. This means we need to behave as if some hosts were
- skipped because their retry time had not come. Specifically, this prevents
- the address from timing out. */
+ because of hosts_max_try or hosts_max_try_hardlimit. In the former case, this
+ means we need to behave as if some hosts were skipped because their retry
+ time had not come. Specifically, this prevents the address from timing out.
+ However, if we have hit hosts_max_try_hardlimit, we want to behave as if all
+ hosts were tried. */
if (host != NULL)
{
- DEBUG(D_transport)
- debug_printf("hosts_max_try limit caused some hosts to be skipped\n");
- setflag(addr, af_retry_skipped);
+ if (total_hosts_tried >= ob->hosts_max_try_hardlimit)
+ {
+ DEBUG(D_transport)
+ debug_printf("hosts_max_try_hardlimit reached: behave as if all "
+ "hosts were tried\n");
+ }
+ else
+ {
+ DEBUG(D_transport)
+ debug_printf("hosts_max_try limit caused some hosts to be skipped\n");
+ setflag(addr, af_retry_skipped);
+ }
}
if (queue_smtp) /* no deliveries attempted */