build: use pkg-config for i18n
[exim.git] / src / src / retry.c
index a040c33108a572fa13091f6627f07632fc68a234..6e4a3459dae7ba5dbcc25a2e3fc0bbc28f95e678 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* 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. */
 
 
 /* Functions concerned with retrying unsuccessful deliveries. */
 
@@ -29,32 +31,31 @@ Returns:        TRUE if the ultimate timeout has been reached
 */
 
 BOOL
 */
 
 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;
   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(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);
 
     retry_record->basic_errno, retry_record->more_errno);
 
-if (retry != NULL && retry->rules != NULL)
+if (retry && retry->rules)
   {
   retry_rule *last_rule;
   {
   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(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
   address_timeout = (now - received_time.tv_sec > last_rule->timeout);
   }
 else
@@ -74,6 +75,29 @@ return address_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          *
 *************************************************/
 /*************************************************
 *     Set status of a host+address item          *
 *************************************************/
@@ -123,34 +147,35 @@ Returns:    TRUE if the host has expired but is usable because
 
 BOOL
 retry_check_address(const uschar *domain, host_item *host, uschar *portstring,
 
 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);
 {
 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 = NULL;
+tree_node * node;
+dbdata_retry * host_retry_record, * message_retry_record;
 
 *retry_host_key = *retry_message_key = NULL;
 
 
 *retry_host_key = *retry_message_key = NULL;
 
-DEBUG(D_transport|D_retry) debug_printf("checking status of %s\n", host->name);
-
 /* Do nothing if status already set; otherwise initialize status as usable. */
 
 if (host->status != hstatus_unknown) return FALSE;
 host->status = hstatus_usable;
 
 /* Do nothing if status already set; otherwise initialize status as usable. */
 
 if (host->status != hstatus_unknown) return FALSE;
 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);
+DEBUG(D_transport|D_retry)
+  {
+  debug_printf_indent("checking retry status of %s\n", host->name);
+  acl_level++;
+  }
 
 
-/* Generate the message-specific key */
+/* Generate the host key for the unusable tree and the retry database. Ensure
+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
 message_key = string_sprintf("%s:%s", host_key, message_id);
 
 /* Search the tree of unusable IP addresses. This is filled in when deliveries
@@ -161,46 +186,62 @@ the retry database when it is updated). */
 
 if ((node = tree_search(tree_unusable, host_key)))
   {
 
 if ((node = tree_search(tree_unusable, host_key)))
   {
-  DEBUG(D_transport|D_retry) debug_printf("found in tree of unusables\n");
-  host->status = (node->data.val > 255)?
-    hstatus_unusable_expired : hstatus_unusable;
+  DEBUG(D_transport|D_retry)
+    debug_printf_indent("found in tree of unusables\n");
+  host->status = node->data.val > 255
+    ? hstatus_unusable_expired : hstatus_unusable;
   host->why = node->data.val & 255;
   host->why = node->data.val & 255;
-  return FALSE;
+  goto out;
   }
 
 /* Open the retry database, giving up if there isn't one. Otherwise, search for
 the retry records, and then close the database again. */
 
   }
 
 /* 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 (continue_retry_db != (open_db *)-1)
+  {
+  DEBUG(D_hints_lookup)
+    debug_printf_indent(" using cached retry hintsdb handle\n");
+  dbm_file = continue_retry_db;
+  }
+else DEBUG(D_hints_lookup)
+    debug_printf_indent(" using cached retry hintsdb nonpresence\n");
+
+if (!dbm_file)
   {
   DEBUG(D_deliver|D_retry|D_hints_lookup)
   {
   DEBUG(D_deliver|D_retry|D_hints_lookup)
-    debug_printf("no retry data available\n");
-  return FALSE;
+    debug_printf_indent("no retry data available\n");
+  goto out;
   }
 host_retry_record = dbfn_read(dbm_file, host_key);
 message_retry_record = dbfn_read(dbm_file, message_key);
   }
 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);
+else
+  DEBUG(D_hints_lookup) debug_printf_indent("retaining retry hintsdb handle\n");
 
 /* Ignore the data if it is too old - too long since it was written */
 
 if (!host_retry_record)
   {
 
 /* Ignore the data if it is too old - too long since it was written */
 
 if (!host_retry_record)
   {
-  DEBUG(D_transport|D_retry) debug_printf("no host retry record\n");
+  DEBUG(D_transport|D_retry) debug_printf_indent("no host retry record\n");
   }
 else if (now - host_retry_record->time_stamp > retry_data_expire)
   {
   host_retry_record = NULL;
   }
 else if (now - host_retry_record->time_stamp > retry_data_expire)
   {
   host_retry_record = NULL;
-  DEBUG(D_transport|D_retry) debug_printf("host retry record too old\n");
+  DEBUG(D_transport|D_retry) debug_printf_indent("host retry record too old\n");
   }
 
 if (!message_retry_record)
   {
   }
 
 if (!message_retry_record)
   {
-  DEBUG(D_transport|D_retry) debug_printf("no message retry record\n");
+  DEBUG(D_transport|D_retry) debug_printf_indent("no message retry record\n");
   }
 else if (now - message_retry_record->time_stamp > retry_data_expire)
   {
   message_retry_record = NULL;
   }
 else if (now - message_retry_record->time_stamp > retry_data_expire)
   {
   message_retry_record = NULL;
-  DEBUG(D_transport|D_retry) debug_printf("message retry record too old\n");
+  DEBUG(D_transport|D_retry)
+    debug_printf_indent("message retry record too old\n");
   }
 
 /* If there's a host-specific retry record, check for reaching the retry
   }
 
 /* If there's a host-specific retry record, check for reaching the retry
@@ -222,7 +263,7 @@ if (host_retry_record)
     if (!host_retry_record->expired &&
         retry_ultimate_address_timeout(host_key, domain,
           host_retry_record, now))
     if (!host_retry_record->expired &&
         retry_ultimate_address_timeout(host_key, domain,
           host_retry_record, now))
-      return FALSE;
+      goto out;
 
     /* We have not hit the ultimate address timeout; host is unusable. */
 
 
     /* We have not hit the ultimate address timeout; host is unusable. */
 
@@ -230,7 +271,7 @@ if (host_retry_record)
       hstatus_unusable_expired : hstatus_unusable;
     host->why = hwhy_retry;
     host->last_try = host_retry_record->last_try;
       hstatus_unusable_expired : hstatus_unusable;
     host->why = hwhy_retry;
     host->last_try = host_retry_record->last_try;
-    return FALSE;
+    goto out;
     }
 
   /* Host is usable; set return TRUE if expired. */
     }
 
   /* Host is usable; set return TRUE if expired. */
@@ -253,10 +294,12 @@ if (message_retry_record)
       host->status = hstatus_unusable;
       host->why = hwhy_retry;
       }
       host->status = hstatus_unusable;
       host->why = hwhy_retry;
       }
