Example #1
0
File: rda.c Project: toddr/exim
int
rda_interpret(redirect_block *rdata, int options, uschar *include_directory,
  uschar *sieve_vacation_directory, uschar *sieve_enotify_mailto_owner,
  uschar *sieve_useraddress, uschar *sieve_subaddress, ugid_block *ugid,
  address_item **generated, uschar **error, error_block **eblockp,
  int *filtertype, uschar *rname)
{
int fd, rc, pfd[2];
int yield, status;
BOOL had_disaster = FALSE;
pid_t pid;
uschar *data;
uschar *readerror = US"";
void (*oldsignal)(int);

DEBUG(D_route) debug_printf("rda_interpret (%s): %s\n",
  (rdata->isfile)? "file" : "string", rdata->string);

/* Do the expansions of the file name or data first, while still privileged. */

data = expand_string(rdata->string);
if (data == NULL)
  {
  if (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);

if (rdata->isfile && data[0] != '/')
  {
  *error = string_sprintf("\"%s\" is not an absolute path", data);
  return FF_ERROR;
  }

/* If no uid/gid are supplied, or if we have a data string which does not start
with #Exim filter or #Sieve filter, and does not contain :include:, do all the
work in this process. Note that for a system filter, we always have a file, so
the work is done in this process only if no user is supplied. */

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
create the sub-process. */

if (rdata->isfile && rda_exists(data, error) == FILE_NOT_EXIST)
  return FF_NONEXIST;

/* If the file does exist, or we can't tell (non-root mounted NFS directory)
we have to create the subprocess to do everything as the given user. The
results of processing are passed back via a pipe. */

if (pipe(pfd) != 0)
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "creation of pipe for filter or "
    ":include: failed for %s: %s", rname, strerror(errno));

/* Ensure that SIGCHLD is set to SIG_DFL before forking, so that the child
process can be waited for. We sometimes get here with it set otherwise. Save
the old state for resetting on the wait. Ensure that all cached resources are
freed so that the subprocess starts with a clean slate and doesn't interfere
with the parent process. */

oldsignal = signal(SIGCHLD, SIG_DFL);
search_tidyup();

if ((pid = fork()) == 0)
  {
  header_line *waslast = header_last;   /* Save last header */

  fd = pfd[pipe_write];
  (void)close(pfd[pipe_read]);
  exim_setugid(ugid->uid, ugid->gid, FALSE, rname);

  /* Addresses can get rewritten in filters; if we are not root or the exim
  user (and we probably are not), turn off rewrite logging, because we cannot
  write to the log now. */

  if (ugid->uid != root_uid && ugid->uid != exim_uid)
    {
    DEBUG(D_rewrite) debug_printf("turned off address rewrite logging (not "
      "root or exim in this process)\n");
    BIT_CLEAR(log_selector, log_selector_size, Li_address_rewrite);
    }

  /* Now do the business */

  yield = rda_extract(rdata, options, include_directory,
    sieve_vacation_directory, sieve_enotify_mailto_owner, sieve_useraddress,
    sieve_subaddress, generated, error, eblockp, filtertype);

  /* Pass back whether it was a filter, and the return code and any overall
  error text via the pipe. */

  if (  write(fd, filtertype, sizeof(int)) != sizeof(int)
     || write(fd, &yield, sizeof(int)) != sizeof(int)
     || rda_write_string(fd, *error) != 0
     )
    goto bad;

  /* Pass back the contents of any syntax error blocks if we have a pointer */

  if (eblockp != NULL)
    {
    error_block *ep;
    for (ep = *eblockp; ep != NULL; ep = ep->next)
      if (  rda_write_string(fd, ep->text1) != 0
         || rda_write_string(fd, ep->text2) != 0
	 )
	goto bad;
    if (rda_write_string(fd, NULL) != 0)    /* Indicates end of eblocks */
      goto bad;
    }

  /* If this is a system filter, we have to pass back the numbers of any
  original header lines that were removed, and then any header lines that were
  added but not subsequently removed. */

  if (system_filtering)
    {
    int i = 0;
    header_line *h;
    for (h = header_list; h != waslast->next; i++, h = h->next)
      if (  h->type == htype_old
         && write(fd, &i, sizeof(i)) != sizeof(i)
	 )
	goto bad;

    i = -1;
    if (write(fd, &i, sizeof(i)) != sizeof(i))
	goto bad;

    while (waslast != header_last)
      {
      waslast = waslast->next;
      if (waslast->type != htype_old)
	if (  rda_write_string(fd, waslast->text) != 0
           || write(fd, &(waslast->type), sizeof(waslast->type))
	      != sizeof(waslast->type)
	   )
	  goto bad;
      }
    if (rda_write_string(fd, NULL) != 0)    /* Indicates end of added headers */
      goto bad;
    }

  /* Write the contents of the $n variables */

  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
  addresses, and their associated information, through the pipe. This is
  just tedious, but it seems to be the only safe way. We do this also for
  FAIL and FREEZE, because a filter is allowed to set up deliveries that
  are honoured before freezing or failing. */

  if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
      yield == FF_FAIL || yield == FF_FREEZE)
    {
    address_item *addr;
    for (addr = *generated; addr != NULL; addr = addr->next)
      {
      int reply_options = 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)
         || rda_write_string(fd, addr->prop.errors_address) != 0
	 )
	goto bad;

      if (addr->pipe_expandn != NULL)
        {
        uschar **pp;
        for (pp = addr->pipe_expandn; *pp != NULL; pp++)
          if (rda_write_string(fd, *pp) != 0)
	    goto bad;
        }
      if (rda_write_string(fd, NULL) != 0)
        goto bad;

      if (addr->reply == NULL)
	{
        if (write(fd, &reply_options, sizeof(int)) != sizeof(int))    /* 0 means no reply */
	  goto bad;
	}
      else
        {
        reply_options |= REPLY_EXISTS;
        if (addr->reply->file_expand) reply_options |= REPLY_EXPAND;
        if (addr->reply->return_message) reply_options |= REPLY_RETURN;
        if (  write(fd, &reply_options, sizeof(int)) != sizeof(int)
           || write(fd, &(addr->reply->expand_forbid), sizeof(int))
	      != sizeof(int)
           || 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
           || rda_write_string(fd, addr->reply->bcc) != 0
           || rda_write_string(fd, addr->reply->from) != 0
           || rda_write_string(fd, addr->reply->reply_to) != 0
           || rda_write_string(fd, addr->reply->subject) != 0
           || rda_write_string(fd, addr->reply->headers) != 0
           || rda_write_string(fd, addr->reply->text) != 0
           || rda_write_string(fd, addr->reply->file) != 0
           || rda_write_string(fd, addr->reply->logfile) != 0
           || rda_write_string(fd, addr->reply->oncelog) != 0
	   )
	  goto bad;
        }
      }

    if (rda_write_string(fd, NULL) != 0)   /* Marks end of addresses */
      goto bad;
    }

  /* OK, this process is now done. Free any cached resources. Must use _exit()
  and not exit() !! */

