-/* $Cambridge: exim/src/src/retry.c,v 1.2 2005/01/04 10:00:42 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with retrying unsuccessful deliveries. */
*************************************************/
/* This function tests whether a message has been on the queue longer than
-the maximum retry time for a particular host.
+the maximum retry time for a particular host or address.
Arguments:
- host_key the key to look up a host retry rule
+ retry_key the key to look up a retry rule
domain the domain to look up a domain retry rule
- basic_errno a specific error number, or zero if none
- more_errno additional data for the error
+ retry_record contains error information for finding rule
now the time
Returns: TRUE if the ultimate timeout has been reached
*/
-static BOOL
-ultimate_address_timeout(uschar *host_key, uschar *domain, int basic_errno,
- int more_errno, time_t now)
+BOOL
+retry_ultimate_address_timeout(uschar *retry_key, const uschar *domain,
+ dbdata_retry *retry_record, time_t now)
{
-BOOL address_timeout = TRUE; /* no rule => timed out */
+BOOL address_timeout;
+
+DEBUG(D_retry)
+ {
+ debug_printf("retry time not reached: checking ultimate address timeout\n");
+ debug_printf(" now=%d first_failed=%d next_try=%d expired=%d\n",
+ (int)now, (int)retry_record->first_failed,
+ (int)retry_record->next_try, retry_record->expired);
+ }
retry_config *retry =
- retry_find_config(host_key+2, domain, basic_errno, more_errno);
+ retry_find_config(retry_key+2, domain,
+ retry_record->basic_errno, retry_record->more_errno);
if (retry != NULL && retry->rules != NULL)
{
for (last_rule = retry->rules;
last_rule->next != NULL;
last_rule = last_rule->next);
+ DEBUG(D_retry)
+ debug_printf(" received_time=%d diff=%d timeout=%d\n",
+ received_time, (int)(now - received_time), last_rule->timeout);
address_timeout = (now - received_time > last_rule->timeout);
}
+else
+ {
+ DEBUG(D_retry)
+ debug_printf("no retry rule found: assume timed out\n");
+ address_timeout = TRUE;
+ }
+
+DEBUG(D_retry)
+ if (address_timeout)
+ debug_printf("on queue longer than maximum retry for address - "
+ "allowing delivery\n");
return address_timeout;
}
*/
BOOL
-retry_check_address(uschar *domain, host_item *host, uschar *portstring,
+retry_check_address(const uschar *domain, host_item *host, uschar *portstring,
BOOL include_ip_address, uschar **retry_host_key, uschar **retry_message_key)
{
BOOL yield = FALSE;
if (now < host_retry_record->next_try && !deliver_force)
{
- DEBUG(D_transport|D_retry)
- debug_printf("host retry time not reached: checking ultimate address "
- "timeout\n");
-
if (!host_retry_record->expired &&
- ultimate_address_timeout(host_key, domain,
- host_retry_record->basic_errno, host_retry_record->more_errno, now))
- {
- DEBUG(D_transport|D_retry)
- debug_printf("on queue longer than maximum retry for "
- "address - allowing delivery\n");
+ retry_ultimate_address_timeout(host_key, domain,
+ host_retry_record, now))
return FALSE;
- }
/* We have not hit the ultimate address timeout; host is unusable. */
*retry_message_key = message_key;
if (now < message_retry_record->next_try && !deliver_force)
{
- DEBUG(D_transport|D_retry)
- debug_printf("host+message retry time not reached: checking ultimate "
- "address timeout\n");
- if (!ultimate_address_timeout(host_key, domain, 0, 0, now))
+ if (!retry_ultimate_address_timeout(host_key, domain,
+ message_retry_record, now))
{
host->status = hstatus_unusable;
host->why = hwhy_retry;
}
- else
- {
- DEBUG(D_transport|D_retry)
- debug_printf("on queue longer than maximum retry for "
- "address - allowing delivery\n");
- }
return FALSE;
}
}
retry_add_item(address_item *addr, uschar *key, int flags)
{
retry_item *rti = store_get(sizeof(retry_item));
+host_item * host = addr->host_used;
rti->next = addr->retries;
addr->retries = rti;
rti->key = key;
rti->basic_errno = addr->basic_errno;
rti->more_errno = addr->more_errno;
-rti->message = addr->message;
+rti->message = host
+ ? string_sprintf("H=%s [%s]: %s", host->name, host->address, addr->message)
+ : addr->message;
rti->flags = flags;
DEBUG(D_transport|D_retry)
*/
retry_config *
-retry_find_config(uschar *key, uschar *alternate, int basic_errno,
+retry_find_config(const uschar *key, const uschar *alternate, int basic_errno,
int more_errno)
{
-int replace;
-uschar *use_key, *use_alternate;
-uschar *colon = Ustrchr(key, ':');
+const uschar *colon = Ustrchr(key, ':');
retry_config *yield;
-/* If there's a colon in the key, temporarily replace it with
-a zero to terminate the string there. */
+/* If there's a colon in the key, there are two possibilities:
-if (colon != NULL)
- {
- replace = ':';
- }
-else
- {
- colon = key + Ustrlen(key);
- replace = 0;
- }
-*colon = 0;
+(1) This is a key for a host, ip address, and possibly port, in the format
+
+ hostname:ip+port
+
+ In this case, we copy the host name.
+
+(2) This is a key for a pipe, file, or autoreply delivery, in the format
+
+ pipe-or-file-or-auto:x@y
+
+ where x@y is the original address that provoked the delivery. The pipe or
+ file or auto will start with | or / or >, whereas a host name will start
+ with a letter or a digit. In this case we want to use the original address
+ to search for a retry rule. */
+
+if (colon)
+ key = isalnum(*key)
+ ? string_copyn(key, colon-key) /* the hostname */
+ : Ustrrchr(key, ':') + 1; /* Take from the last colon */
/* Sort out the keys */
-use_key = (Ustrchr(key, '@') != NULL)? key : string_sprintf("*@%s", key);
-use_alternate = (alternate == NULL)? NULL : string_sprintf("*@%s", alternate);
+if (!Ustrchr(key, '@')) key = string_sprintf("*@%s", key);
+if (alternate) alternate = string_sprintf("*@%s", alternate);
/* Scan the configured retry items. */
for (yield = retries; yield != NULL; yield = yield->next)
{
- uschar *plist = yield->pattern;
- uschar *slist = yield->senders;
+ const uschar *plist = yield->pattern;
+ const uschar *slist = yield->senders;
/* If a specific error is set for this item, check that we are handling that
specific error, and if so, check any additional error information if
continue;
}
- /* Handle 4xx responses to RCPT. The code that was received is in the 2nd
- least significant byte of more_errno (with 400 subtracted). The required
- value is coded in the 2nd least significant byte of the yield->more_errno
- field as follows:
+ /* The TLSREQUIRED error also covers TLSFAILURE. These are subtly different
+ errors, but not worth separating at this level. */
+
+ else if (yield->basic_errno == ERRNO_TLSREQUIRED)
+ {
+ if (basic_errno != ERRNO_TLSREQUIRED && basic_errno != ERRNO_TLSFAILURE)
+ continue;
+ }
+
+ /* Handle 4xx responses to MAIL, RCPT, or DATA. The code that was received
+ is in the 2nd least significant byte of more_errno (with 400 subtracted).
+ The required value is coded in the 2nd least significant byte of the
+ yield->more_errno field as follows:
255 => any 4xx code
>= 100 => the decade must match the value less 100
< 100 => the exact value must match
*/
- else if (yield->basic_errno == ERRNO_RCPT4XX)
+ else if (yield->basic_errno == ERRNO_MAIL4XX ||
+ yield->basic_errno == ERRNO_RCPT4XX ||
+ yield->basic_errno == ERRNO_DATA4XX)
{
int wanted;
- if (basic_errno != ERRNO_RCPT4XX) continue;
+ if (basic_errno != yield->basic_errno) continue;
wanted = (yield->more_errno >> 8) & 255;
if (wanted != 255)
{
/* Check for a match between the address list item at the start of this retry
rule and either the main or alternate keys. */
- if (match_address_list(use_key, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1,
+ if (match_address_list(key, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1,
NULL) == OK ||
- (use_alternate != NULL &&
- match_address_list(use_alternate, TRUE, TRUE, &plist, NULL, -1,
+ (alternate != NULL &&
+ match_address_list(alternate, TRUE, TRUE, &plist, NULL, -1,
UCHAR_MAX+1, NULL) == OK))
break;
}
-*colon = replace;
return yield;
}
DEBUG(D_retry)
{
if ((rti->flags & rf_host) != 0)
- debug_printf("retry for %s (%s) = %s\n", rti->key,
- addr->domain, retry->pattern);
+ debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
+ addr->domain, retry->pattern, retry->basic_errno,
+ retry->more_errno);
else
- debug_printf("retry for %s = %s\n", rti->key, retry->pattern);
+ debug_printf("retry for %s = %s %d %d\n", rti->key, retry->pattern,
+ retry->basic_errno, retry->more_errno);
}
/* Set up the message for the database retry record. Because DBM
message = (rti->basic_errno > 0)? US strerror(rti->basic_errno) :
(rti->message == NULL)?
- US"unknown error" : string_printing(rti->message);
+ US"unknown error" : US string_printing(rti->message);
message_length = Ustrlen(message);
if (message_length > 150) message_length = 150;
/* Compute how long this destination has been failing */
failing_interval = now - retry_record->first_failed;
+ DEBUG(D_retry) debug_printf("failing_interval=%d message_age=%d\n",
+ failing_interval, message_age);
+
+ /* For a non-host error, if the message has been on the queue longer
+ than the recorded time of failure, use the message's age instead. This
+ can happen when some messages can be delivered and others cannot; a
+ successful delivery will reset the first_failed time, and this can lead
+ to a failing message being retried too often. */
+
+ if ((rti->flags & rf_host) == 0 && message_age > failing_interval)
+ failing_interval = message_age;
/* Search for the current retry rule. The cutoff time of the
last rule is handled differently to the others. The rule continues
This implements "timeout this rule if EITHER the host (or routing or
directing) has been failing for more than the maximum time, OR if the
- message has been on the queue for more than the maximum time." */
+ message has been on the queue for more than the maximum time."
+
+ February 2006: It is possible that this code is no longer needed
+ following the change to the retry calculation to use the message age if
+ it is larger than the time since first failure. It may be that the
+ expired flag is always set when the other conditions are met. However,
+ this is a small bit of code, and it does no harm to leave it in place,
+ just in case. */
if (received_time <= retry_record->first_failed &&
addr == endaddr && !retry_record->expired && rule != NULL)
if (rule == NULL) next_try = now; else
{
if (rule->rule == 'F') next_try = now + rule->p1;
- else /* assume rule = 'G' */
+ else /* rule = 'G' or 'H' */
{
int last_predicted_gap =
retry_record->next_try - retry_record->last_try;
int last_actual_gap = now - retry_record->last_try;
int lastgap = (last_predicted_gap < last_actual_gap)?
last_predicted_gap : last_actual_gap;
- next_try = now + ((lastgap < rule->p1)? rule->p1 :
- (lastgap * rule->p2)/1000);
+ int next_gap = (lastgap * rule->p2)/1000;
+ if (rule->rule == 'G')
+ {
+ next_try = now + ((lastgap < rule->p1)? rule->p1 : next_gap);
+ }
+ else /* The 'H' rule */
+ {
+ next_try = now + rule->p1;
+ if (next_gap > rule->p1)
+ next_try += random_number(next_gap - rule->p1)/2 +
+ (next_gap - rule->p1)/2;
+ }
}
}
setflag(addr, af_retry_timedout);
addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
string_sprintf("%s: retry timeout exceeded", addr->message);
+ addr->user_message = (addr->user_message == NULL)?
+ US"retry timeout exceeded" :
+ string_sprintf("%s: retry timeout exceeded", addr->user_message);
log_write(0, LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
addr->address,
(addr->parent == NULL)? US"" : US" <",