-    return FALSE;
+    yield = FALSE; goto out;
     }
   }
 
     }
   }
 
+out:
+DEBUG(D_transport|D_retry) acl_level--;
 return yield;
 }
 
 return yield;
 }
 
@@ -290,9 +333,9 @@ Returns:  nothing
 */
 
 void
 */
 
 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;
 host_item * host = addr->host_used;
 
 rti->next = addr->retries;
@@ -308,7 +351,9 @@ rti->flags = flags;
 DEBUG(D_transport|D_retry)
   {
   int letter = rti->more_errno & 255;
 DEBUG(D_transport|D_retry)
   {
   int letter = rti->more_errno & 255;
-  debug_printf("added retry item for %s: errno=%d more_errno=", rti->key,
+  debug_printf("added retry %sitem for %s: errno=%d more_errno=",
+    flags & rf_delete ? "delete-" : "",
+    rti->key,
     rti->basic_errno);
   if (letter == 'A' || letter == 'M')
     debug_printf("%d,%c", (rti->more_errno >> 8) & 255, letter);
     rti->basic_errno);
   if (letter == 'A' || letter == 'M')
     debug_printf("%d,%c", (rti->more_errno >> 8) & 255, letter);
@@ -343,11 +388,11 @@ Returns:       pointer to retry rule, or NULL
 */
 
 retry_config *
 */
 
 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)
 {
   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:
 
 
 /* If there's a colon in the key, there are two possibilities:
 
@@ -355,7 +400,8 @@ retry_config *yield;
 
       hostname:ip+port
 
 
       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
 
 
 (2) This is a key for a pipe, file, or autoreply delivery, in the format
 
@@ -369,6 +415,8 @@ retry_config *yield;
 if (colon)
   key = isalnum(*key)
     ? string_copyn(key, colon-key)     /* the hostname */
 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 */
     : Ustrrchr(key, ':') + 1;          /* Take from the last colon */
 
 /* Sort out the keys */
@@ -501,6 +549,7 @@ return yield;
 /* Update the retry data for any directing/routing/transporting that was
 deferred, or delete it for those that succeeded after a previous defer. This is
 done all in one go to minimize opening/closing/locking of the database file.
 /* Update the retry data for any directing/routing/transporting that was
 deferred, or delete it for those that succeeded after a previous defer. This is
 done all in one go to minimize opening/closing/locking of the database file.
+Called (only) from deliver_message().
 
 Note that, because SMTP delivery involves a list of destinations to try, there
 may be defer-type retry information for some of them even when the message was
 
 Note that, because SMTP delivery involves a list of destinations to try, there
 may be defer-type retry information for some of them even when the message was
@@ -518,30 +567,31 @@ Returns:        nothing
 */
 
 void
 */
 
 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);
 time_t now = time(NULL);
-int i;
 
 
-DEBUG(D_retry) debug_printf("Processing retry items\n");
+DEBUG(D_retry) { debug_printf_indent("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. */
 
 
 /* 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",
-    i == 0 ? "Succeeded" : i == 1 ? "Failed" : "Deferred");
+  DEBUG(D_retry)
+    {
+    debug_printf_indent("%s addresses:\n",
+      i == 0 ? "Succeeded" : i == 1 ? "Failed" : "Deferred");
+    acl_level++;
+    }
 
   /* Loop for each address on the chain. For deferred addresses, the whole
   address times out unless one of its retry addresses has a retry rule that
 
   /* Loop for each address on the chain. For deferred addresses, the whole
   address times out unless one of its retry addresses has a retry rule that
@@ -556,19 +606,21 @@ for (i = 0; i < 3; i++)
   while ((endaddr = *paddr))
     {
     BOOL timed_out = FALSE;
   while ((endaddr = *paddr))
     {
     BOOL timed_out = FALSE;
-    retry_item *rti;
 
     for (addr = endaddr; addr; addr = addr->parent)
       {
 
     for (addr = endaddr; addr; addr = addr->parent)
       {
-      int update_count = 0;
-      int timedout_count = 0;
+      int update_count = 0, timedout_count = 0;
 
 
-      DEBUG(D_retry) debug_printf(" %s%s\n", addr->address,
-               addr->retries ? "" : ": no retry items");
+      DEBUG(D_retry)
+       {
+       debug_printf_indent("%s%s\n", addr->address,
+                           addr->retries ? "" : ": no retry items");
+       acl_level++;
+       }
 
       /* Loop for each retry item. */
 
 
       /* 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;
         {
         uschar *message;
         int message_length, message_space, failing_interval, next_try;
@@ -583,14 +635,20 @@ for (i = 0; i < 3; i++)
         reached their retry next try time. */
 
         if (!dbm_file)
         reached their retry next try time. */
 
         if (!dbm_file)
-          dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE);
-
-        if (!dbm_file)
-          {
-          DEBUG(D_deliver|D_retry|D_hints_lookup)
-            debug_printf("retry database not available for updating\n");
-          return;
-          }
+         if (continue_retry_db && continue_retry_db != (open_db *)-1)
+           {
+           DEBUG(D_hints_lookup)
+             debug_printf_indent("using cached retry hintsdb handle\n");
+           dbm_file = continue_retry_db;
+           }
+         else if (!(dbm_file = exim_lockfile_needed()
+                   ? dbfn_open(US"retry", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE)
+                   : dbfn_open_multi(US"retry", O_RDWR|O_CREAT, &dbblock)))
+           {
+           DEBUG(D_deliver|D_retry|D_hints_lookup)
+             debug_printf_indent("retry db not available for updating\n");
+           return;
+           }
 
         /* If there are no deferred addresses, that is, if this message is
         completing, and the retry item is for a message-specific SMTP error,
 
         /* If there are no deferred addresses, that is, if this message is
         completing, and the retry item is for a message-specific SMTP error,
@@ -610,7 +668,7 @@ for (i = 0; i < 3; i++)
           {
           (void)dbfn_delete(dbm_file, rti->key);
           DEBUG(D_retry)
           {
           (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;
           }
 
           continue;
           }
 
@@ -630,7 +688,7 @@ for (i = 0; i < 3; i++)
              rti->flags & rf_host ? addr->domain : NULL,
              rti->basic_errno, rti->more_errno)))
           {
              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"");
             rti->key,
             rti->flags & rf_host ? US" or " : US"",
             rti->flags & rf_host ? addr->domain : US"");
@@ -640,11 +698,11 @@ for (i = 0; i < 3; i++)
 
         DEBUG(D_retry)
           if (rti->flags & rf_host)
 
         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
               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
               retry->basic_errno, retry->more_errno);
 
         /* Set up the message for the database retry record. Because DBM
@@ -657,19 +715,33 @@ for (i = 0; i < 3; i++)
          ? US string_printing(rti->message)
          : US"unknown error";
         message_length = Ustrlen(message);
          ? 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;
+         }
+
+       /* For a transaction-capable DB, open one for the read,write
+       sequence used for this retry record */
+
+       if (!exim_lockfile_needed())
+         dbfn_transaction_start(dbm_file);
 
         /* 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. */
 
 
         /* 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)
           {
         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;
           message_space = message_length;
           retry_record->first_failed = now;
           retry_record->last_try = now;
@@ -677,12 +749,12 @@ for (i = 0; i < 3; i++)
           retry_record->expired = FALSE;
           retry_record->text[0] = 0;      /* just in case */
           }
           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;
 
         /* 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
           failing_interval, message_age);
 
         /* For a non-host error, if the message has been on the queue longer
@@ -764,7 +836,7 @@ for (i = 0; i < 3; i++)
            ;
           if (now - received_time.tv_sec > last_rule->timeout)
             {
            ;
           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;
             }
             timedout_count++;
             rule = NULL;
             }
@@ -808,15 +880,17 @@ for (i = 0; i < 3; i++)
         if (next_try - now > retry_interval_max)
           next_try = now + retry_interval_max;
 
         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. */
 
         /* Set up the retry record; message_length may be less than the string
         length for very long error strings. */
@@ -826,16 +900,16 @@ for (i = 0; i < 3; i++)
         retry_record->basic_errno = rti->basic_errno;
         retry_record->more_errno = rti->more_errno;
         Ustrncpy(retry_record->text, message, message_length);
         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(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);
             (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 (letter == 'A' || letter == 'M')
             debug_printf("%d,%c", (retry_record->more_errno >> 8) & 255,
               letter);
@@ -844,9 +918,14 @@ for (i = 0; i < 3; i++)
           debug_printf(" %s\n", retry_record->text);
           }
 
           debug_printf(" %s\n", retry_record->text);
           }
 