out:
  (void)close(fd);
  search_tidyup();
  _exit(0);

bad:
  DEBUG(D_rewrite) debug_printf("rda_interpret: failed write to pipe\n");
  goto out;
  }

/* Back in the main process: panic if the fork did not succeed. */

if (pid < 0)
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for %s", rname);

/* Read the pipe to get the data from the filter/forward. Our copy of the
writing end must be closed first, as otherwise read() won't return zero on an
empty pipe. Afterwards, close the reading end. */

(void)close(pfd[pipe_write]);

/* Read initial data, including yield and contents of *error */

fd = pfd[pipe_read];
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 */

if (eblockp != NULL)
  {
  uschar *s;
  error_block *e;
  error_block **p = eblockp;
  for (;;)
    {
    if (!rda_read_string(fd, &s)) goto DISASTER;
    if (s == NULL) break;
    e = store_get(sizeof(error_block));
    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)
  {
  int hn = 0;
  header_line *h = header_list;

  for (;;)
    {
    int n;
    if (read(fd, &n, sizeof(int)) != sizeof(int)) goto DISASTER;
    if (n < 0) break;
    while (hn < n)
      {
      hn++;
      h = h->next;
      if (h == NULL) goto DISASTER_NO_HEADER;
      }
    h->type = htype_old;
    }

  for (;;)
    {
    uschar *s;
    int type;
    if (!rda_read_string(fd, &s)) goto DISASTER;
    if (s == NULL) break;
    if (read(fd, &type, sizeof(type)) != sizeof(type)) goto DISASTER;
    header_add(type, "%s", s);
    }
  }

/* Read the values of the $n variables */

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
generated chain. */

if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
    yield == FF_FAIL || yield == FF_FREEZE)
  {
  address_item **nextp = generated;

  for (;;)
    {
    int i, reply_options;
    address_item *addr;
    uschar *recipient;
    uschar *expandn[EXPAND_MAXN + 2];

    /* First string is the address; NULL => end of addresses */

    if (!rda_read_string(fd, &recipient)) goto DISASTER;
    if (recipient == NULL) break;

    /* Hang on the end of the chain */

    addr = deliver_make_addr(recipient, FALSE);
    *nextp = addr;
    nextp = &(addr->next);

    /* 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;

    /* Next comes a possible setting for $thisaddress and any numerical
    variables for pipe expansion, terminated by a NULL string. The maximum
    number of numericals is EXPAND_MAXN. Note that we put filter_thisaddress
    into the zeroth item in the vector - this is sorted out inside the pipe
    transport. */

    for (i = 0; i < EXPAND_MAXN + 1; i++)
      {
      uschar *temp;
      if (!rda_read_string(fd, &temp)) goto DISASTER;
      if (i == 0) filter_thisaddress = temp;           /* Just in case */
      expandn[i] = temp;
      if (temp == NULL) break;
      }

    if (i > 0)
      {
      addr->pipe_expandn = store_get((i+1) * sizeof(uschar **));
      addr->pipe_expandn[i] = NULL;
      while (--i >= 0) addr->pipe_expandn[i] = expandn[i];
      }

    /* Then an int containing reply options; zero => no reply data. */

    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->file_expand = (reply_options & REPLY_EXPAND) != 0;
      addr->reply->return_message = (reply_options & REPLY_RETURN) != 0;

      if (read(fd,&(addr->reply->expand_forbid),sizeof(int)) !=
            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)))
        goto DISASTER;
      }
    }
  }

