build: use pkg-config for i18n
[exim.git] / src / src / transports / pipe.c
index ca22f2659f22d65aaef6aef899dec28a8c1e9bf8..b765eeae5cdf1bc82ba3e0c19d5f7c4a24699752 100644 (file)
@@ -2,12 +2,15 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 
 #include "../exim.h"
+
+#ifdef TRANSPORT_PIPE  /* Remainder of file */
 #include "pipe.h"
 
 #ifdef HAVE_SETCLASSRESOURCES
@@ -79,7 +82,7 @@ int pipe_transport_options_count =
 
 /* Dummy values */
 pipe_transport_options_block pipe_transport_option_defaults = {0};
-void pipe_transport_init(transport_instance *tblock) {}
+void pipe_transport_init(driver_instance *tblock) {}
 BOOL pipe_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
 
 #else   /*!MACRO_PREDEF*/
@@ -88,31 +91,13 @@ BOOL pipe_transport_entry(transport_instance *tblock, address_item *addr) {retur
 /* Default private options block for the pipe transport. */
 
 pipe_transport_options_block pipe_transport_option_defaults = {
-  NULL,           /* cmd */
-  NULL,           /* allow_commands */
-  NULL,           /* environment */
-  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 */
-     mac_expanded_string(EX_CANTCREAT),
-  NULL,           /* check_string */
-  NULL,           /* escape_string */
-  022,            /* umask */
-  20480,          /* max_output */
-  60*60,          /* timeout */
-  0,              /* options */
-  FALSE,          /* force_command */
-  FALSE,          /* freeze_exec_fail */
-  FALSE,          /* freeze_signal */
-  FALSE,          /* ignore_status */
-  FALSE,          /* permit_coredump */
-  FALSE,          /* restrict_to_path */
-  FALSE,          /* timeout_defer */
-  FALSE,          /* use_shell */
-  FALSE,          /* use_bsmtp */
-  FALSE,          /* use_classresources */
-  FALSE           /* use_crlf */
+  .path =      US"/bin:/usr/bin",
+  .temp_errors = US mac_expanded_string(EX_TEMPFAIL) ":"
+                  mac_expanded_string(EX_CANTCREAT),
+  .umask =     022,
+  .max_output = 20480,
+  .timeout =   60*60,
+  /* all others null/zero/false */
 };
 
 
@@ -141,15 +126,7 @@ 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;
+pipe_transport_options_block * ob = tblock->drinst.options_block;
 
 #ifdef HAVE_SETCLASSRESOURCES
 if (ob->use_classresources)
@@ -199,10 +176,11 @@ enable consistency checks to be done, or anything else that needs
 to be set up. */
 
 void
-pipe_transport_init(transport_instance *tblock)
+pipe_transport_init(driver_instance * t)
 {
-pipe_transport_options_block *ob =
-  (pipe_transport_options_block *)(tblock->options_block);
+transport_instance * tblock = (transport_instance *)t;
+const uschar * trname = t->name;
+pipe_transport_options_block * ob = t->options_block;
 
 /* Set up the setup entry point, to be called in the privileged state */
 
@@ -214,13 +192,13 @@ if (tblock->deliver_as_creator && (tblock->uid_set || tblock->gid_set ||
   tblock->expand_uid != NULL || tblock->expand_gid != NULL))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "both pipe_as_creator and an explicit uid/gid are set for the %s "
-        "transport", tblock->name);
+        "transport", trname);
 
 /* If a fixed uid field is set, then a gid field must also be set. */
 
 if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
-    "user set without group for the %s transport", tblock->name);
+    "user set without group for the %s transport", trname);
 
 /* Temp_errors must consist only of digits and colons, but there can be
 spaces round the colons, so allow them too. */
@@ -231,7 +209,7 @@ if (ob->temp_errors != NULL && Ustrcmp(ob->temp_errors, "*") != 0)
   if (ob->temp_errors[p] != 0)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "temp_errors must be a list of numbers or an asterisk for the %s "
-      "transport", tblock->name);
+      "transport", trname);
   }
 
 /* Only one of return_output/return_fail_output or log_output/log_fail_output
@@ -240,12 +218,12 @@ should be set. */
 if (tblock->return_output && tblock->return_fail_output)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "both return_output and return_fail_output set for %s transport",
