Esempio n. 1
0
File: rfc2047.c Progetto: Exim/exim
static uschar *
decode_mimeword(uschar *string, BOOL lencheck, uschar **q1ptr, uschar **q2ptr,
  uschar **endptr, size_t *dlenptr, uschar **dptrptr)
{
uschar *mimeword;
for (;; string = mimeword + 2)
  {
  int encoding;
  int dlen = -1;

  if ((mimeword = Ustrstr(string, "=?"))  == NULL ||
      (*q1ptr = Ustrchr(mimeword+2, '?')) == NULL ||
      (*q2ptr = Ustrchr(*q1ptr+1, '?')) == NULL ||
      (*endptr = Ustrstr(*q2ptr+1, "?=")) == NULL) return NULL;

  /* We have found =?xxx?xxx?xxx?= in the string. Optionally check the
  length, and that the second field is just one character long. If not,
  continue the loop to search again. We must start just after the initial =?
  because we might have found =?xxx=?xxx?xxx?xxx?=. */

  if ((lencheck && *endptr - mimeword > 73) || *q2ptr - *q1ptr != 2) continue;

  /* Get the encoding letter, and decode the data string. */

  encoding = toupper((*q1ptr)[1]);
  **endptr = 0;
  if (encoding == 'B')
    dlen = b64decode(*q2ptr+1, dptrptr);
  else if (encoding == 'Q')
    dlen = rfc2047_qpdecode(*q2ptr+1, dptrptr);
  **endptr = '?';   /* restore */

  /* If the decoding succeeded, we are done. Set the length of the decoded
  string, and pass back the initial pointer. Otherwise, the loop continues. */

  if (dlen >= 0)
    {
    *dlenptr = (size_t)dlen;
    return mimeword;
    }
  }

/* Control should never actually get here */
}
Esempio n. 2
0
File: pipe.c Progetto: akissa/exim
static BOOL
set_up_shell_command(const uschar ***argvptr, uschar *cmd,
  BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname)
{
const uschar **argv;

*argvptr = argv = store_get((4)*sizeof(uschar *));

argv[0] = US"/bin/sh";
argv[1] = US"-c";

/* We have to take special action to handle the special "variable" called
$pipe_addresses, which is not recognized by the normal expansion function. */

DEBUG(D_transport)
  debug_printf("shell pipe command before expansion:\n  %s\n", cmd);

if (expand_arguments)
  {
  uschar *s = cmd;
  uschar *p = Ustrstr(cmd, "pipe_addresses");

  if (p != NULL && (
         (p > cmd && p[-1] == '$') ||
         (p > cmd + 1 && p[-2] == '$' && p[-1] == '{' && p[14] == '}')))
    {
    address_item *ad;
    uschar *q = p + 14;
    int size = Ustrlen(cmd) + 64;
    int offset;

    if (p[-1] == '{') { q++; p--; }

    s = store_get(size);
    offset = p - cmd - 1;
    Ustrncpy(s, cmd, offset);

    for (ad = addr; ad != NULL; ad = ad->next)
      {
      if (ad != addr) string_cat(s, &size, &offset, US" ", 1);
      string_cat(s, &size, &offset, ad->address, Ustrlen(ad->address));
      }

    string_cat(s, &size, &offset, q, Ustrlen(q));
    s[offset] = 0;
    }

  /* Allow $recipients in the expansion iff it comes from a system filter */

  enable_dollar_recipients = addr != NULL &&
    addr->parent != NULL &&
    Ustrcmp(addr->parent->address, "system-filter") == 0;
  argv[2] = expand_string(s);
  enable_dollar_recipients = FALSE;

  if (argv[2] == NULL)
    {
    addr->transport_return = search_find_defer? DEFER : expand_fail;
    addr->message = string_sprintf("Expansion of command \"%s\" "
      "in %s transport failed: %s",
      cmd, tname, expand_string_message);
    return FALSE;
    }

  DEBUG(D_transport)
    debug_printf("shell pipe command after expansion:\n  %s\n", argv[2]);
  }
else argv[2] = cmd;

argv[3] = (uschar *)0;
return TRUE;
}
Esempio n. 3
0
File: em_log.c Progetto: fanf2/exim
void read_log(void)
{
struct stat statdata;
uschar buffer[log_buffer_len];

/* If log is not yet open, skip all of this. */

if (LOG != NULL)
  {
  fseek(LOG, log_position, SEEK_SET);

  while (Ufgets(buffer, log_buffer_len, LOG) != NULL)
    {
    uschar *id;
    uschar *p = buffer;
    void *reset_point;
    int length = Ustrlen(buffer);
    int i;

    /* Skip totally blank lines (paranoia: there shouldn't be any) */

    while (*p == ' ' || *p == '\t') p++;
    if (*p == '\n') continue;

    /* We should now have a complete log entry in the buffer; check
    it for various regular expression matches and take appropriate
    action. Get the current store point so we can reset to it. */

    reset_point = store_get(0);

    /* First, update any stripchart data values, noting that the zeroth
    stripchart is the queue length, which is handled elsewhere, and the
    1st may the a size monitor. */

    for (i = stripchart_varstart; i < stripchart_number; i++)
      {
      if (pcre_exec(stripchart_regex[i], NULL, CS buffer, length, 0, PCRE_EOPT,
            NULL, 0) >= 0)
        stripchart_total[i]++;
      }

    /* Munge the log entry and display shortened form on one line.
    We omit the date and show only the time. Remove any time zone offset.
    Take note of the presence of [pid]. */

    if (pcre_exec(yyyymmdd_regex,NULL,CS buffer,length,0,PCRE_EOPT,NULL,0) >= 0)
      {
      int pidlength = 0;
      if ((buffer[20] == '+' || buffer[20] == '-') &&
          isdigit(buffer[21]) && buffer[25] == ' ')
        memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1);
      if (buffer[20] == '[')
        {
        while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL);
        }
      id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH);
      show_log("%s", buffer+11);
      }
    else
      {
      id = US"";
      show_log("%s", buffer);
      }

    /* Deal with frozen and unfrozen messages */

    if (strstric(buffer, US"frozen", FALSE) != NULL)
      {
      queue_item *qq = find_queue(id, queue_noop, 0);
      if (qq != NULL)
        {
        if (strstric(buffer, US"unfrozen", FALSE) != NULL)
          qq->frozen = FALSE;
        else qq->frozen = TRUE;
        }
      }

    /* Notice defer messages, and add the destination if it
    isn't already on the list for this message, with a pointer
    to the parent if we can. */

    if ((p = Ustrstr(buffer, "==")) != NULL)
      {
      queue_item *qq = find_queue(id, queue_noop, 0);
      if (qq != NULL)
        {
        dest_item *d;
        uschar *q, *r;
        p += 2;
        while (isspace(*p)) p++;
        q = p;
        while (*p != 0 && !isspace(*p))
          {
          if (*p++ != '\"') continue;
          while (*p != 0)
            {
            if (*p == '\\') p += 2;
              else if (*p++ == '\"') break;
            }
          }
        *p++ = 0;
        if ((r = strstric(q, qualify_domain, FALSE)) != NULL &&
          *(--r) == '@') *r = 0;

        /* If we already have this destination, as tested case-insensitively,
        do not add it to the destinations list. */

        d = find_dest(qq, q, dest_add, TRUE);

        if (d->parent == NULL)
          {
          while (isspace(*p)) p++;
          if (*p == '<')
            {
            dest_item *dd;
            q = ++p;
            while (*p != 0 && *p != '>') p++;
            *p = 0;
            if ((p = strstric(q, qualify_domain, FALSE)) != NULL &&
              *(--p) == '@') *p = 0;
            dd = find_dest(qq, q, dest_noop, FALSE);
            if (dd != NULL && dd != d) d->parent = dd;
            }
          }
        }
      }

    store_reset(reset_point);
    }
  }


