* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Functions concerned with retrying unsuccessful deliveries. */
*/
BOOL
-retry_ultimate_address_timeout(uschar *retry_key, const uschar *domain,
+retry_ultimate_address_timeout(const uschar * retry_key, const uschar *domain,
dbdata_retry *retry_record, time_t now)
{
BOOL address_timeout;
+retry_config * retry;
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);
+ debug_printf(" now=" TIME_T_FMT " first_failed=" TIME_T_FMT
+ " next_try=" TIME_T_FMT " expired=%c\n",
+ now, retry_record->first_failed,
+ retry_record->next_try, retry_record->expired ? 'T' : 'F');
}
-retry_config *retry =
- retry_find_config(retry_key+2, domain,
+retry = retry_find_config(retry_key+2, domain,
retry_record->basic_errno, retry_record->more_errno);
-if (retry != NULL && retry->rules != NULL)
+if (retry && retry->rules)
{
retry_rule *last_rule;
- for (last_rule = retry->rules;
- last_rule->next != NULL;
- last_rule = last_rule->next);
+ for (last_rule = retry->rules; last_rule->next; last_rule = last_rule->next) ;
DEBUG(D_retry)
- debug_printf(" received_time=%d diff=%d timeout=%d\n",
+ debug_printf(" received_time=" TIME_T_FMT " diff=%d timeout=%d\n",
received_time.tv_sec, (int)(now - received_time.tv_sec), last_rule->timeout);
address_timeout = (now - received_time.tv_sec > last_rule->timeout);
}
+const uschar *
+retry_host_key_build(const host_item * host, BOOL incl_ip,
+ const uschar * portstring)
+{
+const uschar * s = host->name;
+gstring * g = string_is_ip_address(s, NULL)
+ ? string_fmt_append(NULL, "T:[%s]", s) /* wrap a name which is a bare ip */
+ : string_fmt_append(NULL, "T:%s", s);
+
+s = host->address;
+if (incl_ip)
+ g = Ustrchr(s, ':')
+ ? string_fmt_append(g, ":[%s]", s) /* wrap an ipv6 */
+ : string_fmt_append(g, ":%s", s);
+
+if (portstring)
+ g = string_cat(g, portstring);
+
+gstring_release_unused(g);
+return string_from_gstring(g);
+}
+
+
/*************************************************
* Set status of a host+address item *
*************************************************/
BOOL
retry_check_address(const uschar *domain, host_item *host, uschar *portstring,
- BOOL include_ip_address, uschar **retry_host_key, uschar **retry_message_key)
+ BOOL include_ip_address,
+ const uschar **retry_host_key, const uschar **retry_message_key)
{
BOOL yield = FALSE;
time_t now = time(NULL);
-uschar *host_key, *message_key;
-open_db dbblock;
-open_db *dbm_file;
-tree_node *node;
-dbdata_retry *host_retry_record, *message_retry_record;
+const uschar * host_key, * message_key;
+open_db dbblock, * dbm_file;
+tree_node * node;
+dbdata_retry * host_retry_record, * message_retry_record;
*retry_host_key = *retry_message_key = NULL;
-DEBUG(D_transport|D_retry) debug_printf("checking status of %s\n", host->name);
+DEBUG(D_transport|D_retry) debug_printf("checking retry status of %s\n", host->name);
/* Do nothing if status already set; otherwise initialize status as usable. */
host->status = hstatus_usable;
/* Generate the host key for the unusable tree and the retry database. Ensure
-host names are lower cased (that's what %S does). */
-
-host_key = include_ip_address?
- string_sprintf("T:%S:%s%s", host->name, host->address, portstring) :
- string_sprintf("T:%S%s", host->name, portstring);
-
-/* Generate the message-specific key */
+host names are lower cased (that's what %S does).
+Generate the message-specific key too.
+Be sure to maintain lack-of-spaces in retry keys; exinext depends on it. */
+host_key = retry_host_key_build(host, include_ip_address, portstring);
message_key = string_sprintf("%s:%s", host_key, message_id);
/* Search the tree of unusable IP addresses. This is filled in when deliveries
/* Open the retry database, giving up if there isn't one. Otherwise, search for
the retry records, and then close the database again. */
-if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
+if (!continue_retry_db)
+ dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE);
+else if ((dbm_file = continue_retry_db) == (open_db *)-1)
+ dbm_file = NULL;
+
+if (!dbm_file)
{
DEBUG(D_deliver|D_retry|D_hints_lookup)
debug_printf("no retry data available\n");
}
host_retry_record = dbfn_read(dbm_file, host_key);
message_retry_record = dbfn_read(dbm_file, message_key);
-dbfn_close(dbm_file);
+if (!continue_retry_db)
+ dbfn_close(dbm_file);
/* Ignore the data if it is too old - too long since it was written */
/* We have not reached the next try time. Check for the ultimate address
timeout if the host has not expired. */
- if (now < host_retry_record->next_try && !deliver_force)
+ if (now < host_retry_record->next_try && !f.deliver_force)
{
if (!host_retry_record->expired &&
retry_ultimate_address_timeout(host_key, domain,
if (message_retry_record)
{
*retry_message_key = message_key;
- if (now < message_retry_record->next_try && !deliver_force)
+ if (now < message_retry_record->next_try && !f.deliver_force)
{
if (!retry_ultimate_address_timeout(host_key, domain,
message_retry_record, now))
*/
void
-retry_add_item(address_item *addr, uschar *key, int flags)
+retry_add_item(address_item * addr, const uschar * key, int flags)
{
-retry_item *rti = store_get(sizeof(retry_item));
+retry_item * rti = store_get(sizeof(retry_item), GET_UNTAINTED);
host_item * host = addr->host_used;
rti->next = addr->retries;
*/
retry_config *
-retry_find_config(const uschar *key, const uschar *alternate, int basic_errno,
+retry_find_config(const uschar * key, const uschar * alternate, int basic_errno,
int more_errno)
{
-const uschar *colon = Ustrchr(key, ':');
-retry_config *yield;
+const uschar * colon = Ustrchr(key, ':');
+retry_config * yield;
/* If there's a colon in the key, there are two possibilities:
hostname:ip+port
- In this case, we copy the host name.
+ In this case, we copy the host name (which could be an [ip], including
+ being an [ipv6], and we drop the []).
(2) This is a key for a pipe, file, or autoreply delivery, in the format
if (colon)
key = isalnum(*key)
? string_copyn(key, colon-key) /* the hostname */
+ : *key == '['
+ ? string_copyn(key+1, Ustrchr(key, ']')-1-key) /* the ip */
: Ustrrchr(key, ':') + 1; /* Take from the last colon */
/* Sort out the keys */
*/
void
-retry_update(address_item **addr_defer, address_item **addr_failed,
- address_item **addr_succeed)
+retry_update(address_item ** addr_defer, address_item ** addr_failed,
+ address_item ** addr_succeed)
{
-open_db dbblock;
-open_db *dbm_file = NULL;
+open_db dbblock, * dbm_file = NULL;
time_t now = time(NULL);
-int i;
-DEBUG(D_retry) debug_printf("Processing retry items\n");
+DEBUG(D_retry) { debug_printf("Processing retry items\n"); acl_level++; }
/* Three-times loop to handle succeeded, failed, and deferred addresses.
Deferred addresses must be handled after failed ones, because some may be moved
to the failed chain if they have timed out. */
-for (i = 0; i < 3; i++)
+for (int i = 0; i < 3; i++)
{
- address_item *endaddr, *addr;
- address_item *last_first = NULL;
- address_item **paddr = i==0 ? addr_succeed :
- i==1 ? addr_failed : addr_defer;
- address_item **saved_paddr = NULL;
+ address_item * endaddr, *addr;
+ address_item * last_first = NULL;
+ address_item ** paddr = i==0 ? addr_succeed : i==1 ? addr_failed : addr_defer;
+ address_item ** saved_paddr = NULL;
- DEBUG(D_retry) debug_printf("%s addresses:\n",
+ DEBUG(D_retry) debug_printf_indent("%s addresses:\n",
i == 0 ? "Succeeded" : i == 1 ? "Failed" : "Deferred");
/* Loop for each address on the chain. For deferred addresses, the whole
while ((endaddr = *paddr))
{
BOOL timed_out = FALSE;
- retry_item *rti;
for (addr = endaddr; addr; addr = addr->parent)
{
int update_count = 0;
int timedout_count = 0;
- DEBUG(D_retry) debug_printf(" %s%s\n", addr->address,
+ DEBUG(D_retry) debug_printf_indent(" %s%s\n", addr->address,
addr->retries ? "" : ": no retry items");
/* Loop for each retry item. */
- for (rti = addr->retries; rti; rti = rti->next)
+ for (retry_item * rti = addr->retries; rti; rti = rti->next)
{
uschar *message;
int message_length, message_space, failing_interval, next_try;
reached their retry next try time. */
if (!dbm_file)
- dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE);
+ dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE, TRUE);
if (!dbm_file)
{
DEBUG(D_deliver|D_retry|D_hints_lookup)
- debug_printf("retry database not available for updating\n");
+ debug_printf_indent("retry database not available for updating\n");
return;
}
{
(void)dbfn_delete(dbm_file, rti->key);
DEBUG(D_retry)
- debug_printf("deleted retry information for %s\n", rti->key);
+ debug_printf_indent("deleted retry information for %s\n", rti->key);
continue;
}
rti->flags & rf_host ? addr->domain : NULL,
rti->basic_errno, rti->more_errno)))
{
- DEBUG(D_retry) debug_printf("No configured retry item for %s%s%s\n",
+ DEBUG(D_retry) debug_printf_indent("No configured retry item for %s%s%s\n",
rti->key,
rti->flags & rf_host ? US" or " : US"",
rti->flags & rf_host ? addr->domain : US"");
DEBUG(D_retry)
if (rti->flags & rf_host)
- debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
+ debug_printf_indent("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 %d %d\n", rti->key, retry->pattern,
+ debug_printf_indent("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
? US string_printing(rti->message)
: US"unknown error";
message_length = Ustrlen(message);
- if (message_length > 150) message_length = 150;
+ if (message_length > EXIM_DB_RLIMIT)
+ {
+ DEBUG(D_retry)
+ debug_printf_indent("truncating message from %u to %u bytes\n",
+ message_length, EXIM_DB_RLIMIT);
+ message_length = EXIM_DB_RLIMIT;
+ }
/* Read a retry record from the database or construct a new one.
Ignore an old one if it is too old since it was last updated. */
- retry_record = dbfn_read(dbm_file, rti->key);
+ retry_record = dbfn_read_with_length(dbm_file, rti->key,
+ &message_space);
if ( retry_record
&& now - retry_record->time_stamp > retry_data_expire)
retry_record = NULL;
if (!retry_record)
{
- retry_record = store_get(sizeof(dbdata_retry) + message_length);
+ retry_record = store_get(sizeof(dbdata_retry) + message_length,
+ message);
message_space = message_length;
retry_record->first_failed = now;
retry_record->last_try = now;
retry_record->expired = FALSE;
retry_record->text[0] = 0; /* just in case */
}
- else message_space = Ustrlen(retry_record->text);
+ else message_space -= sizeof(dbdata_retry);
/* 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",
+ DEBUG(D_retry) debug_printf_indent("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
;
if (now - received_time.tv_sec > last_rule->timeout)
{
- DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n");
+ DEBUG(D_retry) debug_printf_indent("on queue longer than maximum retry\n");
timedout_count++;
rule = NULL;
}
if (next_try - now > retry_interval_max)
next_try = now + retry_interval_max;
- /* If the new message length is greater than the previous one, we
- have to copy the record first. */
+ /* If the new message length is greater than the previous one, we have
+ to copy the record first. If we're using an old one, the read used
+ tainted memory so we're ok to write into it. */
- if (message_length > message_space)
- {
- dbdata_retry *newr = store_get(sizeof(dbdata_retry) + message_length);
- memcpy(newr, retry_record, sizeof(dbdata_retry));
- retry_record = newr;
- }
+ if (message_length > message_space)
+ {
+ dbdata_retry * newr =
+ store_get(sizeof(dbdata_retry) + message_length, message);
+ memcpy(newr, retry_record, sizeof(dbdata_retry));
+ retry_record = newr;
+ }
/* Set up the retry record; message_length may be less than the string
length for very long error strings. */
retry_record->basic_errno = rti->basic_errno;
retry_record->more_errno = rti->more_errno;
Ustrncpy(retry_record->text, message, message_length);
- retry_record->text[message_length] = 0;
+ retry_record->text[message_length] = 0; /* nul-term string in db */
DEBUG(D_retry)
{
int letter = retry_record->more_errno & 255;
- debug_printf("Writing retry data for %s\n", rti->key);
- debug_printf(" first failed=%d last try=%d next try=%d expired=%d\n",
+ debug_printf_indent("Writing retry data for %s\n", rti->key);
+ debug_printf_indent(" first failed=%d last try=%d next try=%d expired=%d\n",
(int)retry_record->first_failed, (int)retry_record->last_try,
(int)retry_record->next_try, retry_record->expired);
- debug_printf(" errno=%d more_errno=", retry_record->basic_errno);
+ debug_printf_indent(" errno=%d more_errno=", retry_record->basic_errno);
if (letter == 'A' || letter == 'M')
debug_printf("%d,%c", (retry_record->more_errno >> 8) & 255,
letter);
if (update_count > 0 && update_count == timedout_count)
if (!testflag(endaddr, af_retry_skipped))
{
- DEBUG(D_retry) debug_printf("timed out: all retries expired\n");
+ DEBUG(D_retry) debug_printf_indent("timed out: all retries expired\n");
timed_out = TRUE;
}
else
DEBUG(D_retry)
- debug_printf("timed out but some hosts were skipped\n");
+ debug_printf_indent("timed out but some hosts were skipped\n");
} /* Loop for an address and its parents */
/* If this is a deferred address, and retry processing was requested by
for (;; addr = addr->next)
{
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);
+ addr->message = addr->message
+ ? string_sprintf("%s: retry timeout exceeded", addr->message)
+ : US"retry timeout exceeded";
+ addr->user_message = addr->user_message
+ ? string_sprintf("%s: retry timeout exceeded", addr->user_message)
+ : US"retry timeout exceeded";
log_write(0, LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
addr->address,
- (addr->parent == NULL)? US"" : US" <",
- (addr->parent == NULL)? US"" : addr->parent->address,
- (addr->parent == NULL)? US"" : US">");
+ addr->parent ? US" <" : US"",
+ addr->parent ? addr->parent->address : US"",
+ addr->parent ? US">" : US"");
if (addr == endaddr) break;
}
if (dbm_file) dbfn_close(dbm_file);
-DEBUG(D_retry) debug_printf("end of retry processing\n");
+DEBUG(D_retry) { acl_level--; debug_printf("end of retry processing\n"); }
}
/* End of retry.c */
+/* vi: aw ai sw=2
+*/