4.89 JH/17 -> 4.90 JH/01
[exim.git] / src / src / retry.c
index e877bf7d0157d7d3eba76d3c66c16a11ad89744b..364591bd09214a617794d557fde67d7471cb0dca 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* 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)
   {
@@ -44,17 +52,23 @@ if (retry != NULL && retry->rules != NULL)
   for (last_rule = retry->rules;
        last_rule->next != NULL;
        last_rule = last_rule->next);
-  DEBUG(D_transport|D_retry)
+  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_transport|D_retry)
+  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;
 }
 
@@ -108,7 +122,7 @@ Returns:    TRUE if the host has expired but is usable because
 */
 
 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;
@@ -206,25 +220,10 @@ if (host_retry_record != NULL)
 
   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");
-      debug_printf("  now=%d first_failed=%d next_try=%d expired=%d\n",
-        (int)now, (int)host_retry_record->first_failed,
-        (int)host_retry_record->next_try,
-        host_retry_record->expired);
-      }
-
     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. */
 
@@ -249,25 +248,12 @@ if (message_retry_record != NULL)
   *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");
-      debug_printf("  now=%d first_failed=%d next_try=%d expired=%d\n",
-        (int)now, (int)message_retry_record->first_failed,
-        (int)message_retry_record->next_try, message_retry_record->expired);
-      }
-    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;
     }
   }
@@ -308,12 +294,15 @@ void
 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)
@@ -354,12 +343,10 @@ Returns:       pointer to retry rule, or NULL
 */
 
 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 = 0;
-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, there are two possibilities:
@@ -368,8 +355,7 @@ retry_config *yield;
 
       hostname:ip+port
 
-    In this case, we temporarily replace the colon with a zero, to terminate
-    the string after the host name.
+    In this case, we copy the host name.
 
 (2) This is a key for a pipe, file, or autoreply delivery, in the format
 
@@ -380,28 +366,22 @@ retry_config *yield;
     with a letter or a digit. In this case we want to use the original address
     to search for a retry rule. */
 
-if (colon != NULL)
-  {
-  if (isalnum(*key))
-    replace = ':';
-  else
-    key = Ustrrchr(key, ':') + 1;   /* Take from the last colon */
-  }
-
-if (replace == 0) colon = key + Ustrlen(key);
-*colon = 0;
+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
@@ -500,15 +480,14 @@ for (yield = retries; yield != NULL; yield = yield->next)
   /* 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;
 }
 
@@ -557,12 +536,12 @@ for (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 **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("%s addresses:\n",
+    i == 0 ? "Succeeded" : i == 1 ? "Failed" : "Deferred");
 
   /* 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
@@ -574,22 +553,22 @@ for (i = 0; i < 3; i++)
   retry items for any parent addresses - these are typically "delete" items,
   because the parent must have succeeded in order to generate the child. */
 
-  while ((endaddr = *paddr) != NULL)
+  while ((endaddr = *paddr))
     {
     BOOL timed_out = FALSE;
     retry_item *rti;
 
-    for (addr = endaddr; addr != NULL; addr = addr->parent)
+    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, (addr->retries == NULL)?
-        ": no retry items" : "");
+      DEBUG(D_retry) debug_printf(" %s%s\n", addr->address,
+               addr->retries ? "" : ": no retry items");
 
       /* Loop for each retry item. */
 
-      for (rti = addr->retries; rti != NULL; rti = rti->next)
+      for (rti = addr->retries; rti; rti = rti->next)
         {
         uschar *message;
         int message_length, message_space, failing_interval, next_try;
@@ -603,10 +582,10 @@ for (i = 0; i < 3; i++)
         opening if no addresses have retry items - common when none have yet
         reached their retry next try time. */
 
-        if (dbm_file == NULL)
+        if (!dbm_file)
           dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE);
 
