* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* 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",
- (int)received_time.tv_sec, (int)(now - received_time.tv_sec), last_rule->timeout);
+ 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);
}
else
+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 (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
{
DEBUG(D_deliver|D_retry|D_hints_lookup)
debug_printf("no retry data available\n");
/* 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;
time_t now = time(NULL);
-int i;
DEBUG(D_retry) debug_printf("Processing retry items\n");
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",
i == 0 ? "Succeeded" : i == 1 ? "Failed" : "Deferred");
while ((endaddr = *paddr))
{
BOOL timed_out = FALSE;
- retry_item *rti;
for (addr = endaddr; addr; addr = addr->parent)
{
/* 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)
{
? 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 */
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)
{
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;
}
}
/* End of retry.c */
+/* vi: aw ai sw=2
+*/