-    tblock->name);
+    trname);
 
 if (tblock->log_output && tblock->log_fail_output)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "both log_output and log_fail_output set for the %s transport",
-    tblock->name);
+    trname);
 
 /* If batch SMTP is set, force the check and escape strings, and arrange that
 headers are also escaped. */
@@ -272,25 +250,25 @@ else
 if (ob->restrict_to_path && ob->use_shell)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "both restrict_to_path and use_shell set for %s transport",
-    tblock->name);
+    trname);
 
 /* The allow_commands and use_shell options are incompatible */
 
 if (ob->allow_commands && ob->use_shell)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
     "both allow_commands and use_shell set for %s transport",
-    tblock->name);
+    trname);
 
 /* Set up the bitwise options for transport_write_message from the various
 driver options. Only one of body_only and headers_only can be set. */
 
 ob->options |=
-  (tblock->body_only? topt_no_headers : 0) |
-  (tblock->headers_only? topt_no_body : 0) |
-  (tblock->return_path_add? topt_add_return_path : 0) |
-  (tblock->delivery_date_add? topt_add_delivery_date : 0) |
-  (tblock->envelope_to_add? topt_add_envelope_to : 0) |
-  (ob->use_crlf? topt_use_crlf : 0);
+    (tblock->body_only ? topt_no_headers : 0)
+  | (tblock->headers_only ? topt_no_body : 0)
+  | (tblock->return_path_add ? topt_add_return_path : 0)
+  | (tblock->delivery_date_add ? topt_add_delivery_date : 0)
+  | (tblock->envelope_to_add ? topt_add_envelope_to : 0)
+  | (ob->use_crlf ? topt_use_crlf : 0);
 }
 
 