/* All data has been transferred from the sub-process. Reap it, close the
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);

if (had_disaster)
  {
  *error = string_sprintf("internal problem in %s: failure to transfer "
    "data from subprocess: status=%04x%s%s%s", rname,
    status, readerror,
    (*error == NULL)? US"" : US": error=",
    (*error == NULL)? US"" : *error);
  log_write(0, LOG_MAIN|LOG_PANIC, "%s", *error);
  }
else if (status != 0)
  {
  log_write(0, LOG_MAIN|LOG_PANIC, "internal problem in %s: unexpected status "
    "%04x from redirect subprocess (but data correctly received)", rname,
    status);
  }

FINAL_EXIT:
(void)close(fd);
signal(SIGCHLD, oldsignal);   /* restore */
return yield;


/* Come here if the data indicates removal of a header that we can't find */

DISASTER_NO_HEADER:
readerror = US" readerror=bad header identifier";
had_disaster = TRUE;
yield = FF_ERROR;
goto WAIT_EXIT;

/* Come here is there's a shambles in transferring the data over the pipe. The
value of errno should still be set. */

DISASTER:
readerror = string_sprintf(" readerror='%s'", strerror(errno));
had_disaster = TRUE;
yield = FF_ERROR;
goto WAIT_EXIT;
}
Example #2
0
int redirect_router_entry(
  router_instance *rblock,        /* data for this instantiation */
  address_item *addr,             /* address we are working on */
  struct passwd *pw,              /* passwd entry after check_local_user */
  int verify,                     /* v_none/v_recipient/v_sender/v_expn */
  address_item **addr_local,      /* add it to this if it's local */
  address_item **addr_remote,     /* add it to this if it's remote */
  address_item **addr_new,        /* put new addresses on here */
  address_item **addr_succeed)    /* put old address here on success */
{
redirect_router_options_block *ob =
  (redirect_router_options_block *)(rblock->options_block);
address_item *generated = NULL;
const uschar *save_qualify_domain_recipient = qualify_domain_recipient;
uschar *discarded = US"discarded";
address_item_propagated addr_prop;
error_block *eblock = NULL;
ugid_block ugid;
redirect_block redirect;
int filtertype = FILTER_UNSET;
int yield = OK;
int options = ob->bit_options;
int frc = 0;
int xrc = 0;

addr_local = addr_local;     /* Keep picky compilers happy */
addr_remote = addr_remote;

/* Initialize the data to be propagated to the children */

addr_prop.address_data = deliver_address_data;
addr_prop.domain_data = deliver_domain_data;
addr_prop.localpart_data = deliver_localpart_data;
addr_prop.errors_address = NULL;
addr_prop.extra_headers = NULL;
addr_prop.remove_headers = NULL;

#ifdef EXPERIMENTAL_SRS
addr_prop.srs_sender = NULL;
#endif

/* When verifying and testing addresses, the "logwrite" command in filters
must be bypassed. */

if (verify == v_none && !address_test_mode) options |= RDO_REALLOG;

/* Sort out the fixed or dynamic uid/gid. This uid is used (a) for reading the
file (and interpreting a filter) and (b) for running the transports for
generated file and pipe addresses. It is not (necessarily) the same as the uids
that may own the file. Exim panics if an expanded string is not a number and
can't be found in the password file. Other errors set the freezing bit. */

if (!rf_get_ugid(rblock, addr, &ugid)) return DEFER;

if (!ugid.uid_set && pw != NULL)
  {
  ugid.uid = pw->pw_uid;
  ugid.uid_set = TRUE;
  }

if (!ugid.gid_set && pw != NULL)
  {
  ugid.gid = pw->pw_gid;
  ugid.gid_set = TRUE;
  }

#ifdef EXPERIMENTAL_SRS
  /* Perform SRS on recipient/return-path as required  */

  if(ob->srs != NULL)
  {
    BOOL usesrs = TRUE;

    if(ob->srs_condition != NULL)
      usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL);

    if(usesrs)
    {
      int srs_action = 0, n_srs;
      uschar *res;
      uschar *usedomain;

      /* What are we doing? */
      if(Ustrcmp(ob->srs, "forward") == 0)
        srs_action = 1;
      else if(Ustrcmp(ob->srs, "reverseandforward") == 0)
      {
        srs_action = 3;

        if((ob->srs_dbinsert == NULL) ^ (ob->srs_dbselect == NULL))
          return DEFER;
      }
      else if(Ustrcmp(ob->srs, "reverse") == 0)
        srs_action = 2;

      /* Reverse SRS */
      if(srs_action & 2)
      {
        srs_orig_recipient = addr->address;

        eximsrs_init();
        if(ob->srs_dbselect)
          eximsrs_db_set(TRUE, ob->srs_dbselect);
/* Comment this out for now...
//        else
//          eximsrs_db_set(TRUE, NULL);
*/

        if((n_srs = eximsrs_reverse(&res, addr->address)) == OK)
        {
          srs_recipient = res;
          DEBUG(D_any)
            debug_printf("SRS (reverse): Recipient '%s' rewritten to '%s'\n", srs_orig_recipient, srs_recipient);
        }

        eximsrs_done();

        if(n_srs != OK)
          return n_srs;
      }

      /* Forward SRS */
      /* No point in actually performing SRS if we are just verifying a recipient */
      if((srs_action & 1) && verify == v_none &&
         (sender_address ? sender_address[0] != 0 : FALSE))
      {

        srs_orig_sender = sender_address;
        eximsrs_init();
        if(ob->srs_dbinsert)
          eximsrs_db_set(FALSE, ob->srs_dbinsert);
/* Comment this out for now...
//        else
//          eximsrs_db_set(FALSE, NULL);
*/

        if(ob->srs_alias != NULL ? (usedomain = expand_string(ob->srs_alias)) == NULL : 1)
          usedomain = deliver_domain;

        if((n_srs = eximsrs_forward(&res, sender_address, usedomain)) == OK)
        {
          addr_prop.srs_sender = res;
          DEBUG(D_any)
            debug_printf("SRS (forward): Sender '%s' rewritten to '%s'\n", srs_orig_sender, res);
        }

        eximsrs_done();

        if(n_srs != OK)
          return n_srs;
      }
    }
  }
