Fix bounces for non-SMTP reception errors to recognize
[exim.git] / src / src / deliver.c
index 83a3d1ad4e48ccdacd9ab96eeb02bd0dfc12f5d2..0cb0132c454cd376f668aadc031ce2b3ef10731a 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/deliver.c,v 1.23 2005/11/14 13:56:49 ph10 Exp $ */
+/* $Cambridge: exim/src/src/deliver.c,v 1.30 2006/03/01 16:07:16 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 - 2006 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
@@ -1304,6 +1304,14 @@ else if (tp->expand_gid != NULL)
     }
   }
 
     }
   }
 
+/* If the transport did not set a group, see if the router did. */
+
+if (!gid_set && testflag(addr, af_gid_set))
+  {
+  *gidp = addr->gid;
+  gid_set = TRUE;
+  }
+
 /* Pick up a uid from the transport if one is set. */
 
 if (tp->uid_set) *uidp = tp->uid;
 /* Pick up a uid from the transport if one is set. */
 
 if (tp->uid_set) *uidp = tp->uid;
@@ -1339,20 +1347,13 @@ else if (tp->deliver_as_creator)
     }
   }
 
     }
   }
 
-/* Otherwise see if the address specifies the uid and if so, take its
-initgroups flag. The gid from the address is taken only if the transport hasn't
-set it. In other words, a gid on the transport overrides the gid on the
-address. */
+/* Otherwise see if the address specifies the uid and if so, take it and its
+initgroups flag. */
 
 else if (testflag(addr, af_uid_set))
   {
   *uidp = addr->uid;
   *igfp = testflag(addr, af_initgroups);
 
 else if (testflag(addr, af_uid_set))
   {
   *uidp = addr->uid;
   *igfp = testflag(addr, af_initgroups);
-  if (!gid_set)
-    {
-    *gidp = addr->gid;
-    gid_set = TRUE;
-    }
   }
 
 /* Nothing has specified the uid - default to the Exim user, and group if the
   }
 
 /* Nothing has specified the uid - default to the Exim user, and group if the
@@ -1368,7 +1369,9 @@ else
     }
   }
 
     }
   }
 
-/* If no gid is set, it is a disaster. */
+/* If no gid is set, it is a disaster. We default to the Exim gid only if
+defaulting to the Exim uid. In other words, if the configuration has specified
+a uid, it must also provide a gid. */
 
 if (!gid_set)
   {
 
 if (!gid_set)
   {
@@ -1487,6 +1490,44 @@ return FALSE;
 
 
 
 
 
 
+/******************************************************
+*      Check for a given header in a header string    *
+******************************************************/
+
+/* This function is used when generating quota warnings. The configuration may
+specify any header lines it likes in quota_warn_message. If certain of them are
+missing, defaults are inserted, so we need to be able to test for the presence
+of a given header.
+
+Arguments:
+  hdr         the required header name
+  hstring     the header string
+
+Returns:      TRUE  the header is in the string
+              FALSE the header is not in the string
+*/
+
+static BOOL
+contains_header(uschar *hdr, uschar *hstring)
+{
+int len = Ustrlen(hdr);
+uschar *p = hstring;
+while (*p != 0)
+  {
+  if (strncmpic(p, hdr, len) == 0)
+    {
+    p += len;
+    while (*p == ' ' || *p == '\t') p++;
+    if (*p == ':') return TRUE;
+    }
+  while (*p != 0 && *p != '\n') p++;
+  if (*p == '\n') p++;
+  }
+return FALSE;
+}
+
+
+
 
 /*************************************************
 *           Perform a local delivery             *
 
 /*************************************************
 *           Perform a local delivery             *
@@ -1702,7 +1743,7 @@ if ((pid = fork()) == 0)
 
   if (addr->transport->setup != NULL)
     {
 
   if (addr->transport->setup != NULL)
     {
-    switch((addr->transport->setup)(addr->transport, addr, NULL,
+    switch((addr->transport->setup)(addr->transport, addr, NULL, uid, gid,
            &(addr->message)))
       {
       case DEFER:
            &(addr->message)))
       {
       case DEFER:
@@ -1988,12 +2029,13 @@ if (addr->special_action == SPECIAL_WARN &&
     if (pid > 0)
       {
       FILE *f = fdopen(fd, "wb");
     if (pid > 0)
       {
       FILE *f = fdopen(fd, "wb");
-
-      if (errors_reply_to != NULL)
+      if (errors_reply_to != NULL &&
+          !contains_header(US"Reply-To", warn_message))
         fprintf(f, "Reply-To: %s\n", errors_reply_to);
       fprintf(f, "Auto-Submitted: auto-replied\n");
         fprintf(f, "Reply-To: %s\n", errors_reply_to);
       fprintf(f, "Auto-Submitted: auto-replied\n");
-      fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
-        qualify_domain_sender);
+      if (!contains_header(US"From", warn_message))
+        fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
+          qualify_domain_sender);
       fprintf(f, "%s", CS warn_message);
 
       /* Close and wait for child process to complete, without a timeout. */
       fprintf(f, "%s", CS warn_message);
 
       /* Close and wait for child process to complete, without a timeout. */
@@ -2247,10 +2289,12 @@ while (addr_local != NULL)
 
         DEBUG(D_retry)
           {
 
         DEBUG(D_retry)
           {
-          debug_printf("retry record exists: age=%d (max=%d)\n",
-            (int)(now - retry_record->time_stamp), retry_data_expire);
-          debug_printf("  time to retry = %d expired = %d\n",
-            (int)(now - retry_record->next_try), retry_record->expired);
+          debug_printf("retry record exists: age=%s ",
+            readconf_printtime(now - retry_record->time_stamp));
+          debug_printf("(max %s)\n", readconf_printtime(retry_data_expire));
+          debug_printf("  time to retry = %s expired = %d\n",
+            readconf_printtime(retry_record->next_try - now),
+            retry_record->expired);
           }
 
         if (queue_running && !deliver_force)
           }
 
         if (queue_running && !deliver_force)
@@ -2279,9 +2323,18 @@ while (addr_local != 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_deliver|D_retry)
+                debug_printf("now=%d received_time=%d diff=%d timeout=%d\n",
+                  (int)now, received_time, (int)now - received_time,
+                  last_rule->timeout);
               if (now - received_time > last_rule->timeout) ok = TRUE;
               }
               if (now - received_time > last_rule->timeout) ok = TRUE;
               }
-            else ok = TRUE;    /* No rule => timed out */
+            else
+              {
+              DEBUG(D_deliver|D_retry)
+                debug_printf("no retry rule found: assume timed out\n");
+              ok = TRUE;    /* No rule => timed out */
+              }
 
             DEBUG(D_deliver|D_retry)
               {
 
             DEBUG(D_deliver|D_retry)
               {
@@ -3564,12 +3617,25 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     else return_path = new_return_path;
     }
 
     else return_path = new_return_path;
     }
 
+  /* Find the uid, gid, and use_initgroups setting for this transport. Failure
+  logs and sets up error messages, so we just post-process and continue with
+  the next address. */
+
+  if (!findugid(addr, tp, &uid, &gid, &use_initgroups))
+    {
+    remote_post_process(addr, LOG_MAIN|LOG_PANIC, NULL, fallback);
+    continue;
+    }
+
   /* If this transport has a setup function, call it now so that it gets
   run in this process and not in any subprocess. That way, the results of
   /* If this transport has a setup function, call it now so that it gets
   run in this process and not in any subprocess. That way, the results of
-  any setup that are retained by the transport can be reusable. */
+  any setup that are retained by the transport can be reusable. One of the
+  things the setup does is to set the fallback host lists in the addresses.
+  That is why it is called at this point, before the continue delivery
+  processing, because that might use the fallback hosts. */
 
   if (tp->setup != NULL)
 
   if (tp->setup != NULL)
-    (void)((tp->setup)(addr->transport, addr, NULL, NULL));
+    (void)((tp->setup)(addr->transport, addr, NULL, uid, gid, NULL));
 
   /* If this is a run to continue delivery down an already-established
   channel, check that this set of addresses matches the transport and
 
   /* If this is a run to continue delivery down an already-established
   channel, check that this set of addresses matches the transport and
@@ -3645,16 +3711,6 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   transport_filter_argv = NULL;
 
 
   transport_filter_argv = NULL;
 
-  /* Find the uid, gid, and use_initgroups setting for this transport. Failure
-  logs and sets up error messages, so we just post-process and continue with
-  the next address. */
-
-  if (!findugid(addr, tp, &uid, &gid, &use_initgroups))
-    {
-    remote_post_process(addr, LOG_MAIN|LOG_PANIC, NULL, fallback);
-    continue;
-    }
-
   /* Create the pipe for inter-process communication. If pipe creation
   fails, it is probably because the value of remote_max_parallel is so
   large that too many file descriptors for pipes have been created. Arrange
   /* Create the pipe for inter-process communication. If pipe creation
   fails, it is probably because the value of remote_max_parallel is so
   large that too many file descriptors for pipes have been created. Arrange
@@ -4243,15 +4299,15 @@ introducing newlines. All lines are indented by 4; the initial printing
 position must be set before calling.
 
 This function used always to print the error. Nowadays we want to restrict it
 position must be set before calling.
 
 This function used always to print the error. Nowadays we want to restrict it
-to cases such as SMTP errors from a remote host, and errors from :fail: and
-filter "fail". We no longer pass other information willy-nilly in bounce and
-warning messages. Text in user_message is always output; text in message only
-if the af_pass_message flag is set.
+to cases such as LMTP/SMTP errors from a remote host, and errors from :fail:
+and filter "fail". We no longer pass other information willy-nilly in bounce
+and warning messages. Text in user_message is always output; text in message
+only if the af_pass_message flag is set.
 
 Arguments:
   addr         the address
   f            the FILE to print on
 
 Arguments:
   addr         the address
   f            the FILE to print on
-  s            some leading text
+  t            some leading text
 
 Returns:       nothing
 */
 
 Returns:       nothing
 */
@@ -4260,14 +4316,11 @@ static void
 print_address_error(address_item *addr, FILE *f, uschar *t)
 {
 int count = Ustrlen(t);
 print_address_error(address_item *addr, FILE *f, uschar *t)
 {
 int count = Ustrlen(t);
-uschar *s = (addr->user_message != NULL)? addr->user_message : addr->message;
+uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;
 
 
-if (addr->user_message != NULL)
-  s = addr->user_message;
-else
+if (s == NULL)
   {
   {
-  if (!testflag(addr, af_pass_message) || addr->message == NULL) return;
-  s = addr->message;
+  if (addr->user_message != NULL) s = addr->user_message; else return;
   }
 
 fprintf(f, "\n    %s", t);
   }
 
 fprintf(f, "\n    %s", t);
@@ -5172,7 +5225,20 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
 
     if (testflag(addr, af_pfr))
       {
 
     if (testflag(addr, af_pfr))
       {
-      int offset = testflag(addr->parent, af_homonym)? 3:0;
+      /* If an autoreply in a filter could not generate a syntactically valid
+      address, give up forthwith. Set af_ignore_error so that we don't try to
+      generate a bounce. */
+
+      if (testflag(addr, af_bad_reply))
+        {
+        addr->basic_errno = ERRNO_BADADDRESS2;
+        addr->local_part = addr->address;
+        addr->message =
+          US"filter autoreply generated syntactically invalid recipient";
+        setflag(addr, af_ignore_error);
+        (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+        continue;   /* with the next new address */
+        }
 
       /* If two different users specify delivery to the same pipe or file or
       autoreply, there should be two different deliveries, so build a unique
 
       /* If two different users specify delivery to the same pipe or file or
       autoreply, there should be two different deliveries, so build a unique
@@ -5180,7 +5246,8 @@ while (addr_new != NULL)           /* Loop until all addresses dealt with */
       duplicate testing and recording delivery, and also for retrying. */
 
       addr->unique =
       duplicate testing and recording delivery, and also for retrying. */
 
       addr->unique =
-        string_sprintf("%s:%s", addr->address, addr->parent->unique + offset);
+        string_sprintf("%s:%s", addr->address, addr->parent->unique +
+          (testflag(addr->parent, af_homonym)? 3:0));
 
       addr->address_retry_key = addr->domain_retry_key =
         string_sprintf("T:%s", addr->unique);
 
       addr->address_retry_key = addr->domain_retry_key =
         string_sprintf("T:%s", addr->unique);