@@ -316,9 +294,9 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 static BOOL
-set_up_direct_command(const uschar ***argvptr, uschar *cmd,
-  BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname,
-  pipe_transport_options_block *ob)
+set_up_direct_command(const uschar *** argvptr, const uschar * cmd,
+  BOOL expand_arguments, int expand_fail, address_item * addr,
+  const uschar * tname, pipe_transport_options_block * ob)
 {
 BOOL permitted = FALSE;
 const uschar **argv;
@@ -328,8 +306,9 @@ call the common function for creating an argument list and expanding
 the items if necessary. If it fails, this function fails (error information
 is in the addresses). */
 
-if (!transport_set_up_command(argvptr, cmd, expand_arguments, expand_fail,
-      addr, string_sprintf("%.50s transport", tname), NULL))
+if (!transport_set_up_command(argvptr, cmd,
+      expand_arguments ? TSUC_EXPAND_ARGS : 0,
+      expand_fail, addr, string_sprintf("%.50s transport", tname), NULL))
   return FALSE;
 
 /* Point to the set-up arguments. */
@@ -338,6 +317,7 @@ argv = *argvptr;
 
 /* If allow_commands is set, see if the command is in the permitted list. */
 
+GET_OPTION("allow_commands");
 if (ob->allow_commands)
   {
   int sep = 0;
@@ -391,10 +371,11 @@ for it. */
 if (argv[0][0] != '/')
   {
   int sep = 0;
-  uschar *p;
-  const uschar *listptr = expand_string(ob->path);
+  uschar * p;
 
-  while ((p = string_nextinlist(&listptr, &sep, NULL, 0)))
+  GET_OPTION("path");
+  for (const uschar * listptr = expand_string(ob->path);
+      p = string_nextinlist(&listptr, &sep, NULL, 0); )
     {
     struct stat statbuf;
     sprintf(CS big_buffer, "%.256s/%.256s", p, argv[0]);
@@ -437,12 +418,13 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 static BOOL
-set_up_shell_command(const uschar ***argvptr, uschar *cmd,
-  BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname)
+set_up_shell_command(const uschar *** argvptr, const uschar * cmd,
+  BOOL expand_arguments, int expand_fail, address_item * addr,
+  const uschar * tname)
 {
 const uschar **argv;
 
-*argvptr = argv = store_get((4)*sizeof(uschar *), FALSE);
+*argvptr = argv = store_get((4)*sizeof(uschar *), GET_UNTAINTED);
 
 argv[0] = US"/bin/sh";
 argv[1] = US"-c";
@@ -476,16 +458,19 @@ if (expand_arguments)
 
     for (address_item * ad = addr; ad; ad = ad->next)
       {
+      DEBUG(D_transport) if (is_tainted(ad->address))
+       debug_printf("tainted element '%s' from $pipe_addresses\n", ad->address);
+
       /*XXX string_append_listele() ? */
       if (ad != addr) g = string_catn(g, US" ", 1);
       g = string_cat(g, ad->address);
       }
 
     g = string_cat(g, q);
-    argv[2] = (cmd = string_from_gstring(g)) ? expand_string(cmd) : NULL;
+    argv[2] = (cmd = string_from_gstring(g)) ? expand_cstring(cmd) : NULL;
     }
   else
-    argv[2] = expand_string(cmd);
+    argv[2] = expand_cstring(cmd);
 
   f.enable_dollar_recipients = FALSE;
 
@@ -528,21 +513,17 @@ pipe_transport_entry(
   transport_instance *tblock,      /* data for this instantiation */
   address_item *addr)              /* address(es) we are working on */
 {
+pipe_transport_options_block * ob = tblock->drinst.options_block;
+const uschar * trname = tblock->drinst.name;
 pid_t pid, outpid;
 int fd_in, fd_out, rc;
-int envcount = 0;
-int envsep = 0;
-int expand_fail;
-pipe_transport_options_block *ob =
-  (pipe_transport_options_block *)(tblock->options_block);
-int timeout = ob->timeout;
-BOOL written_ok = FALSE;
-BOOL expand_arguments;
-const uschar **argv;
-uschar *envp[50];
-const uschar *envlist = ob->environment;
-uschar *cmd, *ss;
-uschar *eol = ob->use_crlf ? US"\r\n" : US"\n";
+int envcount = 0, envsep = 0, expand_fail, timeout = ob->timeout;
+BOOL written_ok = FALSE, expand_arguments;
+const uschar ** argv;
+uschar * envp[50];
+const uschar * envlist = ob->environment, * cmd;
+uschar * ss;
+uschar * eol = ob->use_crlf ? US"\r\n" : US"\n";
 transport_ctx tctx = {
   .tblock = tblock,
   .addr = addr,
@@ -551,7 +532,7 @@ transport_ctx tctx = {
   ob->options | topt_not_socket /* set at initialization time */
 };
 
-DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
+DEBUG(D_transport) debug_printf("%s transport entered\n", trname);
 
 /* Set up for the good case */
 
@@ -569,6 +550,7 @@ if (testflag(addr, af_pfr) && addr->local_part[0] == '|')
     {
     /* Enables expansion of $address_pipe into separate arguments */
     setflag(addr, af_force_command);
+    GET_OPTION("commsnd");
     cmd = ob->cmd;
     expand_arguments = TRUE;
     expand_fail = PANIC;
@@ -576,33 +558,34 @@ if (testflag(addr, af_pfr) && addr->local_part[0] == '|')
   else
     {
     cmd = addr->local_part + 1;
-    while (isspace(*cmd)) cmd++;
+    Uskip_whitespace(&cmd);
     expand_arguments = testflag(addr, af_expand_pipe);
     expand_fail = FAIL;
     }
 else
   {
+  GET_OPTION("commsnd");
   cmd = ob->cmd;
   expand_arguments = TRUE;
   expand_fail = PANIC;
   }
 
 /* If no command has been supplied, we are in trouble.
- * We also check for an empty string since it may be
- * coming from addr->local_part[0] == '|'
- */
+We also check for an empty string since it may be
+coming from addr->local_part[0] == '|' */
 
 if (!cmd || !*cmd)
   {
   addr->transport_return = DEFER;
   addr->message = string_sprintf("no command specified for %s transport",
-    tblock->name);
+    trname);
   return FALSE;
   }
 if (is_tainted(cmd))
   {
+  DEBUG(D_transport) debug_printf("cmd '%s' is tainted\n", cmd);
   addr->message = string_sprintf("Tainted '%s' (command "
-    "for %s transport) not permitted", cmd, tblock->name);
+    "for %s transport) not permitted", cmd, trname);
   addr->transport_return = PANIC;
   return FALSE;
   }
@@ -631,10 +614,10 @@ there is an option to do that. */
 if (ob->use_shell)
   {
   if (!set_up_shell_command(&argv, cmd, expand_arguments, expand_fail, addr,
-    tblock->name)) return FALSE;
+    trname)) return FALSE;
   }
 else if (!set_up_direct_command(&argv, cmd, expand_arguments, expand_fail, addr,
-  tblock->name, ob)) return FALSE;
+  trname, ob)) return FALSE;
 
 expand_nmax = -1;           /* Reset */
 filter_thisaddress = NULL;
@@ -659,33 +642,35 @@ envp[envcount++] = string_sprintf("QUALIFY_DOMAIN=%s", qualify_domain_sender);
 envp[envcount++] = string_sprintf("SENDER=%s", sender_address);
 envp[envcount++] = US"SHELL=/bin/sh";
 
-if (addr->host_list != NULL)
+if (addr->host_list)
   envp[envcount++] = string_sprintf("HOST=%s", addr->host_list->name);
 
-if (f.timestamps_utc) envp[envcount++] = US"TZ=UTC";
-else if (timezone_string != NULL && timezone_string[0] != 0)
+if (f.timestamps_utc)
+  envp[envcount++] = US"TZ=UTC";
+else if (timezone_string && timezone_string[0])
   envp[envcount++] = string_sprintf("TZ=%s", timezone_string);
 
 /* Add any requested items */
 
+GET_OPTION("environment");
 if (envlist)
   if (!(envlist = expand_cstring(envlist)))
     {
     addr->transport_return = DEFER;
     addr->message = string_sprintf("failed to expand string \"%s\" "
-      "for %s transport: %s", ob->environment, tblock->name,
+      "for %s transport: %s", ob->environment, trname,
       expand_string_message);
     return FALSE;
     }
 
-while ((ss = string_nextinlist(&envlist, &envsep, big_buffer, big_buffer_size)))
+while ((ss = string_nextinlist(&envlist, &envsep, NULL, 0)))
    {
    if (envcount > nelem(envp) - 2)
      {
      addr->transport_return = DEFER;
      addr->basic_errno = E2BIG;
      addr->message = string_sprintf("too many environment settings for "
-       "%s transport", tblock->name);
+       "%s transport", trname);
      return FALSE;
      }
    envp[envcount++] = string_copy(ss);
@@ -699,7 +684,7 @@ if (f.dont_deliver)
   {
   DEBUG(D_transport)
     debug_printf("*** delivery by %s transport bypassed by -N option",
-      tblock->name);
+      trname);
   return FALSE;
   }
 