#endif

/* Call the function that interprets redirection data, either inline or from a
file. This is a separate function so that the system filter can use it. It will
run the function in a subprocess if necessary. If qualify_preserve_domain is
set, temporarily reset qualify_domain_recipient to the current domain so that
any unqualified addresses get qualified with the same domain as the incoming
address. Otherwise, if a local qualify_domain is provided, set that up. */

if (ob->qualify_preserve_domain)
  qualify_domain_recipient = addr->domain;
else if (ob->qualify_domain != NULL)
  {
  uschar *new_qdr = rf_expand_data(addr, ob->qualify_domain, &xrc);
  if (new_qdr == NULL) return xrc;
  qualify_domain_recipient = new_qdr;
  }

redirect.owners = ob->owners;
redirect.owngroups = ob->owngroups;
redirect.modemask = ob->modemask;
redirect.check_owner = ob->check_owner;
redirect.check_group = ob->check_group;
redirect.pw = pw;

if (ob->file != NULL)
  {
  redirect.string = ob->file;
  redirect.isfile = TRUE;
  }
else
  {
  redirect.string = ob->data;
  redirect.isfile = FALSE;
  }

frc = rda_interpret(&redirect, options, ob->include_directory,
  ob->sieve_vacation_directory, ob->sieve_enotify_mailto_owner,
  ob->sieve_useraddress, ob->sieve_subaddress, &ugid, &generated,
  &(addr->message), ob->skip_syntax_errors? &eblock : NULL, &filtertype,
  string_sprintf("%s router (recipient is %s)", rblock->name, addr->address));

