-/* $Cambridge: exim/src/src/transports/smtp.c,v 1.3 2005/01/04 10:00:45 ph10 Exp $ */
+/* $Cambridge: exim/src/src/transports/smtp.c,v 1.17 2005/08/09 13:31:53 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
(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) },
(void *)offsetof(smtp_transport_options_block, interface) },
{ "keepalive", opt_bool,
(void *)offsetof(smtp_transport_options_block, keepalive) },
+ { "lmtp_ignore_quota", opt_bool,
+ (void *)offsetof(smtp_transport_options_block, lmtp_ignore_quota) },
{ "max_rcpt", opt_int | opt_public,
(void *)offsetof(transport_instance, max_addresses) },
{ "multi_domain", opt_bool | opt_public,
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 */
FALSE, /* hosts_override */
FALSE, /* hosts_randomize */
TRUE, /* keepalive */
+ FALSE, /* lmtp_ignore_quota */
TRUE /* retry_include_ip_address */
#ifdef SUPPORT_TLS
,NULL, /* tls_certificate */
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
Returns: nothing
*/
-static
-void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc)
+static 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)%s",
- more_errno,
+ 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;
}
}
else
{
- log_write(0, LOG_MAIN, "%s [%s]: %s",
- host->name,
- host->address,
- strerror(addr->basic_errno));
- deliver_msglog("%s %s [%s]: %s\n",
- tod_stamp(tod_log),
- host->name,
- host->address,
+ uschar *msg =
+ ((log_extra_selector & LX_outgoing_port) != 0)?
+ string_sprintf("%s [%s]:%d", host->name, host->address,
+ (host->port == PORT_NONE)? 25 : host->port)
+ :
+ string_sprintf("%s [%s]", host->name, host->address);
+ log_write(0, LOG_MAIN, "%s %s", msg, strerror(addr->basic_errno));
+ deliver_msglog("%s %s %s\n", tod_stamp(tod_log), msg,
strerror(addr->basic_errno));
}
}
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);
}
failed by one of them.
host host to deliver to
host_af AF_INET or AF_INET6
- port TCP/IP port to use, in host byte order
+ port default TCP/IP port to use, in host byte order
interface interface to bind to, or NULL
tblock transport instance block
copy_host TRUE if host set in addr->host_used must be copied, because
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 *igquotstr = US"";
uschar *local_authenticated_sender = authenticated_sender;
uschar *helo_data;
uschar *message = NULL;
{
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;
}
ob->command_timeout)) goto RESPONSE_FAILED;
}
+ /* Set IGNOREQUOTA if the response to LHLO specifies support and the
+ lmtp_ignore_quota option was set. */
+
+ igquotstr = (lmtp && ob->lmtp_ignore_quota &&
+ pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
+ PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
#ifdef SUPPORT_TLS
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;
+ }
}
}
}
int require_auth;
uschar *fail_reason = US"server did not advertise AUTH support";
+ /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+ lmtp_ignore_quota option was set. */
+
+ igquotstr = (lmtp && ob->lmtp_ignore_quota &&
+ pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
+ PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+
/* If the response to EHLO specified support for the SIZE parameter, note
this, provided size_addition is non-negative. */
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;
}
yield as OK, because this error can often mean that there is a problem with
just one address, so we don't want to delay the host. */
- count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>\r\n",
- transport_rcpt_address(addr, tblock->rcpt_include_affixes));
+ count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s\r\n",
+ transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr);
if (count < 0) goto SEND_FAILED;
if (count > 0)
{
}
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) |
}
/* SMTP, or success return from LMTP for this address. Pass back the
- actual port used. */
+ actual host that was used. */
addr->transport_return = OK;
addr->more_errno = delivery_time;
- thost->port = port;
addr->host_used = thost;
addr->special_action = flag;
addr->message = conf;
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);
}
}
specified in the transports, and therefore not visible at top level, in which
case continue_more won't get set. */
-close(inblock.sock);
+(void)close(inblock.sock);
continue_transport = NULL;
continue_hostname = NULL;
return yield;
(void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
(void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->command_timeout);
-close(inblock.sock);
+(void)close(inblock.sock);
}
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;
}
-/* Sort out the port. Set up a string for adding to the retry key if the port
-number is not the standard SMTP port. */
+/* Sort out the default port. */
if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
-pistring = string_sprintf(":%d", port);
-if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
/* For each host-plus-IP-address on the list:
. 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;
if (host->address == NULL)
{
+ int new_port;
+ host_item *hh;
uschar *canonical_name;
if (host->status >= hstatus_unusable)
DEBUG(D_transport) debug_printf("getting address for %s\n", host->name);
+ /* The host name is permitted to have an attached port. Find it, and
+ strip it from the name. Just remember it for now. */
+
+ new_port = host_item_get_port(host);
+
+ /* Count hosts looked up */
+
hosts_looked_up++;
/* 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
{
&canonical_name, NULL);
}
+ /* Update the host (and any additional blocks, resulting from
+ multihoming) with a host-specific port, if any. */
+
+ for (hh = host; hh != nexthost; hh = hh->next) hh->port = new_port;
+
/* Failure to find the host at this time (usually DNS temporary failure)
is really a kind of routing failure rather than a transport failure.
Therefore we add a retry item of the routing kind, not to stop us trying
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
doing a two-stage queue run, don't do this if forcing. */
if ((!deliver_force || queue_2stage) && (queue_smtp ||
- match_isinlist(addrlist->domain, &queue_smtp_domains, 0, NULL, NULL,
- MCL_DOMAIN, TRUE, NULL) == OK))
+ match_isinlist(addrlist->domain, &queue_smtp_domains, 0,
+ &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
{
expired = FALSE;
for (addr = addrlist; addr != NULL; addr = addr->next)
deliver_host = host->name;
deliver_host_address = host->address;
+ /* Set up a string for adding to the retry key if the port number is not
+ the standard SMTP port. A host may have its own port setting that overrides
+ the default. */
+
+ pistring = string_sprintf(":%d", (host->port == PORT_NONE)?
+ port : host->port);
+ if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
+
/* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
string changes upon expansion, we must add it to the key that is used for
retries, because connections to the same host from a different interface
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 */
}
else if (expired)
{
+ setflag(addr, af_pass_message); /* This is not a security risk */
addr->message = (ob->delay_after_cutoff)?
US"retry time not reached for any host after a long failure period" :
US"all hosts have been failing for a long time and were last tried "