@@ -725,11 +710,12 @@ reading of the output pipe. */
 uid/gid and current directory. Request that the new process be a process group
 leader, so we can kill it and all its children on a timeout. */
 
-if ((pid = child_open(USS argv, envp, ob->umask, &fd_in, &fd_out, TRUE)) < 0)
+if ((pid = child_open(USS argv, envp, ob->umask, &fd_in, &fd_out, TRUE,
+                       US"pipe-tpt-cmd")) < 0)
   {
   addr->transport_return = DEFER;
   addr->message = string_sprintf(
-    "Failed to create child process for %s transport: %s", tblock->name,
+    "Failed to create child process for %s transport: %s", trname,
       strerror(errno));
   return FALSE;
   }
@@ -737,13 +723,13 @@ tctx.u.fd = fd_in;
 
 /* Now fork a process to handle the output that comes down the pipe. */
 
-if ((outpid = fork()) < 0)
+if ((outpid = exim_fork(US"pipe-tpt-output")) < 0)
   {
   addr->basic_errno = errno;
   addr->transport_return = DEFER;
   addr->message = string_sprintf(
     "Failed to create process for handling output in %s transport",
-      tblock->name);
+      trname);
   (void)close(fd_in);
   (void)close(fd_out);
   return FALSE;
@@ -813,14 +799,15 @@ transport_count = 0;
 
 /* First write any configured prefix information */
 