qualify_domain_recipient = save_qualify_domain_recipient;

/* Handle exceptional returns from filtering or processing an address list.
For FAIL and FREEZE we honour any previously set up deliveries by a filter. */

switch (frc)
  {
  case FF_NONEXIST:
  addr->message = addr->user_message = NULL;
  return DECLINE;

  case FF_BLACKHOLE:
  DEBUG(D_route) debug_printf("address :blackhole:d\n");
  generated = NULL;
  discarded = US":blackhole:";
  frc = FF_DELIVERED;
  break;

  /* FF_DEFER and FF_FAIL can arise only as a result of explicit commands
  (:defer: or :fail: in an alias file or "fail" in a filter). If a configured
  message was supplied, allow it to be included in an SMTP response after
  verifying. Remove any SMTP code if it is not allowed. */

  case FF_DEFER:
  yield = DEFER;
  goto SORT_MESSAGE;

  case FF_FAIL:
  if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK)
    return xrc;
  add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
  yield = FAIL;

  SORT_MESSAGE:
  if (addr->message == NULL)
    addr->message = (yield == FAIL)? US"forced rejection" : US"forced defer";
  else
    {
    int ovector[3];
    if (ob->forbid_smtp_code &&
        pcre_exec(regex_smtp_code, NULL, CS addr->message,
          Ustrlen(addr->message), 0, PCRE_EOPT,
          ovector, sizeof(ovector)/sizeof(int)) >= 0)
      {
      DEBUG(D_route) debug_printf("SMTP code at start of error message "
        "is ignored because forbid_smtp_code is set\n");
      addr->message += ovector[1];
      }
    addr->user_message = addr->message;
    setflag(addr, af_pass_message);
    }
  return yield;

  /* As in the case of a system filter, a freeze does not happen after a manual
  thaw. In case deliveries were set up by the filter, we set the child count
  high so that their completion does not mark the original address done. */

  case FF_FREEZE:
  if (!deliver_manual_thaw)
    {
    if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop))
      != OK) return xrc;
    add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
    if (addr->message == NULL) addr->message = US"frozen by filter";
    addr->special_action = SPECIAL_FREEZE;
    addr->child_count = 9999;
    return DEFER;
    }
  frc = FF_NOTDELIVERED;
  break;

  /* Handle syntax errors and :include: failures and lookup defers */

  case FF_ERROR:
  case FF_INCLUDEFAIL:

  /* If filtertype is still FILTER_UNSET, it means that the redirection data
  was never inspected, so the error was an expansion failure or failure to open
  the file, or whatever. In these cases, the existing error message is probably
  sufficient. */

  if (filtertype == FILTER_UNSET) return DEFER;

  /* If it was a filter and skip_syntax_errors is set, we want to set up
  the error message so that it can be logged and mailed to somebody. */

  if (filtertype != FILTER_FORWARD && ob->skip_syntax_errors)
    {
    eblock = store_get(sizeof(error_block));
    eblock->next = NULL;
    eblock->text1 = addr->message;
    eblock->text2 = NULL;
    addr->message = addr->user_message = NULL;
    }

  /* Otherwise set up the error for the address and defer. */

  else
    {
    addr->basic_errno = ERRNO_BADREDIRECT;
    addr->message = string_sprintf("error in %s %s: %s",
      (filtertype != FILTER_FORWARD)? "filter" : "redirect",
      (ob->data == NULL)? "file" : "data",
      addr->message);
    return DEFER;
    }
  }