-        if (dbm_file == NULL)
+        if (!dbm_file)
           {
           DEBUG(D_deliver|D_retry|D_hints_lookup)
             debug_printf("retry database not available for updating\n");
@@ -621,13 +600,13 @@ for (i = 0; i < 3; i++)
         but the address gets delivered to the second one. This optimization
         doesn't succeed in cleaning out all the dead entries, but it helps. */
 
-        if (*addr_defer == NULL && (rti->flags & rf_message) != 0)
+        if (!*addr_defer  &&  rti->flags & rf_message)
           rti->flags |= rf_delete;
 
         /* Handle the case of a request to delete the retry info for this
         destination. */
 
-        if ((rti->flags & rf_delete) != 0)
+        if (rti->flags & rf_delete)
           {
           (void)dbfn_delete(dbm_file, rti->key);
           DEBUG(D_retry)
@@ -647,21 +626,21 @@ for (i = 0; i < 3; i++)
         information is found, we can't generate a retry time, so there is
         no point updating the database. This retry item is timed out. */
 
-        if ((retry = retry_find_config(rti->key + 2,
-             ((rti->flags & rf_host) != 0)? addr->domain : NULL,
-             rti->basic_errno, rti->more_errno)) == NULL)
+        if (!(retry = retry_find_config(rti->key + 2,
+             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",
             rti->key,
-            ((rti->flags & rf_host) != 0)? US" or " : US"",
-            ((rti->flags & rf_host) != 0)? addr->domain : US"");
+            rti->flags & rf_host ? US" or " : US"",
+            rti->flags & rf_host ? addr->domain : US"");
           if (addr == endaddr) timedout_count++;
           continue;
           }
 
         DEBUG(D_retry)
           {
-          if ((rti->flags & rf_host) != 0)
+          if (rti->flags & rf_host)
             debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
               addr->domain, retry->pattern, retry->basic_errno,
               retry->more_errno);
@@ -674,9 +653,11 @@ for (i = 0; i < 3; i++)
         records have a maximum data length, we enforce a limit. There isn't
         much point in keeping a huge message here, anyway. */
 
-        message = (rti->basic_errno > 0)? US strerror(rti->basic_errno) :
-          (rti->message == NULL)?
-          US"unknown error" : string_printing(rti->message);
+        message = rti->basic_errno > 0
+         ? US strerror(rti->basic_errno)
+         : rti->message
+         ? US string_printing(rti->message)
+         : US"unknown error";
         message_length = Ustrlen(message);
         if (message_length > 150) message_length = 150;
 
@@ -684,11 +665,11 @@ for (i = 0; i < 3; i++)
         Ignore an old one if it is too old since it was last updated. */
 
         retry_record = dbfn_read(dbm_file, rti->key);
-        if (retry_record != NULL &&
-            now - retry_record->time_stamp > retry_data_expire)
+        if (  retry_record
+          && now - retry_record->time_stamp > retry_data_expire)
           retry_record = NULL;
 
-        if (retry_record == NULL)
+        if (!retry_record)
           {
           retry_record = store_get(sizeof(dbdata_retry) + message_length);
           message_space = message_length;
@@ -712,7 +693,7 @@ for (i = 0; i < 3; i++)
         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)
+        if (!(rti->flags & rf_host) && message_age > failing_interval)
           failing_interval = message_age;
 
         /* Search for the current retry rule. The cutoff time of the
@@ -723,7 +704,7 @@ for (i = 0; i < 3; i++)
         always times out, but we can't compute a retry time. */
 
         final_rule = NULL;
-        for (rule = retry->rules; rule != NULL; rule = rule->next)
+        for (rule = retry->rules; rule; rule = rule->next)
           {
           if (failing_interval <= rule->timeout) break;
           final_rule = rule;
@@ -735,10 +716,8 @@ for (i = 0; i < 3; i++)
         flag is false (can be forced via fixdb from outside, but ensure it is
         consistent with the rules whenever we go through here). */
 
-        if (rule != NULL)
-          {
+        if (rule)
           retry_record->expired = FALSE;
-          }
 
         /* Otherwise, set the retry timeout expired, and set the final rule
         as the one from which to compute the next retry time. Subsequent
@@ -777,13 +756,14 @@ for (i = 0; i < 3; i++)
         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 (  received_time <= retry_record->first_failed
+          && addr == endaddr
+          && !retry_record->expired
+          && rule)
           {
           retry_rule *last_rule;
-          for (last_rule = rule;
-               last_rule->next != NULL;
-               last_rule = last_rule->next);
+          for (last_rule = rule; last_rule->next; last_rule = last_rule->next)
+           ;
           if (now - received_time > last_rule->timeout)
             {
             DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n");
@@ -799,9 +779,12 @@ for (i = 0; i < 3; i++)
         case set the next retry time to now, so that one delivery attempt
         happens for subsequent messages. */
 
-        if (rule == NULL) next_try = now; else
+        if (!rule)
+         next_try = now;
+       else
           {
-          if (rule->rule == 'F') next_try = now + rule->p1;
+          if (rule->rule == 'F')
+           next_try = now + rule->p1;
           else  /* rule = 'G' or 'H' */
             {
             int last_predicted_gap =
@@ -811,9 +794,7 @@ for (i = 0; i < 3; i++)
               last_predicted_gap : last_actual_gap;
             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;
@@ -874,7 +855,6 @@ for (i = 0; i < 3; i++)
       time was not reached (or because of hosts_max_try). */
 
       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");
@@ -885,7 +865,6 @@ for (i = 0; i < 3; i++)
           DEBUG(D_retry)
             debug_printf("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
@@ -901,7 +880,7 @@ for (i = 0; i < 3; i++)
 
     if (i == 2)   /* Handling defers */
       {
-      if (endaddr->retries != NULL && timed_out)
+      if (endaddr->retries && timed_out)
         {
         if (last_first == endaddr) paddr = saved_paddr;
         addr = *paddr;
@@ -949,7 +928,7 @@ for (i = 0; i < 3; i++)
 
 /* Close and unlock the database */
 
-if (dbm_file != NULL) dbfn_close(dbm_file);
+if (dbm_file) dbfn_close(dbm_file);
 
 DEBUG(D_retry) debug_printf("end of retry processing\n");
 }