* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* This module contains code for extracting addresses from a forwarding list
static BOOL
match_tag(const uschar *s, const uschar *tag)
{
-for (; *tag != 0; s++, tag++)
- {
+for (; *tag; s++, tag++)
if (*tag == ' ')
{
while (*s == ' ' || *s == '\t') s++;
s--;
}
- else if (tolower(*s) != tolower(*tag)) break;
- }
+ else
+ if (tolower(*s) != tolower(*tag)) break;
+
return (*tag == 0);
}
int
rda_is_filter(const uschar *s)
{
-while (isspace(*s)) s++; /* Skips initial blank lines */
-if (match_tag(s, CUS"# exim filter")) return FILTER_EXIM;
- else if (match_tag(s, CUS"# sieve filter")) return FILTER_SIEVE;
- else return FILTER_FORWARD;
+Uskip_whitespace(&s); /* Skips initial blank lines */
+if (match_tag(s, CUS"# exim filter")) return FILTER_EXIM;
+else if (match_tag(s, CUS"# sieve filter")) return FILTER_SIEVE;
+else return FILTER_FORWARD;
}
rda_exists(uschar *filename, uschar **error)
{
int rc, saved_errno;
-uschar *slash;
struct stat statbuf;
+uschar * s;
if ((rc = Ustat(filename, &statbuf)) >= 0) return FILE_EXIST;
saved_errno = errno;
-Ustrncpy(big_buffer, filename, big_buffer_size - 3);
+s = string_copy(filename);
sigalrm_seen = FALSE;
if (saved_errno == ENOENT)
{
- slash = Ustrrchr(big_buffer, '/');
- Ustrcpy(slash+1, ".");
+ uschar * slash = Ustrrchr(s, '/');
+ Ustrcpy(slash+1, US".");
- alarm(30);
- rc = Ustat(big_buffer, &statbuf);
+ ALARM(30);
+ rc = Ustat(s, &statbuf);
if (rc != 0 && errno == EACCES && !sigalrm_seen)
{
*slash = 0;
- rc = Ustat(big_buffer, &statbuf);
+ rc = Ustat(s, &statbuf);
}
saved_errno = errno;
- alarm(0);
+ ALARM_CLR(0);
- DEBUG(D_route) debug_printf("stat(%s)=%d\n", big_buffer, rc);
+ DEBUG(D_route) debug_printf("stat(%s)=%d\n", s, rc);
}
if (sigalrm_seen || rc != 0)
{
- *error = string_sprintf("failed to stat %s (%s)", big_buffer,
- sigalrm_seen? "timeout" : strerror(saved_errno));
+ *error = string_sprintf("failed to stat %s (%s)", s,
+ sigalrm_seen? "timeout" : strerror(saved_errno));
return FILE_EXIST_UNCLEAR;
}
BOOL gid_ok = !rdata->check_group;
struct stat statbuf;
+/* Reading a file is a form of expansion; we wish to deny attackers the
+capability to specify the file name. */
+
+if (is_tainted(filename))
+ {
+ *error = string_sprintf("Tainted name '%s' for file read not permitted\n",
+ filename);
+ *yield = FF_ERROR;
+ return NULL;
+ }
+
/* Attempt to open the file. If it appears not to exist, check up on the
containing directory by statting it. If the directory does not exist, we treat
this situation as an error (which will cause delivery to defer); otherwise we
path is not a directory" errors), the right behaviour seems to be not to do the
directory test. */
-fwd = Ufopen(filename, "rb");
-if (fwd == NULL)
+if (!(fwd = Ufopen(filename, "rb"))) switch(errno)
{
- switch(errno)
- {
- case ENOENT: /* File does not exist */
+ case ENOENT: /* File does not exist */
DEBUG(D_route) debug_printf("%s does not exist\n%schecking parent directory\n",
- filename,
- ((options & RDO_ENOTDIR) != 0)? "ignore_enotdir set => skip " : "");
- *yield = (((options & RDO_ENOTDIR) != 0) ||
- rda_exists(filename, error) == FILE_NOT_EXIST)?
- FF_NONEXIST : FF_ERROR;
+ filename, options & RDO_ENOTDIR ? "ignore_enotdir set => skip " : "");
+ *yield =
+ options & RDO_ENOTDIR || rda_exists(filename, error) == FILE_NOT_EXIST
+ ? FF_NONEXIST : FF_ERROR;
return NULL;
- case ENOTDIR: /* Something on the path isn't a directory */
- if ((options & RDO_ENOTDIR) == 0) goto DEFAULT_ERROR;
+ case ENOTDIR: /* Something on the path isn't a directory */
+ if (!(options & RDO_ENOTDIR)) goto DEFAULT_ERROR;
DEBUG(D_route) debug_printf("non-directory on path %s: file assumed not to "
"exist\n", filename);
*yield = FF_NONEXIST;
return NULL;
- case EACCES: /* Permission denied */
- if ((options & RDO_EACCES) == 0) goto DEFAULT_ERROR;
+ case EACCES: /* Permission denied */
+ if (!(options & RDO_EACCES)) goto DEFAULT_ERROR;
DEBUG(D_route) debug_printf("permission denied for %s: file assumed not to "
"exist\n", filename);
*yield = FF_NONEXIST;
return NULL;
- DEFAULT_ERROR:
- default:
+DEFAULT_ERROR:
+ default:
*error = string_open_failed(errno, "%s", filename);
*yield = FF_ERROR;
return NULL;
- }
}
/* Check that we have a regular file. */
/* Check the file owner and file group if required to do so. */
if (!uid_ok)
- {
- if (rdata->pw != NULL && statbuf.st_uid == rdata->pw->pw_uid)
+ if (rdata->pw && statbuf.st_uid == rdata->pw->pw_uid)
uid_ok = TRUE;
- else if (rdata->owners != NULL)
- {
- int i;
- for (i = 1; i <= (int)(rdata->owners[0]); i++)
+ else if (rdata->owners)
+ for (int i = 1; i <= (int)(rdata->owners[0]); i++)
if (rdata->owners[i] == statbuf.st_uid) { uid_ok = TRUE; break; }
- }
- }
if (!gid_ok)
- {
- if (rdata->pw != NULL && statbuf.st_gid == rdata->pw->pw_gid)
+ if (rdata->pw && statbuf.st_gid == rdata->pw->pw_gid)
gid_ok = TRUE;
- else if (rdata->owngroups != NULL)
- {
- int i;
- for (i = 1; i <= (int)(rdata->owngroups[0]); i++)
+ else if (rdata->owngroups)
+ for (int i = 1; i <= (int)(rdata->owngroups[0]); i++)
if (rdata->owngroups[i] == statbuf.st_gid) { gid_ok = TRUE; break; }
- }
- }
if (!uid_ok || !gid_ok)
{
/* Read the file in one go in order to minimize the time we have it open. */
-filebuf = store_get(statbuf.st_size + 1);
+filebuf = store_get(statbuf.st_size + 1, is_tainted(filename));
if (fread(filebuf, 1, statbuf.st_size, fwd) != statbuf.st_size)
{
}
filebuf[statbuf.st_size] = 0;
-DEBUG(D_route)
- debug_printf(OFF_T_FMT " bytes read from %s\n", statbuf.st_size, filename);
+DEBUG(D_route) debug_printf(OFF_T_FMT " %sbytes read from %s\n",
+ statbuf.st_size, is_tainted(filename) ? "(tainted) " : "", filename);
(void)fclose(fwd);
return filebuf;
if (rdata->isfile)
{
int yield = 0;
- data = rda_get_file_contents(rdata, options, error, &yield);
- if (data == NULL) return yield;
+ if (!(data = rda_get_file_contents(rdata, options, error, &yield)))
+ return yield;
}
else data = rdata->string;
-*filtertype = system_filtering? FILTER_EXIM : rda_is_filter(data);
+*filtertype = f.system_filtering ? FILTER_EXIM : rda_is_filter(data);
/* Filter interpretation is done by a general function that is also called from
the filter testing option (-bf). There are two versions: one for Exim filtering
int old_expand_forbid = expand_forbid;
DEBUG(D_route) debug_printf("data is %s filter program\n",
- (*filtertype == FILTER_EXIM)? "an Exim" : "a Sieve");
+ *filtertype == FILTER_EXIM ? "an Exim" : "a Sieve");
/* RDO_FILTER is an "allow" bit */
- if ((options & RDO_FILTER) == 0)
+ if (!(options & RDO_FILTER))
{
*error = US"filtering not enabled";
return FF_ERROR;
}
expand_forbid =
- (expand_forbid & ~RDO_FILTER_EXPANSIONS) |
- (options & RDO_FILTER_EXPANSIONS);
+ expand_forbid & ~RDO_FILTER_EXPANSIONS | options & RDO_FILTER_EXPANSIONS;
/* RDO_{EXIM,SIEVE}_FILTER are forbid bits */
}
else
{
- if ((options & RDO_SIEVE_FILTER) != 0)
+ if (options & RDO_SIEVE_FILTER)
{
*error = US"Sieve filtering not enabled";
return FF_ERROR;
else
/* We know we have enough memory so disable the error on "len" */
/* coverity[tainted_data] */
- if (read(fd, *sp = store_get(len), len) != len) return FALSE;
+ /* We trust the data source, so untainted */
+ if (read(fd, *sp = store_get(len, FALSE), len) != len) return FALSE;
return TRUE;
}
/* This function is passed a forward list string (unexpanded) or the name of a
file (unexpanded) whose contents are the forwarding list. The list may in fact
be a filter program if it starts with "#Exim filter" or "#Sieve filter". Other
-types of filter, with different inital tag strings, may be introduced in due
+types of filter, with different initial tag strings, may be introduced in due
course.
The job of the function is to process the forwarding list or filter. It is
uschar *readerror = US"";
void (*oldsignal)(int);
-DEBUG(D_route) debug_printf("rda_interpret (%s): %s\n",
- (rdata->isfile)? "file" : "string", rdata->string);
+DEBUG(D_route) debug_printf("rda_interpret (%s): '%s'\n",
+ rdata->isfile ? "file" : "string", string_printing(rdata->string));
/* Do the expansions of the file name or data first, while still privileged. */
-data = expand_string(rdata->string);
-if (data == NULL)
+if (!(data = expand_string(rdata->string)))
{
- if (expand_string_forcedfail) return FF_NOTDELIVERED;
+ if (f.expand_string_forcedfail) return FF_NOTDELIVERED;
*error = string_sprintf("failed to expand \"%s\": %s", rdata->string,
expand_string_message);
return FF_ERROR;
}
rdata->string = data;
-DEBUG(D_route) debug_printf("expanded: %s\n", data);
+DEBUG(D_route)
+ debug_printf("expanded: '%s'%s\n", data, is_tainted(data) ? " (tainted)":"");
if (rdata->isfile && data[0] != '/')
{
(!rdata->isfile && /* We've got the data, and */
rda_is_filter(data) == FILTER_FORWARD && /* It's not a filter script, */
Ustrstr(data, ":include:") == NULL)) /* and there's no :include: */
- {
return rda_extract(rdata, options, include_directory,
sieve_vacation_directory, sieve_enotify_mailto_owner, sieve_useraddress,
sieve_subaddress, generated, error, eblockp, filtertype);
- }
/* We need to run the processing code in a sub-process. However, if we can
determine the non-existence of a file first, we can decline without having to
oldsignal = signal(SIGCHLD, SIG_DFL);
search_tidyup();
-if ((pid = fork()) == 0)
+if ((pid = exim_fork(US"router-interpret")) == 0)
{
header_line *waslast = header_last; /* Save last header */
/* Pass back the contents of any syntax error blocks if we have a pointer */
- if (eblockp != NULL)
+ if (eblockp)
{
- error_block *ep;
- for (ep = *eblockp; ep != NULL; ep = ep->next)
+ for (error_block * ep = *eblockp; ep; ep = ep->next)
if ( rda_write_string(fd, ep->text1) != 0
|| rda_write_string(fd, ep->text2) != 0
)
original header lines that were removed, and then any header lines that were
added but not subsequently removed. */
- if (system_filtering)
+ if (f.system_filtering)
{
int i = 0;
- header_line *h;
- for (h = header_list; h != waslast->next; i++, h = h->next)
+ for (header_line * h = header_list; h != waslast->next; i++, h = h->next)
if ( h->type == htype_old
&& write(fd, &i, sizeof(i)) != sizeof(i)
)
if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
yield == FF_FAIL || yield == FF_FREEZE)
{
- address_item *addr;
- for (addr = *generated; addr != NULL; addr = addr->next)
+ for (address_item * addr = *generated; addr; addr = addr->next)
{
int reply_options = 0;
+ int ig_err = addr->prop.ignore_error ? 1 : 0;
if ( rda_write_string(fd, addr->address) != 0
- || write(fd, &(addr->mode), sizeof(addr->mode))
- != sizeof(addr->mode)
- || write(fd, &(addr->flags), sizeof(addr->flags))
- != sizeof(addr->flags)
+ || write(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode)
+ || write(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags)
|| rda_write_string(fd, addr->prop.errors_address) != 0
+ || write(fd, &ig_err, sizeof(ig_err)) != sizeof(ig_err)
)
goto bad;
- if (addr->pipe_expandn != NULL)
- {
- uschar **pp;
- for (pp = addr->pipe_expandn; *pp != NULL; pp++)
+ if (addr->pipe_expandn)
+ for (uschar ** pp = addr->pipe_expandn; *pp; pp++)
if (rda_write_string(fd, *pp) != 0)
goto bad;
- }
if (rda_write_string(fd, NULL) != 0)
goto bad;
- if (addr->reply == NULL)
+ if (!addr->reply)
{
if (write(fd, &reply_options, sizeof(int)) != sizeof(int)) /* 0 means no reply */
goto bad;
out:
(void)close(fd);
search_tidyup();
- _exit(0);
+ exim_underbar_exit(EXIT_SUCCESS);
bad:
DEBUG(D_rewrite) debug_printf("rda_interpret: failed write to pipe\n");
/* Read the contents of any syntax error blocks if we have a pointer */
-if (eblockp != NULL)
+if (eblockp)
{
- uschar *s;
error_block *e;
- error_block **p = eblockp;
- for (;;)
+ for (error_block ** p = eblockp; ; p = &e->next)
{
+ uschar *s;
if (!rda_read_string(fd, &s)) goto DISASTER;
- if (s == NULL) break;
- e = store_get(sizeof(error_block));
+ if (!s) break;
+ e = store_get(sizeof(error_block), FALSE);
e->next = NULL;
e->text1 = s;
if (!rda_read_string(fd, &s)) goto DISASTER;
e->text2 = s;
*p = e;
- p = &(e->next);
}
}
/* If this is a system filter, read the identify of any original header lines
that were removed, and then read data for any new ones that were added. */
-if (system_filtering)
+if (f.system_filtering)
{
int hn = 0;
header_line *h = header_list;
while (hn < n)
{
hn++;
- h = h->next;
- if (h == NULL) goto DISASTER_NO_HEADER;
+ if (!(h = h->next)) goto DISASTER_NO_HEADER;
}
h->type = htype_old;
}
uschar *s;
int type;
if (!rda_read_string(fd, &s)) goto DISASTER;
- if (s == NULL) break;
+ if (!s) break;
if (read(fd, &type, sizeof(type)) != sizeof(type)) goto DISASTER;
header_add(type, "%s", s);
}
/* First string is the address; NULL => end of addresses */
if (!rda_read_string(fd, &recipient)) goto DISASTER;
- if (recipient == NULL) break;
+ if (!recipient) break;
/* Hang on the end of the chain */
/* Next comes the mode and the flags fields */
- if (read(fd, &(addr->mode), sizeof(addr->mode)) != sizeof(addr->mode) ||
- read(fd, &(addr->flags), sizeof(addr->flags)) != sizeof(addr->flags) ||
- !rda_read_string(fd, &(addr->prop.errors_address))) goto DISASTER;
+ if ( read(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode)
+ || read(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags)
+ || !rda_read_string(fd, &addr->prop.errors_address)
+ || read(fd, &i, sizeof(i)) != sizeof(i)
+ )
+ goto DISASTER;
+ addr->prop.ignore_error = (i != 0);
/* Next comes a possible setting for $thisaddress and any numerical
variables for pipe expansion, terminated by a NULL string. The maximum
if (i > 0)
{
- addr->pipe_expandn = store_get((i+1) * sizeof(uschar *));
+ addr->pipe_expandn = store_get((i+1) * sizeof(uschar *), FALSE);
addr->pipe_expandn[i] = NULL;
while (--i >= 0) addr->pipe_expandn[i] = expandn[i];
}
if (read(fd, &reply_options, sizeof(int)) != sizeof(int)) goto DISASTER;
if ((reply_options & REPLY_EXISTS) != 0)
{
- addr->reply = store_get(sizeof(reply_item));
+ addr->reply = store_get(sizeof(reply_item), FALSE);
addr->reply->file_expand = (reply_options & REPLY_EXPAND) != 0;
addr->reply->return_message = (reply_options & REPLY_RETURN) != 0;
sizeof(int) ||
read(fd,&(addr->reply->once_repeat),sizeof(time_t)) !=
sizeof(time_t) ||
- !rda_read_string(fd, &(addr->reply->to)) ||
- !rda_read_string(fd, &(addr->reply->cc)) ||
- !rda_read_string(fd, &(addr->reply->bcc)) ||
- !rda_read_string(fd, &(addr->reply->from)) ||
- !rda_read_string(fd, &(addr->reply->reply_to)) ||
- !rda_read_string(fd, &(addr->reply->subject)) ||
- !rda_read_string(fd, &(addr->reply->headers)) ||
- !rda_read_string(fd, &(addr->reply->text)) ||
- !rda_read_string(fd, &(addr->reply->file)) ||
- !rda_read_string(fd, &(addr->reply->logfile)) ||
- !rda_read_string(fd, &(addr->reply->oncelog)))
+ !rda_read_string(fd, &addr->reply->to) ||
+ !rda_read_string(fd, &addr->reply->cc) ||
+ !rda_read_string(fd, &addr->reply->bcc) ||
+ !rda_read_string(fd, &addr->reply->from) ||
+ !rda_read_string(fd, &addr->reply->reply_to) ||
+ !rda_read_string(fd, &addr->reply->subject) ||
+ !rda_read_string(fd, &addr->reply->headers) ||
+ !rda_read_string(fd, &addr->reply->text) ||
+ !rda_read_string(fd, &addr->reply->file) ||
+ !rda_read_string(fd, &addr->reply->logfile) ||
+ !rda_read_string(fd, &addr->reply->oncelog))
goto DISASTER;
}
}
WAIT_EXIT:
while ((rc = wait(&status)) != pid)
- {
if (rc < 0 && errno == ECHILD) /* Process has vanished */
{
log_write(0, LOG_MAIN, "redirection process %d vanished unexpectedly", pid);
goto FINAL_EXIT;
}
- }
DEBUG(D_route)
debug_printf("rda_interpret: subprocess yield=%d error=%s\n", yield, *error);