Taint: treat $message_body & $message_body_end as tainted
[users/heiko/exim.git] / src / src / retry.c
index 364591bd09214a617794d557fde67d7471cb0dca..30b308fe37f9dfe2f37b5bfaf03d0806c006c61f 100644 (file)
@@ -2,7 +2,8 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* 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. */
@@ -33,29 +34,28 @@ retry_ultimate_address_timeout(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",
-      received_time, (int)(now - received_time), last_rule->timeout);
-  address_timeout = (now - received_time > 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
   {
   }
 else
   {
@@ -159,8 +159,7 @@ deliveries (so as to do it all in one go). The tree records addresses that have
 become unusable during this delivery process (i.e. those that will get put into
 the retry database when it is updated). */
 
 become unusable during this delivery process (i.e. those that will get put into
 the retry database when it is updated). */
 
-node = tree_search(tree_unusable, host_key);
-if (node != NULL)
+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)?
   {
   DEBUG(D_transport|D_retry) debug_printf("found in tree of unusables\n");
   host->status = (node->data.val > 255)?
@@ -172,7 +171,7 @@ if (node != NULL)
 /* 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)) == NULL)
+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");
   {
   DEBUG(D_deliver|D_retry|D_hints_lookup)
     debug_printf("no retry data available\n");
@@ -184,7 +183,7 @@ dbfn_close(dbm_file);
 
 /* Ignore the data if it is too old - too long since it was written */
 
 
 /* Ignore the data if it is too old - too long since it was written */
 
-if (host_retry_record == NULL)
+if (!host_retry_record)
   {
   DEBUG(D_transport|D_retry) debug_printf("no host retry record\n");
   }
   {
   DEBUG(D_transport|D_retry) debug_printf("no host retry record\n");
   }
@@ -194,7 +193,7 @@ else if (now - host_retry_record->time_stamp > retry_data_expire)
   DEBUG(D_transport|D_retry) debug_printf("host retry record too old\n");
   }
 
   DEBUG(D_transport|D_retry) debug_printf("host retry record too old\n");
   }
 
-if (message_retry_record == NULL)
+if (!message_retry_record)
   {
   DEBUG(D_transport|D_retry) debug_printf("no message retry record\n");
   }
   {
   DEBUG(D_transport|D_retry) debug_printf("no message retry record\n");
   }
@@ -211,14 +210,14 @@ address. Allow the delivery if it has. Otherwise set the appropriate unusable
 flag and return FALSE. Otherwise arrange to return TRUE if this is an expired
 host. */
 
 flag and return FALSE. Otherwise arrange to return TRUE if this is an expired
 host. */
 
