Call initgroups() when dropping privilege, in order that Exim runs with
[exim.git] / src / src / deliver.c
index 9351d45d7a27bc247edb4cdd1d1935ea911c99dd..a80d97842f370e89599d5ac1e42be95b191b41b5 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/deliver.c,v 1.26 2006/02/07 11:19:00 ph10 Exp $ */
+/* $Cambridge: exim/src/src/deliver.c,v 1.31 2006/04/20 14:11:29 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -228,11 +228,18 @@ if (addr->next == NULL)
   }
 
 /* For multiple addresses, don't set local part, and leave the domain and
-self_hostname set only if it is the same for all of them. */
+self_hostname set only if it is the same for all of them. It is possible to
+have multiple pipe and file addresses, but only when all addresses have routed
+to the same pipe or file. */
 
 else
   {
   address_item *addr2;
+  if (testflag(addr, af_pfr))
+    {
+    if (testflag(addr, af_file)) address_file = addr->local_part;
+      else if (addr->local_part[0] == '|') address_pipe = addr->local_part;
+    }
   for (addr2 = addr->next; addr2 != NULL; addr2 = addr2->next)
     {
     if (deliver_domain != NULL &&
@@ -1490,6 +1497,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             *
@@ -1705,7 +1750,7 @@ if ((pid = fork()) == 0)
 
   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:
@@ -1991,12 +2036,13 @@ if (addr->special_action == SPECIAL_WARN &&
     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, "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. */
@@ -2080,15 +2126,17 @@ while (addr_local != NULL)
 
   disable_logging = tp->disable_logging;
 
-  /* Check for batched addresses and possible amalgamation. File deliveries can
-  never be batched. Skip all the work if either batch_max <= 1 or there aren't
-  any other addresses for local delivery. */
+  /* Check for batched addresses and possible amalgamation. Skip all the work
+  if either batch_max <= 1 or there aren't any other addresses for local
+  delivery. */
 
-  if (!testflag(addr, af_file) && tp->batch_max > 1 && addr_local != NULL)
+  if (tp->batch_max > 1 && addr_local != NULL)
     {
     int batch_count = 1;
     BOOL uses_dom = readconf_depends((driver_instance *)tp, US"domain");
-    BOOL uses_lp = readconf_depends((driver_instance *)tp, US"local_part");
+    BOOL uses_lp = (testflag(addr, af_pfr) &&
+      (testflag(addr, af_file) || addr->local_part[0] == '|')) ||
+      readconf_depends((driver_instance *)tp, US"local_part");
     uschar *batch_id = NULL;
     address_item **anchor = &addr_local;
     address_item *last = addr;
@@ -2117,6 +2165,7 @@ while (addr_local != NULL)
       same transport
       not previously delivered (see comment about 50 lines above)
       same local part if the transport's configuration contains $local_part
+        or if this is a file or pipe delivery from a redirection
       same domain if the transport's configuration contains $domain
       same errors address
       same additional headers
@@ -2130,6 +2179,7 @@ while (addr_local != NULL)
       BOOL ok =
         tp == next->transport &&
         !previously_transported(next, TRUE) &&
+        (addr->flags & (af_pfr|af_file)) == (next->flags & (af_pfr|af_file)) &&
         (!uses_lp  || Ustrcmp(next->local_part, addr->local_part) == 0) &&
         (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0) &&
         same_strings(next->p.errors_address, addr->p.errors_address) &&
@@ -2250,10 +2300,12 @@ while (addr_local != NULL)
 
         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)
@@ -2282,9 +2334,18 @@ while (addr_local != NULL)
               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;
               }
-            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)
               {
@@ -3567,12 +3628,25 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     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
-  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)
-    (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
@@ -3648,16 +3722,6 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
   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
@@ -4246,15 +4310,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
-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
-  s            some leading text
+  t            some leading text
 
 Returns:       nothing
 */
@@ -4263,14 +4327,11 @@ static void
 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);