-        (void)dbfn_write(dbm_file, rti->key, retry_record,
-          sizeof(dbdata_retry) + message_length);
+        if (dbfn_write(dbm_file, rti->key, retry_record,
+                     sizeof(dbdata_retry) + message_length) != 0)
+         DEBUG(D_retry) debug_printf_indent("retry record write failed\n");
+
+       if (!exim_lockfile_needed())
+         dbfn_transaction_commit(dbm_file);
         }                            /* Loop for each retry item */
         }                            /* Loop for each retry item */
+      DEBUG(D_retry) acl_level--;
 
       /* If all the non-delete retry items are timed out, the address is
       timed out, provided that we didn't skip any hosts because their retry
 
       /* If all the non-delete retry items are timed out, the address is
       timed out, provided that we didn't skip any hosts because their retry
@@ -855,12 +934,12 @@ for (i = 0; i < 3; i++)
       if (update_count > 0 && update_count == timedout_count)
         if (!testflag(endaddr, af_retry_skipped))
           {
       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)
           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
       }     /* Loop for an address and its parents */
 
     /* If this is a deferred address, and retry processing was requested by
@@ -888,16 +967,17 @@ for (i = 0; i < 3; i++)
         for (;; addr = addr->next)
           {
           setflag(addr, af_retry_timedout);
         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,
           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 (addr == endaddr) break;
           }
@@ -920,13 +1000,24 @@ for (i = 0; i < 3; i++)
 
     paddr = &(endaddr->next);         /* Advance to next address */
     }                                 /* Loop for all addresses  */
 
     paddr = &(endaddr->next);         /* Advance to next address */
     }                                 /* Loop for all addresses  */
+  DEBUG(D_retry) acl_level--;
   }                                   /* Loop for succeed, fail, defer */
 
 /* Close and unlock the database */
 
   }                                   /* Loop for succeed, fail, defer */
 
 /* Close and unlock the database */
 
-if (dbm_file) dbfn_close(dbm_file);
+if (dbm_file)
+  if (dbm_file != continue_retry_db)
+      if (exim_lockfile_needed())
+       dbfn_close(dbm_file);
+      else
+       dbfn_close_multi(dbm_file);
+  else DEBUG(D_hints_lookup)
+    debug_printf_indent("retaining retry hintsdb handle\n");
 
 
-DEBUG(D_retry) debug_printf("end of retry processing\n");
+DEBUG(D_retry)
+  { acl_level--; debug_printf_indent("end of retry processing\n"); }
 }
 
 /* End of retry.c */
 }
 
 /* End of retry.c */
+/* vi: aw ai sw=2
+*/