Events: Fix msg:defer event for the hosts_max_try_hardlimit case. Bug 2554
[exim.git] / src / src / rda.c
index 341979df5603820ff2eb4014298835af4968cce6..aed8abc246b54b5e1a764f0e13171c827f958542 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* 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
@@ -41,7 +42,7 @@ Returns:   FILTER_EXIM    if it starts with "# Exim filter"
 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++;
@@ -59,10 +60,10 @@ tags for other types of filter. */
 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;
 }
 
 
@@ -175,6 +176,17 @@ BOOL uid_ok = !rdata->check_owner;
 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
@@ -184,40 +196,35 @@ However, if the ignore_enotdir option is set (to ignore "something on the
 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. */
@@ -246,22 +253,18 @@ if ((statbuf.st_mode & rdata->modemask) != 0)
 /* 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)
+  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)
+  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)
   {
@@ -291,8 +294,8 @@ 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;
@@ -347,8 +350,8 @@ uschar *data;
 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;
 
@@ -370,7 +373,7 @@ if (*filtertype != FILTER_FORWARD)
 
   /* RDO_FILTER is an "allow" bit */
 
-  if ((options & RDO_FILTER) == 0)
+  if (!(options & RDO_FILTER))
     {
     *error = US"filtering not enabled";
     return FF_ERROR;
@@ -392,7 +395,7 @@ if (*filtertype != FILTER_FORWARD)
     }
   else
     {
-    if ((options & RDO_SIEVE_FILTER) != 0)
+    if (options & RDO_SIEVE_FILTER)
       {
       *error = US"Sieve filtering not enabled";
       return FF_ERROR;
@@ -439,9 +442,9 @@ Returns:     -1 on error, else 0
 static int
 rda_write_string(int fd, const uschar *s)
 {
-int len = s ? Ustrlen(s) + 1 : 0;
-return (  os_pipe_write(fd, &len, sizeof(int)) != sizeof(int)
-       || s  &&  write(fd, s, len) != len
+int len = (s == NULL)? 0 : Ustrlen(s) + 1;
+return (  write(fd, &len, sizeof(int)) != sizeof(int)
+       || (s != NULL  &&  write(fd, s, len) != len)
        )
        ? -1 : 0;
 }
@@ -466,14 +469,14 @@ rda_read_string(int fd, uschar **sp)
 {
 int len;
 
-if (os_pipe_read(fd, &len, sizeof(int)) != sizeof(int)) return FALSE;
+if (read(fd, &len, sizeof(int)) != sizeof(int)) return FALSE;
 if (len == 0)
   *sp = NULL;
 else
   /* We know we have enough memory so disable the error on "len" */
   /* coverity[tainted_data] */
   /* We trust the data source, so untainted */
-  if (os_pipe_read(fd, *sp = store_get(len, FALSE), len) != len) return FALSE;
+  if (read(fd, *sp = store_get(len, FALSE), len) != len) return FALSE;
 return TRUE;
 }
 
@@ -566,7 +569,8 @@ if (!(data = expand_string(rdata->string)))
   }
 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] != '/')
   {
@@ -583,11 +587,9 @@ if (!ugid->uid_set ||                         /* Either there's no uid, or */
     (!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
@@ -613,7 +615,7 @@ with the parent process. */
 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 */
 
@@ -641,8 +643,8 @@ if ((pid = fork()) == 0)
   /* Pass back whether it was a filter, and the return code and any overall
   error text via the pipe. */
 
-  if (  os_pipe_write(fd, filtertype, sizeof(int)) != sizeof(int)
-     || os_pipe_write(fd, &yield, sizeof(int)) != sizeof(int)
+  if (  write(fd, filtertype, sizeof(int)) != sizeof(int)
+     || write(fd, &yield, sizeof(int)) != sizeof(int)
      || rda_write_string(fd, *error) != 0
      )
     goto bad;
@@ -669,12 +671,12 @@ if ((pid = fork()) == 0)
     int i = 0;
     for (header_line * h = header_list; h != waslast->next; i++, h = h->next)
       if (  h->type == htype_old
-         && os_pipe_write(fd, &i, sizeof(i)) != sizeof(i)
+         && write(fd, &i, sizeof(i)) != sizeof(i)
         )
        goto bad;
 
     i = -1;
-    if (os_pipe_write(fd, &i, sizeof(i)) != sizeof(i))
+    if (write(fd, &i, sizeof(i)) != sizeof(i))
        goto bad;
 
     while (waslast != header_last)
@@ -682,7 +684,7 @@ if ((pid = fork()) == 0)
       waslast = waslast->next;
       if (waslast->type != htype_old)
        if (  rda_write_string(fd, waslast->text) != 0
-           || os_pipe_write(fd, &(waslast->type), sizeof(waslast->type))
+           || write(fd, &(waslast->type), sizeof(waslast->type))
              != sizeof(waslast->type)
           )
          goto bad;
@@ -693,7 +695,7 @@ if ((pid = fork()) == 0)
 
   /* Write the contents of the $n variables */
 
-  if (os_pipe_write(fd, filter_n, sizeof(filter_n)) != sizeof(filter_n))
+  if (write(fd, filter_n, sizeof(filter_n)) != sizeof(filter_n))
     goto bad;
 
   /* If the result was DELIVERED or NOTDELIVERED, we pass back the generated
@@ -711,10 +713,10 @@ if ((pid = fork()) == 0)
       int ig_err = addr->prop.ignore_error ? 1 : 0;
 
       if (  rda_write_string(fd, addr->address) != 0
-         || os_pipe_write(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode)
-         || os_pipe_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
-         || os_pipe_write(fd, &ig_err, sizeof(ig_err)) != sizeof(ig_err)
+         || write(fd, &ig_err, sizeof(ig_err)) != sizeof(ig_err)
         )
        goto bad;
 
@@ -727,7 +729,7 @@ if ((pid = fork()) == 0)
 
       if (!addr->reply)
        {
-        if (os_pipe_write(fd, &reply_options, sizeof(int)) != sizeof(int))    /* 0 means no reply */
+        if (write(fd, &reply_options, sizeof(int)) != sizeof(int))    /* 0 means no reply */
          goto bad;
        }
       else
@@ -735,10 +737,10 @@ if ((pid = fork()) == 0)
         reply_options |= REPLY_EXISTS;
         if (addr->reply->file_expand) reply_options |= REPLY_EXPAND;
         if (addr->reply->return_message) reply_options |= REPLY_RETURN;
-        if (  os_pipe_write(fd, &reply_options, sizeof(int)) != sizeof(int)
-           || os_pipe_write(fd, &(addr->reply->expand_forbid), sizeof(int))
+        if (  write(fd, &reply_options, sizeof(int)) != sizeof(int)
+           || write(fd, &(addr->reply->expand_forbid), sizeof(int))
              != sizeof(int)
-           || os_pipe_write(fd, &(addr->reply->once_repeat), sizeof(time_t))
+           || write(fd, &(addr->reply->once_repeat), sizeof(time_t))
              != sizeof(time_t)
            || rda_write_string(fd, addr->reply->to) != 0
            || rda_write_string(fd, addr->reply->cc) != 0
@@ -766,7 +768,7 @@ if ((pid = fork()) == 0)
 out:
   (void)close(fd);
   search_tidyup();
-  exim_underbar_exit(0);
+  exim_underbar_exit(EXIT_SUCCESS);
 
 bad:
   DEBUG(D_rewrite) debug_printf("rda_interpret: failed write to pipe\n");
@@ -787,8 +789,8 @@ empty pipe. Afterwards, close the reading end. */
 /* Read initial data, including yield and contents of *error */
 
 fd = pfd[pipe_read];
-if (os_pipe_read(fd, filtertype, sizeof(int)) != sizeof(int) ||
-    os_pipe_read(fd, &yield, sizeof(int)) != sizeof(int) ||
+if (read(fd, filtertype, sizeof(int)) != sizeof(int) ||
+    read(fd, &yield, sizeof(int)) != sizeof(int) ||
     !rda_read_string(fd, error)) goto DISASTER;
 
 /* Read the contents of any syntax error blocks if we have a pointer */
@@ -821,7 +823,7 @@ if (f.system_filtering)
   for (;;)
     {
     int n;
-    if (os_pipe_read(fd, &n, sizeof(int)) != sizeof(int)) goto DISASTER;
+    if (read(fd, &n, sizeof(int)) != sizeof(int)) goto DISASTER;
     if (n < 0) break;
     while (hn < n)
       {
@@ -837,14 +839,14 @@ if (f.system_filtering)
     int type;
     if (!rda_read_string(fd, &s)) goto DISASTER;
     if (!s) break;
-    if (os_pipe_read(fd, &type, sizeof(type)) != sizeof(type)) goto DISASTER;
+    if (read(fd, &type, sizeof(type)) != sizeof(type)) goto DISASTER;
     header_add(type, "%s", s);
     }
   }
 
 /* Read the values of the $n variables */
 
-if (os_pipe_read(fd, filter_n, sizeof(filter_n)) != sizeof(filter_n)) goto DISASTER;
+if (read(fd, filter_n, sizeof(filter_n)) != sizeof(filter_n)) goto DISASTER;
 
 /* If the yield is DELIVERED, NOTDELIVERED, FAIL, or FREEZE there may follow
 addresses and data to go with them. Keep them in the same order in the
@@ -875,10 +877,10 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
 
     /* Next comes the mode and the flags fields */
 
-    if (  os_pipe_read(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode)
-       || os_pipe_read(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags)
+    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)
-       || os_pipe_read(fd, &i, sizeof(i)) != sizeof(i)
+       || read(fd, &i, sizeof(i)) != sizeof(i)
        )
       goto DISASTER;
     addr->prop.ignore_error = (i != 0);
@@ -907,7 +909,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
 
     /* Then an int containing reply options; zero => no reply data. */
 
-    if (os_pipe_read(fd, &reply_options, sizeof(int)) != sizeof(int)) goto DISASTER;
+    if (read(fd, &reply_options, sizeof(int)) != sizeof(int)) goto DISASTER;
     if ((reply_options & REPLY_EXISTS) != 0)
       {
       addr->reply = store_get(sizeof(reply_item), FALSE);
@@ -915,21 +917,21 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
       addr->reply->file_expand = (reply_options & REPLY_EXPAND) != 0;
       addr->reply->return_message = (reply_options & REPLY_RETURN) != 0;
 
-      if (os_pipe_read(fd,&(addr->reply->expand_forbid),sizeof(int)) !=
+      if (read(fd,&(addr->reply->expand_forbid),sizeof(int)) !=
             sizeof(int) ||
-          os_pipe_read(fd,&(addr->reply->once_repeat),sizeof(time_t)) !=
+          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;
       }
     }
@@ -940,13 +942,11 @@ reading end of the pipe, and we are done. */
 
 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);