Change HAVE_LOGIN_CAP to HAVE_SETCLASSRESOURCES because the former isn't
[exim.git] / src / src / transports / pipe.c
index f2fe47112d34d8008956b74d3263d65cff7e4e26..7fbfc86cc95e4d4ff341bca12402324466a95236 100644 (file)
@@ -1,16 +1,20 @@
-/* $Cambridge: exim/src/src/transports/pipe.c,v 1.1 2004/10/07 13:10:02 ph10 Exp $ */
+/* $Cambridge: exim/src/src/transports/pipe.c,v 1.11 2006/03/16 12:25:24 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
 #include "../exim.h"
 #include "pipe.h"
 
+#ifdef HAVE_SETCLASSRESOURCES
+#include <login_cap.h>
+#endif
+
 
 
 /* Options specific to the pipe transport. They must be in alphabetic
@@ -65,10 +69,16 @@ optionlist pipe_transport_options[] = {
       (void *)offsetof(pipe_transport_options_block, temp_errors) },
   { "timeout",           opt_time,
       (void *)offsetof(pipe_transport_options_block, timeout) },
+  { "timeout_defer",     opt_bool,
+      (void *)offsetof(pipe_transport_options_block, timeout_defer) },
   { "umask",             opt_octint,
       (void *)offsetof(pipe_transport_options_block, umask) },
   { "use_bsmtp",         opt_bool,
       (void *)offsetof(pipe_transport_options_block, use_bsmtp) },
+  #ifdef HAVE_SETCLASSRESOURCES
+  { "use_classresources", opt_bool,
+      (void *)offsetof(pipe_transport_options_block, use_classresources) },
+  #endif
   { "use_crlf",          opt_bool,
       (void *)offsetof(pipe_transport_options_block, use_crlf) },
   { "use_shell",         opt_bool,
@@ -87,7 +97,7 @@ pipe_transport_options_block pipe_transport_option_defaults = {
   NULL,           /* cmd */
   NULL,           /* allow_commands */
   NULL,           /* environment */
-  US"/usr/bin",   /* path */
+  US"/bin:/usr/bin",  /* path */
   NULL,           /* message_prefix (reset in init if not bsmtp) */
   NULL,           /* message_suffix (ditto) */
   US mac_expanded_string(EX_TEMPFAIL) ":"    /* temp_errors */
@@ -101,13 +111,70 @@ pipe_transport_options_block pipe_transport_option_defaults = {
   FALSE,          /* freeze_exec_fail */
   FALSE,          /* ignore_status */
   FALSE,          /* restrict_to_path */
+  FALSE,          /* timeout_defer */
   FALSE,          /* use_shell */
   FALSE,          /* use_bsmtp */
+  FALSE,          /* use_classresources */
   FALSE           /* use_crlf */
 };
 
 
 
+/*************************************************
+*              Setup entry point                 *
+*************************************************/
+
+/* Called for each delivery in the privileged state, just before the uid/gid
+are changed and the main entry point is called. In a system that supports the
+login_cap facilities, this function is used to set the class resource limits
+for the user.
+
+Arguments:
+  tblock     points to the transport instance
+  addrlist   addresses about to be delivered (not used)
+  dummy      not used (doesn't pass back data)
+  uid        the uid that will be set (not used)
+  gid        the gid that will be set (not used)
+  errmsg     where to put an error message
+
+Returns:     OK, FAIL, or DEFER
+*/
+
+static int
+pipe_transport_setup(transport_instance *tblock, address_item *addrlist,
+  transport_feedback *dummy, uid_t uid, gid_t gid, uschar **errmsg)
+{
+pipe_transport_options_block *ob =
+  (pipe_transport_options_block *)(tblock->options_block);
+
+addrlist = addrlist;  /* Keep compiler happy */
+dummy = dummy;
+uid = uid;
+gid = gid;
+errmsg = errmsg;
+ob = ob;
+
+#ifdef HAVE_SETCLASSRESOURCES
+if (ob->use_classresources)
+  {
+  struct passwd *pw = getpwuid(uid);
+  if (pw != NULL)
+    {
+    login_cap_t *lc = login_getpwclass(pw);
+    if (lc != NULL)
+      {
+      setclassresources(lc);
+      login_close(lc);
+      }
+    }
+  }
+#endif
+
+return OK;
+}
+
+
+
 /*************************************************
 *          Initialization entry point            *
 *************************************************/
@@ -122,6 +189,10 @@ pipe_transport_init(transport_instance *tblock)
 pipe_transport_options_block *ob =
   (pipe_transport_options_block *)(tblock->options_block);
 
+/* Set up the setup entry point, to be called in the privileged state */
+
+tblock->setup = pipe_transport_setup;
+
 /* If pipe_as_creator is set, then uid/gid should not be set. */
 
 if (tblock->deliver_as_creator && (tblock->uid_set || tblock->gid_set ||
@@ -637,8 +708,8 @@ if ((outpid = fork()) < 0)
   addr->message = string_sprintf(
     "Failed to create process for handling output in %s transport",
       tblock->name);
-  close(fd_in);
-  close(fd_out);
+  (void)close(fd_in);
+  (void)close(fd_out);
   return FALSE;
   }
 
@@ -651,7 +722,7 @@ the subprocess to fail. */
 if (outpid == 0)
   {
   int count = 0;
-  close(fd_in);
+  (void)close(fd_in);
   set_process_info("reading output from |%s", cmd);
   while ((rc = read(fd_out, big_buffer, big_buffer_size)) > 0)
     {
@@ -668,11 +739,11 @@ if (outpid == 0)
       break;
       }
     }
-  close(fd_out);
+  (void)close(fd_out);
   _exit(0);
   }
 
