* 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. */
/* 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,
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)
{
{
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);
}
}
}
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");
}
/* If requested, add a terminating "." line (SMTP output). */
-if (tctx->options & topt_end_dot && !write_chunk(tctx, US".\n", 2))
- return FALSE;
+if (tctx->options & topt_end_dot)
+ {
+ smtp_debug_cmd(US".", 0);
+ if (!write_chunk(tctx, US".\n", 2))
+ return FALSE;
+ }
/* Write out any remaining data in the buffer before returning. */
? !write_chunk(tctx, US".\n", 2)
: !write_chunk(tctx, US"\n.\n", 3)
) )
- yield = FALSE;
+ { smtp_debug_cmd(US".", 0); yield = FALSE; }
/* Write out any remaining data in the buffer. */
DEBUG(D_exec) debug_print_argv(argv);
exim_nullstd(); /* Ensure std{out,err} exist */
+/* argv[0] should be untainted, from child_exec_exim() */
execv(CS argv[0], (char *const *)argv);
DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
+/* 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 *
*************************************************/
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
*/
BOOL
-transport_set_up_command(const uschar ***argvptr, uschar *cmd,
- BOOL expand_arguments, int expand_failed, address_item *addr,
- uschar *etext, uschar **errptr)
+transport_set_up_command(const uschar *** argvptr, const uschar * cmd,
+ BOOL expand_arguments, int expand_failed, address_item * addr,
+ BOOL allow_tainted_args, const uschar * etext, uschar ** errptr)
{
-const uschar **argv;
-uschar *s, *ss;
-int address_count = 0;
-int argcount = 0;
-int max_args;
+const uschar ** argv, * s;
+int address_count = 0, argcount = 0, max_args;
/* Get store in which to build an argument list. Count the number of addresses
supplied, and allow for that many arguments, plus an additional 60, which
arguments are verbatim. Copy each argument into a new string. */
s = cmd;
-while (isspace(*s)) s++;
+Uskip_whitespace(&s);
-for (; *s != 0 && argcount < max_args; argcount++)
+for (; *s && argcount < max_args; argcount++)
{
if (*s == '\'')
{
- ss = s + 1;
- while (*ss != 0 && *ss != '\'') ss++;
- argv[argcount] = ss = store_get(ss - s++, cmd);
- while (*s != 0 && *s != '\'') *ss++ = *s++;
- if (*s != 0) s++;
- *ss++ = 0;
+ int n = Ustrcspn(++s, "'");
+ argv[argcount] = string_copyn(s, n);
+ if (*(s += n) == '\'') s++;
}
else
argv[argcount] = string_dequote(CUSS &s);
- while (isspace(*s)) s++;
+ Uskip_whitespace(&s);
}
-argv[argcount] = US 0;
+argv[argcount] = NULL;
/* If *s != 0 we have run out of argument slots. */
-if (*s != 0)
+if (*s)
{
uschar *msg = string_sprintf("Too many arguments in command \"%s\" in "
"%s", cmd, etext);
- if (addr != NULL)
+ if (addr)
{
addr->transport_return = FAIL;
addr->message = msg;
if (expand_arguments)
{
- BOOL allow_dollar_recipients = addr != NULL &&
- addr->parent != NULL &&
- Ustrcmp(addr->parent->address, "system-filter") == 0;
+ BOOL allow_dollar_recipients = addr && addr->parent
+ && Ustrcmp(addr->parent->address, "system-filter") == 0;
- for (int i = 0; argv[i] != US 0; i++)
+ for (int i = 0; argv[i]; i++)
{
+ DEBUG(D_expand) debug_printf_indent("arg %d\n", i);
/* Handle special fudge for passing an address list */
- if (addr != NULL &&
+ if (addr &&
(Ustrcmp(argv[i], "$pipe_addresses") == 0 ||
Ustrcmp(argv[i], "${pipe_addresses}") == 0))
{
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++;
}
/* Handle special case of $address_pipe when af_force_command is set */
- else if (addr != NULL && testflag(addr,af_force_command) &&
+ else if (addr && testflag(addr,af_force_command) &&
(Ustrcmp(argv[i], "$address_pipe") == 0 ||
Ustrcmp(argv[i], "${address_pipe}") == 0))
{
/* +1 because addr->local_part[0] == '|' since af_force_command is set */
s = expand_string(addr->local_part + 1);
- if (!s || *s == '\0')
+ if (!s || !*s)
{
addr->transport_return = FAIL;
addr->message = string_sprintf("Expansion of \"%s\" "
return FALSE;
}
- while (isspace(*s)) s++; /* strip leading space */
+ Uskip_whitespace(&s); /* strip leading space */
while (*s && address_pipe_argcount < address_pipe_max_args)
{
if (*s == '\'')
- {
- int n;
- for (ss = s + 1; *ss && *ss != '\''; ) ss++;
- n = ss - s++;
- address_pipe_argv[address_pipe_argcount++] = ss = store_get(n, s);
- while (*s && *s != '\'') *ss++ = *s++;
- if (*s) s++;
- *ss++ = 0;
- }
- else address_pipe_argv[address_pipe_argcount++] =
- string_copy(string_dequote(CUSS &s));
- while (isspace(*s)) s++; /* strip space after arg */
+ {
+ int n = Ustrcspn(++s, "'");
+ argv[argcount] = string_copyn(s, n);
+ if (*(s += n) == '\'') s++;
+ }
+ else
+ address_pipe_argv[address_pipe_argcount++] = string_dequote(CUSS &s);
+ Uskip_whitespace(&s); /* strip space after arg */
}
- address_pipe_argv[address_pipe_argcount] = US 0;
+ address_pipe_argv[address_pipe_argcount] = NULL;
/* If *s != 0 we have run out of argument slots. */
- if (*s != 0)
+ if (*s)
{
uschar *msg = string_sprintf("Too many arguments in $address_pipe "
"\"%s\" in %s", addr->local_part + 1, etext);
- if (addr != NULL)
+ if (addr)
{
addr->transport_return = FAIL;
addr->message = msg;
}
/* address_pipe_argcount - 1
- * because we are replacing $address_pipe in the argument list
- * with the first thing it expands to */
+ because we are replacing $address_pipe in the argument list
+ with the first thing it expands to */
+
if (argcount + address_pipe_argcount - 1 > max_args)
{
addr->transport_return = FAIL;
[argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0] */
for (int address_pipe_i = 0;
- address_pipe_argv[address_pipe_i] != US 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--;
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)
{
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;
}
}
DEBUG(D_transport)
{
debug_printf("direct command after expansion:\n");
- for (int i = 0; argv[i] != US 0; i++)
- debug_printf(" argv[%d] = %s\n", i, string_printing(argv[i]));
+ for (int i = 0; argv[i]; i++)
+ {
+ debug_printf(" argv[%d] = '%s'\n", i, string_printing(argv[i]));
+ debug_print_taint(argv[i]);
+ }
}
}