SPDX: Mass-update to GPL-2.0-or-later
[exim.git] / src / src / transport.c
index 428d522adfa72172ca5f6e4a4177106ad855f307..d04ea516a5c4e3bc59366ed619393cf2160c0385 100644 (file)
@@ -2,9 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 - 2021 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* General functions concerned with transportation, and generic options for all
 transports. */
@@ -782,7 +783,7 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
   /* Header removed */
 
   else
-    DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text);
+    DEBUG(D_transport) debug_printf("removed header line:\n %s---\n", h->text);
   }
 
 /* Add on any address-specific headers. If there are multiple addresses,
@@ -798,8 +799,8 @@ Headers added to an address by a router are guaranteed to end with a newline.
 
 if (addr)
   {
-  header_line *hprev = addr->prop.extra_headers;
-  header_line *hnext, * h;
+  header_line * hprev = addr->prop.extra_headers, * hnext, * h;
+
   for (int i = 0; i < 2; i++)
     for (h = hprev, hprev = NULL; h; h = hnext)
       {
@@ -810,7 +811,7 @@ if (addr)
        {
        if (!sendfn(tctx, h->text, h->slen)) return FALSE;
        DEBUG(D_transport)
-         debug_printf("added header line(s):\n%s---\n", h->text);
+         debug_printf("added header line(s):\n %s---\n", h->text);
        }
       }
   }
@@ -838,7 +839,7 @@ if (tblock && (list = CUS tblock->add_headers))
          return FALSE;
        DEBUG(D_transport)
          {
-         debug_printf("added header line:\n%s", s);
+         debug_printf("added header line:\n %s", s);
          if (s[len-1] != '\n') debug_printf("\n");
          debug_printf("---\n");
          }
@@ -2053,6 +2054,31 @@ else
 
 
 
+/* Enforce all args untainted, for consistency with a router-sourced pipe
+command, where (because the whole line is passed as one to the tpt) a
+tainted arg taints the executable name.  It's unclear also that letting an
+attacker supply command arguments is wise. */
+
+static BOOL
+arg_is_tainted(const uschar * s, int argn, address_item * addr,
+  const uschar * etext, uschar ** errptr)
+{
+if (is_tainted(s))
+  {
+  uschar * msg = string_sprintf("Tainted arg %d for %s command: '%s'",
+                               argn, etext, s);
+  if (addr)
+    {
+    addr->transport_return = FAIL;
+    addr->message = msg;
+    }
+  else *errptr = msg;
+  return TRUE;
+  }
+return FALSE;
+}
+
+
 /*************************************************
 *          Set up direct (non-shell) command     *
 *************************************************/
@@ -2070,6 +2096,7 @@ Arguments:
   expand_failed      error value to set if expansion fails; not relevant if
                      addr == NULL
   addr               chain of addresses, or NULL
+  allow_tainted_args as it says; used for ${run}
   etext              text for use in error messages
   errptr             where to put error message if addr is NULL;
                      otherwise it is put in the first address
@@ -2080,8 +2107,8 @@ Returns:             TRUE if all went well; otherwise an error will be
 
 BOOL
 transport_set_up_command(const uschar *** argvptr, const uschar * cmd,
-  BOOL expand_arguments, int expand_failed, address_item *addr,
-  const uschar * etext, uschar ** errptr)
+  BOOL expand_arguments, int expand_failed, address_item * addr,
+  BOOL allow_tainted_args, const uschar * etext, uschar ** errptr)
 {
 const uschar ** argv, * s;
 int address_count = 0, argcount = 0, max_args;
@@ -2186,6 +2213,16 @@ if (expand_arguments)
 
       for (address_item * ad = addr; ad; ad = ad->next)
         {
+       /* $pipe_addresses is spefically not checked for taint, because there is
+       a testcase (321) depending on it.  It's unclear if the exact thing being
+       done really needs to be legitimate, though I suspect it reflects an
+       actual use-case that showed up a bug.
+       This is a hole in the taint-pretection, mitigated only in that
+       shell-syntax metachars cannot be injected via this route. */
+
+       DEBUG(D_transport) if (is_tainted(ad->address))
+         debug_printf("tainted element '%s' from $pipe_addresses\n", ad->address);
+
        argv[i++] = ad->address;
        argcount++;
        }
@@ -2292,7 +2329,11 @@ if (expand_arguments)
       for (int address_pipe_i = 0;
            address_pipe_argv[address_pipe_i];
            address_pipe_i++, argcount++)
-        argv[i++] = address_pipe_argv[address_pipe_i];
+       {
+        uschar * s = address_pipe_argv[address_pipe_i];
+       if (arg_is_tainted(s, i, addr, etext, errptr)) return FALSE;
+        argv[i++] = s;
+       }
 
       /* Subtract one since we replace $address_pipe */
       argcount--;
@@ -2304,9 +2345,10 @@ if (expand_arguments)
     else
       {
       const uschar *expanded_arg;
+      BOOL enable_dollar_recipients_g = f.enable_dollar_recipients;
       f.enable_dollar_recipients = allow_dollar_recipients;
       expanded_arg = expand_cstring(argv[i]);
-      f.enable_dollar_recipients = FALSE;
+      f.enable_dollar_recipients = enable_dollar_recipients_g;
 
       if (!expanded_arg)
         {
@@ -2321,6 +2363,17 @@ if (expand_arguments)
         else *errptr = msg;
         return FALSE;
         }
+
+      if ( f.running_in_test_harness && is_tainted(expanded_arg)
+        && Ustrcmp(etext, "queryprogram router") == 0)
+       {                       /* hack, would be good to not need it */
+       DEBUG(D_transport)
+         debug_printf("SPECIFIC TESTSUITE EXEMPTION: tainted arg '%s'\n",
+                     expanded_arg);
+       }
+      else if (  !allow_tainted_args
+             && arg_is_tainted(expanded_arg, i, addr, etext, errptr))
+       return FALSE;
       argv[i] = expanded_arg;
       }
     }
@@ -2329,7 +2382,10 @@ if (expand_arguments)
     {
     debug_printf("direct command after expansion:\n");
     for (int i = 0; argv[i]; i++)
-      debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
+      {
+      debug_printf("  argv[%d] = '%s'\n", i, string_printing(argv[i]));
+      debug_print_taint(argv[i]);
+      }
     }
   }