/* Yield is either FF_DELIVERED (significant action) or FF_NOTDELIVERED (no
significant action). Before dealing with these, however, we must handle the
effect of skip_syntax_errors.

If skip_syntax_errors was set and there were syntax errors in an address list,
error messages will be present in eblock. Log them and send a message if so
configured. We cannot do this earlier, because the error message must not be
sent as the local user. If there were no valid addresses, generated will be
NULL. In this case, the router declines.

For a filter file, the error message has been fudged into an eblock. After
dealing with it, the router declines. */

if (eblock != NULL)
  {
  if (!moan_skipped_syntax_errors(
        rblock->name,                            /* For message content */
        eblock,                                  /* Ditto */
        (verify != v_none || address_test_mode)?
          NULL : ob->syntax_errors_to,           /* Who to mail */
        generated != NULL,                       /* True if not all failed */
        ob->syntax_errors_text))                 /* Custom message */
    return DEFER;

  if (filtertype != FILTER_FORWARD || generated == NULL)
    {
    addr->message = US"syntax error in redirection data";
    return DECLINE;
    }
  }

/* Sort out the errors address and any header modifications, and handle the
generated addresses, if any. If there are no generated addresses, we must avoid
calling sort_errors_and_headers() in case this router declines - that function
may modify the errors_address field in the current address, and we don't want
to do that for a decline. */

if (generated != NULL)
  {
  if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK)
    return xrc;
  add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
  }

/* FF_DELIVERED with no generated addresses is what we get when an address list
contains :blackhole: or a filter contains "seen finish" without having
generated anything. Log what happened to this address, and return DISCARD. */

if (frc == FF_DELIVERED)
  {
  if (generated == NULL && verify == v_none && !address_test_mode)
    {
    log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
      rblock->name);
    yield = DISCARD;
    }
  }

/* For an address list, FF_NOTDELIVERED always means that no addresses were
generated. For a filter, addresses may or may not have been generated. If none
were, it's the same as an empty address list, and the router declines. However,
if addresses were generated, we can't just decline because successful delivery
of the base address gets it marked "done", so deferred generated addresses
never get tried again. We have to generate a new version of the base address,
as if there were a "deliver" command in the filter file, with the original
address as parent. */