/* We have to detect when the log file is changed, and switch to the new file.
In practice, for non-datestamped files, this means that some deliveries might
go unrecorded, since they'll be written to the old file, but this usually
happens in the middle of the night, and I don't think the hassle of keeping
track of two log files is worth it.

First we check the datestamped name of the log file if necessary; if it is
different to the file we currently have open, go for the new file. As happens
in Exim itself, we leave in the following inode check, even when datestamping
because it does no harm and will cope should a file actually be renamed for
some reason.

The test for a changed log file is to look up the inode of the file by name and
compare it with the saved inode of the file we currently are processing. This
accords with the usual interpretation of POSIX and other Unix specs that imply
"one file, one inode". However, it appears that on some Digital systems, if an
open file is unlinked, a new file may be created with the same inode while the
old file remains in existence. This can happen if the old log file is renamed,
processed in some way, and then deleted. To work round this, also test for a
link count of zero on the currently open file. */

if (log_datestamping)
  {
  uschar log_file_wanted[256];
  string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file);
  if (Ustrcmp(log_file_wanted, log_file_open) != 0)
    {
    if (LOG != NULL)
      {
      fclose(LOG);
      LOG = NULL;
      }
    Ustrcpy(log_file_open, log_file_wanted);
    }
  }

if (LOG == NULL ||
    (fstat(fileno(LOG), &statdata) == 0 && statdata.st_nlink == 0) ||
    (Ustat(log_file, &statdata) == 0 && log_inode != statdata.st_ino))
  {
  FILE *TEST;

  /* Experiment shows that sometimes you can't immediately open
  the new log file - presumably immediately after the old one
  is renamed and before the new one exists. Therefore do a
  trial open first to be sure. */

  if ((TEST = fopen(CS log_file_open, "r")) != NULL)
    {
    if (LOG != NULL) fclose(LOG);
    LOG = TEST;
    fstat(fileno(LOG), &statdata);
    log_inode = statdata.st_ino;
    }
  }

/* Save the position we have got to in the log. */

if (LOG != NULL) log_position = ftell(LOG);
}
Esempio n. 4
0
File: rda.c Progetto: 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;
}