-close(fd_out);  /* Not used in this process */
+(void)close(fd_out);  /* Not used in this process */
 
 
 /* Carrying on now with the main parent process. Attempt to write the message
@@ -786,17 +857,20 @@ the child process to 1 second. If the process at the far end of the pipe died
 without reading all of it, we expect an EPIPE error, which should be ignored.
 We used also to ignore WRITEINCOMPLETE but the writing function is now cleverer
 at handling OS where the death of a pipe doesn't give EPIPE immediately. See
-comments therein. This change made 04-Sep-98. Clean up this code in a year or
-so. */
+comments therein. */
 
 if (!written_ok)
   {
   if (errno == ETIMEDOUT)
+    {
+    addr->message = string_sprintf("%stimeout while writing to pipe",
+      transport_filter_timed_out? "transport filter " : "");
+    addr->transport_return = ob->timeout_defer? DEFER : FAIL;
     timeout = 1;
-  else if (errno == EPIPE /* || errno == ERRNO_WRITEINCOMPLETE */ )
+    }
+  else if (errno == EPIPE)
     {
-    debug_printf("transport error %s ignored\n",
-      (errno == EPIPE)? "EPIPE" : "WRITEINCOMPLETE");
+    debug_printf("transport error EPIPE ignored\n");
     }
   else
     {
@@ -807,7 +881,9 @@ if (!written_ok)
         string_sprintf("Failed to expand headers_add or headers_remove: %s",
           expand_string_message);
     else if (errno == ERRNO_FILTER_FAIL)
-      addr->message = string_sprintf("Filter process failure");
+      addr->message = string_sprintf("Transport filter process failed (%d)%s",
+      addr->more_errno,
+      (addr->more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
     else if (errno == ERRNO_WRITEINCOMPLETE)
       addr->message = string_sprintf("Failed repeatedly to write data");
     else
@@ -822,6 +898,9 @@ above timed out. */
 
 if ((rc = child_close(pid, timeout)) != 0)
   {
+  uschar *tmsg = (addr->message == NULL)? US"" :
+    string_sprintf(" (preceded by %s)", addr->message);
+
   /* The process did not complete in time; kill its process group and fail
   the delivery. It appears to be necessary to kill the output process too, as
   otherwise it hangs on for some time if the actual pipe process is sleeping.
@@ -832,8 +911,8 @@ if ((rc = child_close(pid, timeout)) != 0)
     {
     killpg(pid, SIGKILL);
     kill(outpid, SIGKILL);
-    addr->transport_return = FAIL;
-    addr->message = string_sprintf("pipe delivery process timed out");
+    addr->transport_return = ob->timeout_defer? DEFER : FAIL;
+    addr->message = string_sprintf("pipe delivery process timed out%s", tmsg);
     }
 
   /* Wait() failed. */
@@ -842,7 +921,7 @@ if ((rc = child_close(pid, timeout)) != 0)
     {
     addr->transport_return = PANIC;
     addr->message = string_sprintf("Wait() failed for child process of %s "
-      "transport: %s", tblock->name, strerror(errno));
+      "transport: %s%s", tblock->name, strerror(errno), tmsg);
     }
 
   /* Either the process completed, but yielded a non-zero (necessarily
@@ -856,8 +935,8 @@ if ((rc = child_close(pid, timeout)) != 0)
       {
       addr->transport_return = FAIL;
       addr->message = string_sprintf("Child process of %s transport (running "
-        "command \"%s\") was terminated by signal %d (%s)", tblock->name, cmd,
-        -rc, os_strsignal(-rc));
+        "command \"%s\") was terminated by signal %d (%s)%s", tblock->name, cmd,
+        -rc, os_strsignal(-rc), tmsg);
       }
     }
 
@@ -909,8 +988,8 @@ if ((rc = child_close(pid, timeout)) != 0)
       {
       addr->transport_return = DEFER;
       addr->special_action = SPECIAL_FREEZE;
-      addr->message = string_sprintf("pipe process failed to exec \"%s\"",
-        cmd);
+      addr->message = string_sprintf("pipe process failed to exec \"%s\"%s",
+        cmd, tmsg);
       }
 
     /* Otherwise take action only if not ignoring status */
@@ -984,6 +1063,13 @@ if ((rc = child_close(pid, timeout)) != 0)
         if (quote)
           addr->message = string_cat(addr->message, &size, &ptr, US"\"", 1);
         }
+
+      /* Add previous filter timeout message, if present. */
+
+      if (*tmsg != 0)
+        addr->message = string_cat(addr->message, &size, &ptr, tmsg,
+          Ustrlen(tmsg));
+
       addr->message[ptr] = 0;  /* Ensure concatenated string terminated */
       }
     }