-if (host_retry_record != NULL)
+if (host_retry_record)
   {
   *retry_host_key = host_key;
 
   /* We have not reached the next try time. Check for the ultimate address
   timeout if the host has not expired. */
 
   {
   *retry_host_key = host_key;
 
   /* 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 (!host_retry_record->expired &&
         retry_ultimate_address_timeout(host_key, domain,
@@ -243,10 +242,10 @@ if (host_retry_record != NULL)
 for reaching its retry time (or forcing). If not, mark the host unusable,
 unless the ultimate address timeout has been reached. */
 
 for reaching its retry time (or forcing). If not, mark the host unusable,
 unless the ultimate address timeout has been reached. */
 
-if (message_retry_record != NULL)
+if (message_retry_record)
   {
   *retry_message_key = message_key;
   {
   *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))
     {
     if (!retry_ultimate_address_timeout(host_key, domain,
         message_retry_record, now))
@@ -293,8 +292,9 @@ Returns:  nothing
 void
 retry_add_item(address_item *addr, uschar *key, int flags)
 {
 void
 retry_add_item(address_item *addr, uschar *key, int flags)
 {
-retry_item *rti = store_get(sizeof(retry_item));
+retry_item *rti = store_get(sizeof(retry_item), FALSE);
 host_item * host = addr->host_used;
 host_item * host = addr->host_used;
+
 rti->next = addr->retries;
 addr->retries = rti;
 rti->key = key;
 rti->next = addr->retries;
 addr->retries = rti;
 rti->key = key;
@@ -378,7 +378,7 @@ if (alternate)    alternate = string_sprintf("*@%s", alternate);
 
 /* Scan the configured retry items. */
 
 
 /* Scan the configured retry items. */
 
-for (yield = retries; yield != NULL; yield = yield->next)
+for (yield = retries; yield; yield = yield->next)
   {
   const uschar *plist = yield->pattern;
   const uschar *slist = yield->senders;
   {
   const uschar *plist = yield->pattern;
   const uschar *slist = yield->senders;
@@ -472,19 +472,19 @@ for (yield = retries; yield != NULL; yield = yield->next)
   /* If the "senders" condition is set, check it. Note that sender_address may
   be null during -brt checking, in which case we do not use this rule. */
 
   /* If the "senders" condition is set, check it. Note that sender_address may
   be null during -brt checking, in which case we do not use this rule. */
 
-  if (slist != NULL && (sender_address == NULL ||
-      match_address_list(sender_address, TRUE, TRUE, &slist, NULL, -1, 0,
-        NULL) != OK))
+  if (  slist
+     && (  !sender_address
+               || match_address_list_basic(sender_address, &slist, 0) != OK
+     )  )
     continue;
 
   /* Check for a match between the address list item at the start of this retry
   rule and either the main or alternate keys. */
 
     continue;
 
   /* 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(key, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1,
-        NULL) == OK ||
-     (alternate != NULL &&
-      match_address_list(alternate, TRUE, TRUE, &plist, NULL, -1,
-        UCHAR_MAX+1, NULL) == OK))
+  if (  match_address_list_basic(key, &plist, UCHAR_MAX+1) == OK
+     || (  alternate
+       && match_address_list_basic(alternate, &plist, UCHAR_MAX+1) == OK
+     )  )
     break;
   }
 
     break;
   }
 
@@ -524,7 +524,6 @@ retry_update(address_item **addr_defer, address_item **addr_failed,
 open_db dbblock;
 open_db *dbm_file = NULL;
 time_t now = time(NULL);
 open_db dbblock;
 open_db *dbm_file = NULL;
 time_t now = time(NULL);
-int i;
 
 DEBUG(D_retry) debug_printf("Processing retry items\n");
 
 
 DEBUG(D_retry) debug_printf("Processing retry items\n");
 
@@ -532,7 +531,7 @@ 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. */
 
 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 *endaddr, *addr;
   address_item *last_first = NULL;
@@ -556,7 +555,6 @@ 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)
       {
@@ -568,7 +566,7 @@ for (i = 0; i < 3; i++)
 
       /* 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,7 +581,7 @@ 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);
+          dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE, TRUE);
 
         if (!dbm_file)
           {
 
         if (!dbm_file)
           {
@@ -639,7 +637,6 @@ for (i = 0; i < 3; i++)
           }
 
         DEBUG(D_retry)
           }
 
         DEBUG(D_retry)
-          {
           if (rti->flags & rf_host)
             debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
               addr->domain, retry->pattern, retry->basic_errno,
           if (rti->flags & rf_host)
             debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
               addr->domain, retry->pattern, retry->basic_errno,
@@ -647,7 +644,6 @@ for (i = 0; i < 3; i++)
           else
             debug_printf("retry for %s = %s %d %d\n", rti->key, retry->pattern,
               retry->basic_errno, retry->more_errno);
           else
             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
         records have a maximum data length, we enforce a limit. There isn't
 
         /* Set up the message for the database retry record. Because DBM
         records have a maximum data length, we enforce a limit. There isn't
@@ -664,14 +660,16 @@ for (i = 0; i < 3; i++)
         /* 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,
+                                  is_tainted(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;
@@ -679,7 +677,7 @@ 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 */
 
 
         /* Compute how long this destination has been failing */
 
@@ -756,7 +754,7 @@ 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. */
 
         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
+        if (  received_time.tv_sec <= retry_record->first_failed
           && addr == endaddr
           && !retry_record->expired
           && rule)
           && addr == endaddr
           && !retry_record->expired
           && rule)
@@ -764,7 +762,7 @@ for (i = 0; i < 3; i++)
           retry_rule *last_rule;
           for (last_rule = rule; last_rule->next; last_rule = last_rule->next)
            ;
           retry_rule *last_rule;
           for (last_rule = rule; last_rule->next; last_rule = last_rule->next)
            ;
-          if (now - received_time > last_rule->timeout)
+          if (now - received_time.tv_sec > last_rule->timeout)
             {
             DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n");
             timedout_count++;
             {
             DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n");
             timedout_count++;
@@ -810,15 +808,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, is_tainted(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. */
@@ -861,10 +861,8 @@ for (i = 0; i < 3; i++)
           timed_out = TRUE;
           }
         else
           timed_out = TRUE;
           }
         else
-          {
           DEBUG(D_retry)
             debug_printf("timed out but some hosts were skipped\n");
           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
       }     /* Loop for an address and its parents */
 
     /* If this is a deferred address, and retry processing was requested by
@@ -892,16 +890,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;
           }