Pull Andreas Metzler's fix for gnutls_certificate_verify_peers (bug 1095)
[exim.git] / src / src / retry.c
index 9c90f6699affcad795beafeaf21d87dce35da546..e877bf7d0157d7d3eba76d3c66c16a11ad89744b 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/retry.c,v 1.2 2005/01/04 10:00:42 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with retrying unsuccessful deliveries. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with retrying unsuccessful deliveries. */
@@ -46,8 +44,16 @@ if (retry != NULL && retry->rules != NULL)
   for (last_rule = retry->rules;
        last_rule->next != NULL;
        last_rule = last_rule->next);
   for (last_rule = retry->rules;
        last_rule->next != NULL;
        last_rule = last_rule->next);
+  DEBUG(D_transport|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);
   }
   address_timeout = (now - received_time > last_rule->timeout);
   }
+else
+  {
+  DEBUG(D_transport|D_retry)
+    debug_printf("no retry rule found: assume timed out\n");
+  }
 
 return address_timeout;
 }
 
 return address_timeout;
 }
@@ -201,8 +207,14 @@ if (host_retry_record != NULL)
   if (now < host_retry_record->next_try && !deliver_force)
     {
     DEBUG(D_transport|D_retry)
   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("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,
 
     if (!host_retry_record->expired &&
         ultimate_address_timeout(host_key, domain,
@@ -238,8 +250,13 @@ if (message_retry_record != NULL)
   if (now < message_retry_record->next_try && !deliver_force)
     {
     DEBUG(D_transport|D_retry)
   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("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))
       {
       host->status = hstatus_unusable;
     if (!ultimate_address_timeout(host_key, domain, 0, 0, now))
       {
       host->status = hstatus_unusable;
@@ -340,23 +357,38 @@ retry_config *
 retry_find_config(uschar *key, uschar *alternate, int basic_errno,
   int more_errno)
 {
 retry_find_config(uschar *key, uschar *alternate, int basic_errno,
   int more_errno)
 {
-int replace;
+int replace = 0;
 uschar *use_key, *use_alternate;
 uschar *colon = Ustrchr(key, ':');
 retry_config *yield;
 
 uschar *use_key, *use_alternate;
 uschar *colon = Ustrchr(key, ':');
 retry_config *yield;
 
-/* If there's a colon in the key, temporarily replace it with
-a zero to terminate the string there. */
+/* If there's a colon in the key, there are two possibilities:
+
+(1) This is a key for a host, ip address, and possibly port, in the format
+
+      hostname:ip+port
+
+    In this case, we temporarily replace the colon with a zero, to terminate
+    the string after the host name.
+
+(2) This is a key for a pipe, file, or autoreply delivery, in the format
+
+      pipe-or-file-or-auto:x@y
+
+    where x@y is the original address that provoked the delivery. The pipe or
+    file or auto will start with | or / or >, whereas a host name will start
+    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 (colon != NULL)
   {
-  replace = ':';
-  }
-else
-  {
-  colon = key + Ustrlen(key);
-  replace = 0;
+  if (isalnum(*key))
+    replace = ':';
+  else
+    key = Ustrrchr(key, ':') + 1;   /* Take from the last colon */
   }
   }
+
+if (replace == 0) colon = key + Ustrlen(key);
 *colon = 0;
 
 /* Sort out the keys */
 *colon = 0;
 
 /* Sort out the keys */
@@ -390,20 +422,31 @@ for (yield = retries; yield != NULL; yield = yield->next)
         continue;
       }
 
         continue;
       }
 
-    /* Handle 4xx responses to RCPT. The code that was received is in the 2nd
-    least significant byte of more_errno (with 400 subtracted). The required
-    value is coded in the 2nd least significant byte of the yield->more_errno
-    field as follows:
+    /* The TLSREQUIRED error also covers TLSFAILURE. These are subtly different
+    errors, but not worth separating at this level. */
+
+    else if (yield->basic_errno == ERRNO_TLSREQUIRED)
+      {
+      if (basic_errno != ERRNO_TLSREQUIRED && basic_errno != ERRNO_TLSFAILURE)
+        continue;
+      }
+
+    /* Handle 4xx responses to MAIL, RCPT, or DATA. The code that was received
+    is in the 2nd least significant byte of more_errno (with 400 subtracted).
+    The required value is coded in the 2nd least significant byte of the
+    yield->more_errno field as follows:
 
       255     => any 4xx code
       >= 100  => the decade must match the value less 100
       < 100   => the exact value must match
     */
 
 
       255     => any 4xx code
       >= 100  => the decade must match the value less 100
       < 100   => the exact value must match
     */
 
-    else if (yield->basic_errno == ERRNO_RCPT4XX)
+    else if (yield->basic_errno == ERRNO_MAIL4XX ||
+             yield->basic_errno == ERRNO_RCPT4XX ||
+             yield->basic_errno == ERRNO_DATA4XX)
       {
       int wanted;
       {
       int wanted;
-      if (basic_errno != ERRNO_RCPT4XX) continue;
+      if (basic_errno != yield->basic_errno) continue;
       wanted = (yield->more_errno >> 8) & 255;
       if (wanted != 255)
         {
       wanted = (yield->more_errno >> 8) & 255;
       if (wanted != 255)
         {
@@ -619,10 +662,12 @@ for (i = 0; i < 3; i++)
         DEBUG(D_retry)
           {
           if ((rti->flags & rf_host) != 0)
         DEBUG(D_retry)
           {
           if ((rti->flags & rf_host) != 0)
-            debug_printf("retry for %s (%s) = %s\n", rti->key,
-              addr->domain, retry->pattern);
+            debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
+              addr->domain, retry->pattern, retry->basic_errno,
+              retry->more_errno);
           else
           else
-            debug_printf("retry for %s = %s\n", rti->key, retry->pattern);
+            debug_printf("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
           }
 
         /* Set up the message for the database retry record. Because DBM
@@ -658,6 +703,17 @@ for (i = 0; i < 3; i++)
         /* 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",
+          failing_interval, message_age);
+
+        /* For a non-host error, if the message has been on the queue longer
+        than the recorded time of failure, use the message's age instead. This
+        can happen when some messages can be delivered and others cannot; a
+        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)
+          failing_interval = message_age;
 
         /* Search for the current retry rule. The cutoff time of the
         last rule is handled differently to the others. The rule continues
 
         /* Search for the current retry rule. The cutoff time of the
         last rule is handled differently to the others. The rule continues
@@ -712,7 +768,14 @@ for (i = 0; i < 3; i++)
 
         This implements "timeout this rule if EITHER the host (or routing or
         directing) has been failing for more than the maximum time, OR if the
 
         This implements "timeout this rule if EITHER the host (or routing or
         directing) has been failing for more than the maximum time, OR if the
-        message has been on the queue for more than the maximum time." */
+        message has been on the queue for more than the maximum time."
+
+        February 2006: It is possible that this code is no longer needed
+        following the change to the retry calculation to use the message age if
+        it is larger than the time since first failure. It may be that the
+        expired flag is always set when the other conditions are met. However,
+        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 != NULL)
@@ -739,15 +802,25 @@ for (i = 0; i < 3; i++)
         if (rule == NULL) next_try = now; else
           {
           if (rule->rule == 'F') next_try = now + rule->p1;
         if (rule == NULL) next_try = now; else
           {
           if (rule->rule == 'F') next_try = now + rule->p1;
-          else  /* assume rule = 'G' */
+          else  /* rule = 'G' or 'H' */
             {
             int last_predicted_gap =
               retry_record->next_try - retry_record->last_try;
             int last_actual_gap = now - retry_record->last_try;
             int lastgap = (last_predicted_gap < last_actual_gap)?
               last_predicted_gap : last_actual_gap;
             {
             int last_predicted_gap =
               retry_record->next_try - retry_record->last_try;
             int last_actual_gap = now - retry_record->last_try;
             int lastgap = (last_predicted_gap < last_actual_gap)?
               last_predicted_gap : last_actual_gap;
-            next_try = now + ((lastgap < rule->p1)? rule->p1 :
-               (lastgap * rule->p2)/1000);
+            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;
+              if (next_gap > rule->p1)
+                next_try += random_number(next_gap - rule->p1)/2 +
+                  (next_gap - rule->p1)/2;
+              }
             }
           }
 
             }
           }
 
@@ -842,6 +915,9 @@ for (i = 0; i < 3; i++)
           setflag(addr, af_retry_timedout);
           addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
             string_sprintf("%s: retry timeout exceeded", addr->message);
           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);
           log_write(0, LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
             addr->address,
            (addr->parent == NULL)? US"" : US" <",
           log_write(0, LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
             addr->address,
            (addr->parent == NULL)? US"" : US" <",