+GET_OPTION("message_prefix");
 if (ob->message_prefix)
   {
-  uschar *prefix = expand_string(ob->message_prefix);
+  uschar * prefix = expand_string(ob->message_prefix);
   if (!prefix)
     {
     addr->transport_return = f.search_find_defer? DEFER : PANIC;
     addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s "
-      "transport) failed: %s", ob->message_prefix, tblock->name,
+      "transport) failed: %s", ob->message_prefix, trname,
       expand_string_message);
     return FALSE;
     }
@@ -855,14 +842,15 @@ if (!transport_write_message(&tctx, 0))
 
 /* Now any configured suffix */
 
+GET_OPTION("message_suffix");
 if (ob->message_suffix)
   {
-  uschar *suffix = expand_string(ob->message_suffix);
+  uschar * suffix = expand_string(ob->message_suffix);
   if (!suffix)
     {
     addr->transport_return = f.search_find_defer? DEFER : PANIC;
     addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s "
-      "transport) failed: %s", ob->message_suffix, tblock->name,
+      "transport) failed: %s", ob->message_suffix, trname,
       expand_string_message);
     return FALSE;
     }
@@ -956,7 +944,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%s", tblock->name, strerror(errno), tmsg);
+      "transport: %s%s", trname, strerror(errno), tmsg);
     }
 
   /* Since the transport_filter timed out we assume it has sent the child process
@@ -984,14 +972,14 @@ if ((rc = child_close(pid, timeout)) != 0)
       addr->transport_return = DEFER;
       addr->special_action = SPECIAL_FREEZE;
       addr->message = string_sprintf("Child process of %s transport (running "
-        "command \"%s\") was terminated by signal %d (%s)%s", tblock->name, cmd,
+        "command \"%s\") was terminated by signal %d (%s)%s", trname, cmd,
         -rc, os_strsignal(-rc), tmsg);
       }
     else if (!ob->ignore_status)
       {
       addr->transport_return = FAIL;
       addr->message = string_sprintf("Child process of %s transport (running "
-        "command \"%s\") was terminated by signal %d (%s)%s", tblock->name, cmd,
+        "command \"%s\") was terminated by signal %d (%s)%s", trname, cmd,
         -rc, os_strsignal(-rc), tmsg);
       }
     }
@@ -1077,7 +1065,7 @@ if ((rc = child_close(pid, timeout)) != 0)
       doesn't have to be brilliantly efficient - it is an error situation. */
 
       addr->message = string_sprintf("Child process of %s transport returned "
-        "%d", tblock->name, rc);
+        "%d", trname, rc);
       g = string_cat(NULL, addr->message);
 
       /* If the return code is > 128, it often means that a shell command
@@ -1127,7 +1115,7 @@ are complete before we pass this point. */
 
 while (wait(&rc) >= 0);
 
-DEBUG(D_transport) debug_printf("%s transport yielded %d\n", tblock->name,
+DEBUG(D_transport) debug_printf("%s transport yielded %d\n", trname,
   addr->transport_return);
 
 /* If there has been a problem, the message in addr->message contains details
@@ -1140,5 +1128,31 @@ if (addr->transport_return != OK)
 return FALSE;
 }
 
+
+
+
+# ifdef DYNLOOKUP
+#  define pipe_transport_info _transport_info
+# endif
+
+transport_info pipe_transport_info = {
+.drinfo = {
+  .driver_name =       US"pipe",
+  .options =           pipe_transport_options,
+  .options_count =     &pipe_transport_options_count,
+  .options_block =     &pipe_transport_option_defaults,
+  .options_len =       sizeof(pipe_transport_options_block),
+  .init =              pipe_transport_init,
+# ifdef DYNLOOKUP
+  .dyn_magic =         TRANSPORT_MAGIC,
+# endif
+  },
+.code =                pipe_transport_entry,
+.tidyup =      NULL,
+.closedown =   NULL,
+.local =       TRUE
+};
+
 #endif /*!MACRO_PREDEF*/
+#endif /*TRASPORT_PIPE*/
 /* End of transport/pipe.c */