else
  {
  address_item *next;

  if (generated == NULL) return DECLINE;

  next = deliver_make_addr(addr->address, FALSE);
  next->parent = addr;
  addr->child_count++;
  next->next = *addr_new;
  *addr_new = next;

  /* Copy relevant flags (af_propagate is a name for the set), and set the
  data that propagates. */

  copyflag(next, addr, af_propagate);
  next->prop = addr_prop;

  DEBUG(D_route) debug_printf("%s router autogenerated %s\n%s%s%s",
    rblock->name,
    next->address,
    (addr_prop.errors_address != NULL)? "  errors to " : "",
    (addr_prop.errors_address != NULL)? addr_prop.errors_address : US"",
    (addr_prop.errors_address != NULL)? "\n" : "");
  }

/* Control gets here only when the address has been completely handled. Put the
original address onto the succeed queue so that any retry items that get
attached to it get processed. */

addr->next = *addr_succeed;
*addr_succeed = addr;

return yield;
}
Example #3
0
int
rf_get_errors_address(address_item *addr, router_instance *rblock,
  int verify, uschar **errors_to)
{
uschar *s;

*errors_to = addr->prop.errors_address;
if (rblock->errors_to == NULL) return OK;

s = expand_string(rblock->errors_to);

if (s == NULL)
  {
  if (f.expand_string_forcedfail)
    {
    DEBUG(D_route)
      debug_printf("forced expansion failure - ignoring errors_to\n");
    return OK;
    }
  addr->message = string_sprintf("%s router failed to expand \"%s\": %s",
    rblock->name, rblock->errors_to, expand_string_message);
  return DEFER;
  }

/* If the errors_to address is empty, it means "ignore errors" */

if (*s == 0)
  {
  addr->prop.ignore_error = TRUE;   /* For locally detected errors */
  *errors_to = US"";                   /* Return path for SMTP */
  return OK;
  }

/* If we are already verifying, do not check the errors address, in order to
save effort (but we do verify when testing an address). When we do verify, set
the sender address to null, because that's what it will be when sending an
error message, and there are now configuration options that control the running
of routers by checking the sender address. When testing an address, there may
not be a sender address. We also need to save and restore the expansion values
associated with an address. */

if (verify != v_none)
  {
  *errors_to = s;
  DEBUG(D_route)
    debug_printf("skipped verify errors_to address: already verifying\n");
  }
else
  {
  BOOL save_address_test_mode = f.address_test_mode;
  int save1 = 0;
  int i;
  const uschar ***p;
  const uschar *address_expansions_save[ADDRESS_EXPANSIONS_COUNT];
  address_item *snew = deliver_make_addr(s, FALSE);

  if (sender_address != NULL)
    {
    save1 = sender_address[0];
    sender_address[0] = 0;
    }

  for (i = 0, p = address_expansions; *p != NULL;)
    address_expansions_save[i++] = **p++;
  f.address_test_mode = FALSE;

  /* NOTE: the address is verified as a recipient, not a sender. This is
  perhaps confusing. It isn't immediately obvious what to do: we want to have
  some confidence that we can deliver to the address, in which case it will be
  a recipient, but on the other hand, it will be passed on in SMTP deliveries
  as a sender. However, I think on balance recipient is right because sender
  verification is really about the *incoming* sender of the message.

  If this code is changed, note that you must set vopt_fake_sender instead of
  vopt_is_recipient, as otherwise sender_address may be altered because
  verify_address() thinks it is dealing with *the* sender of the message. */

  DEBUG(D_route|D_verify)
    debug_printf("------ Verifying errors address %s ------\n", s);
  if (verify_address(snew, NULL,
      vopt_is_recipient /* vopt_fake_sender is the alternative */
      | vopt_qualify, -1, -1, -1, NULL, NULL, NULL) == OK)
    *errors_to = snew->address;
  DEBUG(D_route|D_verify)
    debug_printf("------ End verifying errors address %s ------\n", s);

  f.address_test_mode = save_address_test_mode;
  for (i = 0, p = address_expansions; *p != NULL;)
    **p++ = address_expansions_save[i++];

  if (sender_address != NULL) sender_address[0] = save1;
  }

return OK;
}