Transactions in retry hintsdb
[exim.git] / src / src / retry.c
index 1993b776877d1aef60b0fb399ae64ad4c06d91e3..fdcb6abea8a48f813315d34b9f88e06168a4c527 100644 (file)
@@ -2,9 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Functions concerned with retrying unsuccessful deliveries. */
 
@@ -30,7 +31,7 @@ Returns:        TRUE if the ultimate timeout has been reached
 */
 
 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;
@@ -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          *
 *************************************************/
@@ -123,19 +147,19 @@ Returns:    TRUE if the host has expired but is usable because
 
 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. */
 
@@ -143,14 +167,11 @@ 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);
-
-/* 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
@@ -171,7 +192,12 @@ if ((node = tree_search(tree_unusable, host_key)))
 /* 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, TRUE)))
+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");
@@ -179,7 +205,8 @@ if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
   }
 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 */
 
@@ -290,9 +317,9 @@ Returns:  nothing
 */
 
 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), FALSE);
+retry_item * rti = store_get(sizeof(retry_item), GET_UNTAINTED);
 host_item * host = addr->host_used;
 
 rti->next = addr->retries;
@@ -343,11 +370,11 @@ Returns:       pointer to retry rule, or NULL
 */
 
 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:
 
@@ -355,7 +382,8 @@ retry_config *yield;
 
       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
 
@@ -369,6 +397,8 @@ retry_config *yield;
 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 */
@@ -518,14 +548,13 @@ Returns:        nothing
 */
 
 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);
 
-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
@@ -533,13 +562,12 @@ to the failed chain if they have timed out. */
 
 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
@@ -561,7 +589,7 @@ for (int i = 0; i < 3; i++)
       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. */
@@ -586,7 +614,7 @@ for (int i = 0; i < 3; i++)
         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;
           }
 
@@ -608,7 +636,7 @@ for (int i = 0; i < 3; i++)
           {
           (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;
           }
 
@@ -628,7 +656,7 @@ for (int i = 0; i < 3; i++)
              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"");
@@ -638,11 +666,11 @@ for (int i = 0; i < 3; i++)
 
         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
@@ -655,7 +683,13 @@ for (int i = 0; i < 3; i++)
          ? US string_printing(rti->message)
          : US"unknown error";
         message_length = Ustrlen(message);
-        if (message_length > EXIM_DB_RLIMIT) message_length = EXIM_DB_RLIMIT;
+        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. */
@@ -669,7 +703,7 @@ for (int i = 0; i < 3; i++)
         if (!retry_record)
           {
           retry_record = store_get(sizeof(dbdata_retry) + message_length,
-                                  is_tainted(message));
+                                  message);
           message_space = message_length;
           retry_record->first_failed = now;
           retry_record->last_try = now;
@@ -682,7 +716,7 @@ for (int i = 0; i < 3; i++)
         /* 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
@@ -764,7 +798,7 @@ for (int i = 0; i < 3; i++)
            ;
           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;
             }
@@ -815,7 +849,7 @@ for (int i = 0; i < 3; i++)
        if (message_length > message_space)
          {
          dbdata_retry * newr =
-           store_get(sizeof(dbdata_retry) + message_length, is_tainted(message));
+           store_get(sizeof(dbdata_retry) + message_length, message);
          memcpy(newr, retry_record, sizeof(dbdata_retry));
          retry_record = newr;
          }
@@ -828,16 +862,16 @@ for (int 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->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);
@@ -857,12 +891,12 @@ for (int i = 0; i < 3; i++)
       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
@@ -929,7 +963,9 @@ for (int i = 0; i < 3; i++)
 
 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
+*/