/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Copyright (c) University of Cambridge 1995 - 1999 */
/* See the file NOTICE for conditions of use and distribution. */

/* Functions for sending messages to sender or to mailmaster. */


#include "exim.h"



/*************************************************
*          Send message to sender                *
*************************************************/

/* This function is called when errors are detected during the receipt of a
message. Delivery failures are handled separately in deliver.c.

If there is a valid sender_address, and the failing message is not a local
error message, then this function calls moan_send_message to send a message to
that person. If the sender's address is null, then an error has occurred with a
message that was generated by a mailer daemon. All we can do is to write
information to log files. The same action is taken if local_error_message is
set - this can happen for non null-senders in certain configurations where exim
doesn't run setuid root.

Arguments:
  ident         identifies the particular error
  eblock        chain of error_blocks containing data about the error
  headers       message's headers (chain)
  message_file  a FILE where the body of the message can be read
  check_sender  if TRUE, read the first line of the file for a possible
                  "From " sender (if a trusted caller)

Returns:        FALSE if there is no sender_address to send to;
                else the return from moan_send_message()
*/

BOOL
moan_to_sender(int ident, error_block *eblock, header_line *headers,
  FILE *message_file, BOOL check_sender)
{
char *msg = "Error while reading message with no usable sender address";

if (message_reference != NULL)
  msg = string_sprintf("%s (R=%s)", msg, message_reference);

/* Find the sender from a From line if permitted and possible */

if (check_sender && message_file != NULL && trusted_caller &&
    fgets(big_buffer, BIG_BUFFER_SIZE, message_file) != NULL)
  {
  char *new_sender = NULL;
  if (regex_match_and_setup(regex_From, big_buffer, 0, -1))
    new_sender = expand_string(uucp_from_sender);
  if (new_sender != NULL) sender_address = new_sender;
    else rewind(message_file);
  }

/* If viable sender address, send a message */

if (sender_address != NULL && sender_address[0] != 0 && !local_error_message)
  return moan_send_message(sender_address, ident, eblock, headers,
    message_file);

/* Otherwise, we can only log */

switch(ident)
  {
  case ERRMESS_BADARGADDRESS:
  case ERRMESS_BADNOADDRESS:
  case ERRMESS_BADADDRESS:
  log_write(0, LOG_MAIN, "%s: at least one malformed recipient address: "
    "%s - %s", msg, eblock->text1, eblock->text2);
  break;

  case ERRMESS_IGADDRESS:
  case ERRMESS_NOADDRESS:
  log_write(0, LOG_MAIN, "%s: no recipient addresses", msg);
  break;

  case ERRMESS_SPOOLWRITE:
  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s: write to spool failed", msg);
  break;

  case ERRMESS_MESSAGEREAD:
  log_write(0, LOG_MAIN, "%s: read from input file failed", msg);
  break;

  case ERRMESS_VLONGHEADER:
  log_write(0, LOG_MAIN, "%s: excessively long header line read", msg);
  break;

  case ERRMESS_TOOBIG:
  log_write(0, LOG_MAIN, "%s: message too big (limit set to %d%s)", msg,
    message_size_limit,
    message_size_limit_count_recipients?
      ", message_size_limit_count_recipients set" : "");
  break;

  case ERRMESS_TOOMANYRECIP:
  log_write(0, LOG_MAIN, "%s: too many recipients (max set to %d)", msg,
    recipients_max);
  break;

  default:
  log_write(0, LOG_MAIN|LOG_PANIC, "%s: unknown error number %d", msg,
    ident);
  break;
  }

return FALSE;
}



/*************************************************
*              Send error message                *
*************************************************/

/* This function sends an error message by opening a pipe to a new process
running Exim, and writing a message to it using the "-t" option. This is not
used for delivery failures, which have their own code for handing failed
addresses.

Arguments:
  recipient      addressee for the message
  ident          identifies the type of error
  eblock         chain of error_blocks containing data about the error
  headers        the message's headers
  message_file   FILE containing the body of the message

Returns:         TRUE if message successfully sent
*/

BOOL
moan_send_message(char *recipient, int ident, error_block *eblock,
  header_line *headers, FILE *message_file)
{
int written = 0;
int fd;
int status;
int count = 0;
int size_limit = return_size_limit;
FILE *f;
int pid = child_open(mailer_argv, NULL, 077, NULL, NULL, &fd,
    (debug_file != NULL)? fileno(debug_file) : -1, NULL, FALSE);

/* Creation of child failed */

if (pid < 0)
  {
  DEBUG(2) debug_printf("Failed to create child to send message\n");
  return FALSE;
  }
else DEBUG(9) debug_printf("Child process %d for sending message\n", pid);

/* Creation of child succeeded */

f = fdopen(fd, "w");
if (errors_reply_to != NULL) fprintf(f, "Reply-to: %s\n", errors_reply_to);
fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
  qualify_domain_sender);
fprintf(f, "To: %s\n", recipient);

switch(ident)
  {
  case ERRMESS_BADARGADDRESS:
  fprintf(f,
  "Subject: Mail failure - malformed recipient address\n\n");
  fprintf(f,
  "A message that you sent contained a recipient address that was incorrectly\n"
  "constructed:\n\n");
  fprintf(f, "  %s  %s\n", eblock->text1, eblock->text2);
  count = (int)strlen(eblock->text1);
  if (count > 0 && eblock->text1[count-1] == '.')
    fprintf(f,
    "\nRecipient addresses must not end with a '.' character.\n");
  fprintf(f,
  "\nThe message has not been delivered to any recipients.\n");
  break;

  case ERRMESS_BADNOADDRESS:
  case ERRMESS_BADADDRESS:
  fprintf(f,
  "Subject: Mail failure - malformed recipient address\n\n");
  fprintf(f,
  "A message that you sent contained one or more recipient addresses that were\n"
  "incorrectly constructed:\n\n");

  while (eblock != NULL)
    {
    fprintf(f, "  %s: %s\n", eblock->text1, eblock->text2);
    count++;
    eblock = eblock->next;
    }

  fprintf(f, (count == 1)? "\nThis address has been ignored. " :
    "\nThese addresses have been ignored. ");

  fprintf(f, (ident == ERRMESS_BADADDRESS)?
  "The other addresses in the message were\n"
  "syntactically valid and have been passed on for an attempt at delivery.\n" :

  "There were no other addresses in your\n"
  "message, and so no attempt at delivery was possible.\n");
  break;

  case ERRMESS_IGADDRESS:
  fprintf(f, "Subject: Mail failure - no recipient addresses\n\n");
  fprintf(f,
  "A message that you sent using the -t command line option contained no\n"
  "addresses that were not also on the command line, and were therefore\n"
  "suppressed. This left no recipient addresses, and so no delivery could\n"
  "be attempted.\n");
  break;

  case ERRMESS_NOADDRESS:
  fprintf(f, "Subject: Mail failure - no recipient addresses\n\n");
  fprintf(f,
  "A message that you sent contained no recipient addresses, and so no\n"
  "delivery could be attempted.\n");
  break;

  case ERRMESS_SPOOLWRITE:
  fprintf(f, "Subject: Mail failure - system failure\n\n");
  fprintf(f,
  "A system failure (spool write failure) was encountered while processing\n"
  "a message that you sent, so it has not been possible to deliver it.\n");
  break;

  case ERRMESS_MESSAGEREAD:
  fprintf(f, "Subject: Mail failure - system failure\n\n");
  fprintf(f,
  "A system failure (input read failure) was encountered while processing\n"
  "a message that you sent, so it has not been possible to deliver it.\n");
  break;

  case ERRMESS_VLONGHEADER:
  fprintf(f, "Subject: Mail failure - overlong header\n\n");
  fprintf(f,
  "A message that you sent contained a header line that was excessively\n"
  "long and could not be handled by the mail transmission software. The\n"
  "message has not been delivered to any recipients.\n");
  break;

  case ERRMESS_TOOBIG:
  fprintf(f, "Subject: Mail failure - message too big\n\n");
  fprintf(f,
  "A message that you sent was longer than the maximum size allowed on this\n"
  "system. It was not delivered to any recipients.\n");
  break;

  case ERRMESS_TOOMANYRECIP:
  fprintf(f, "Subject: Mail failure - too many recipients\n\n");
  fprintf(f,
  "A message that you sent contained more recipients than allowed on this\n"
  "system. It was not delivered to any recipients.\n");
  break;

  default:
  fprintf(f, "Subject: Mail failure\n\n");
  fprintf(f,
  "A message that you sent has caused the error routine to be entered with\n"
  "an unknown error number (%d).\n", ident);
  break;
  }

/* Now copy the message - headers then the rest of the input if
available, up to the configured limit. */

if (size_limit == 0 || size_limit > message_size_limit)
  size_limit = message_size_limit;

if (size_limit > 0)
  {
  int x = size_limit;
  char *k = "";
  if ((x & 1023) == 0)
    {
    k = "K";
    x >>= 10;
    }
  fprintf(f, "\n"
  "------ This is a copy of your message, including all the headers.\n"
  "------ No more than %d%s characters of the body are included.\n\n", x, k);
  }
else fprintf(f, "\n"
  "------ This is a copy of your message, including all the headers. ------"
  "\n\n");

while (headers != NULL)
  {
  fprintf(f, "%s", headers->text);
  headers = headers->next;
  }

if (ident != ERRMESS_VLONGHEADER) fputc('\n', f);

/* After early detection of an error, the message file may be STDIN,
in which case we might have to terminate on a line containing just "."
as well as on EOF. */

if (message_file != NULL)
  {
  int ch;
  int state = 1;
  BOOL enddot = dot_ends && message_file == stdin;
  while ((ch = fgetc(message_file)) != EOF)
    {
    fputc(ch, f);
    if (size_limit > 0 && ++written > size_limit) break;
    if (enddot)
      {
      if (state == 0) { if (ch == '\n') state = 1; }
      else if (state == 1)
        { if (ch == '.') state = 2; else if (ch != '\n') state = 0; }
      else
        { if (ch == '\n') break; else state = 0; }
      }
    }
  }

/* Close the file, which should send an EOF to the child process
that is receiving the message. Wait for it to finish, without a timeout. */

fclose(f);
status = child_close(pid, 0);  /* Waits for child to close */
if (status != 0)
  {
  char *msg = "Child mail process returned status";
  if (status == -257)
    log_write(0, LOG_MAIN, "%s %d: errno=%d: %s", msg, status, errno,
      strerror(errno));
  else
    log_write(0, LOG_MAIN, "%s %d", msg, status);
  return FALSE;
  }

return TRUE;
}



/*************************************************
*            Send message to someone             *
*************************************************/

/* This is called when exim is configured to tell someone (often the
mailmaster) about some incident.

Arguments:
  who           address to send mail to
  addr          chain of deferred addresses whose details are to be included
  subject       subject text for the message
  format        a printf() format
  ...           arguments for the format

Returns:        nothing
*/

void
moan_tell_someone(char *who, address_item *addr, char *subject,
  char *format, ...)
{
FILE *f;
va_list ap;
int fd;
int pid = child_open(mailer_argv, NULL, 077, NULL, NULL, &fd, -1, NULL, FALSE);

if (pid < 0)
  {
  DEBUG(2) debug_printf("Failed to create child to send message\n");
  return;
  }

f = fdopen(fd, "w");
fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
  qualify_domain_sender);
fprintf(f, "To: %s\n", who);
fprintf(f, "Subject: %s\n\n", subject);
va_start(ap, format);
vfprintf(f, format, ap);
va_end(ap);

if (addr != NULL)
  {
  fprintf(f, "\nThe following address(es) have yet to be delivered:\n");
  for (; addr != NULL; addr = addr->next)
    {
    char *parent = (addr->parent == NULL)? NULL : addr->parent->orig;
    fprintf(f, "  %s%s%s%s: %s%s%s\n",
      addr->orig,
      (parent == NULL)? "" : " <",
      (parent == NULL)? "" : parent,
      (parent == NULL)? "" : ">",
      (addr->basic_errno <= 0)? "" : strerror(addr->basic_errno),
      (addr->basic_errno <= 0 || addr->message == NULL)? "" : ": ",
      (addr->message != NULL)? addr->message :
        (addr->basic_errno <= 0)? "unknown error" : "");
    }
  }

fclose(f);
child_close(pid, 0);  /* Waits for child to close; no timeout */
}



/*************************************************
*            Handle SMTP batch error             *
*************************************************/

/* This is called when exim is configured to tell somebody when something
goes wrong in batched (-bS) SMTP input. If the caller is not trusted, tell the
caller. Otherwise tell the mailmaster.

Arguments:
  cmd_buffer   the command causing the error, or NULL
  format       a printf() format
  ...          arguments for the format

Returns:       nothing
*/

void
moan_smtp_batch(char *cmd_buffer, char *format, ...)
{
FILE *f;
va_list ap;
int fd;
int pid = child_open(mailer_argv, NULL, 077, NULL, NULL, &fd, -1, NULL, FALSE);
char *recipient = trusted_caller? errors_address : real_sender_address;

DEBUG(2) debug_printf("Handling error in batched SMTP input\n");

if (pid < 0)
  {
  DEBUG(2) debug_printf("Failed to create child to send message\n");
  log_write(0, LOG_MAIN, "Failed to create child process to send error message");
  return;
  }

f = fdopen(fd, "w");
fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
  qualify_domain_sender);
fprintf(f, "To: %s\n", recipient);
fprintf(f, "Subject: Error in batched SMTP input\n\n");
fprintf(f,
  "While reading a batch of messages using the -bS option, the\n"
  "following error was detected:\n\n");
if (cmd_buffer != NULL) fprintf(f, "%s\n", cmd_buffer);
va_start(ap, format);
vfprintf(f, format, ap);
va_end(ap);

fprintf(f, "\n\nThe message that contained the error was skipped.\n");
if (sender_address != NULL)
  fprintf(f, "Its sender was: <%s>\n", sender_address);
if (recipients_count > 0)
  {
  int i;
  fprintf(f, "The recipients were:\n");
  for (i = 0; i < recipients_count && i < 20; i++)
    fprintf(f, "  %s\n", recipients_list[i].address);
  if (i >= 20) fprintf(f, "  ...\n");
  }
if (header_list != NULL)
  {
  header_line *h;
  fprintf(f, "The following headers were read:\n\n");
  for (h = header_list; h != NULL; h = h->next)
    if (h->text != NULL) fprintf(f, "%s", h->text);
  }

fclose(f);
child_close(pid, 0);  /* Waits for child to close; no timeout */
}




/*************************************************
*         Check for error copies                 *
*************************************************/

/* This function is passed the recipient of an error message, and must check
the error_copies string to see whether there is an additional recipient list to
which errors for this recipient must be bcc'd. The incoming recipient is always
fully qualified.

Argument:   recipient address
Returns:    additional recipient list or NULL
*/

char *
moan_check_errorcopy(char *recipient)
{
char *item, *localpart, *domain;
char *listptr = errors_copy;
char *yield = NULL;
char buffer[256];
int llen;

if (errors_copy == NULL) return NULL;

/* Set up pointer to the local part and domain, and compute the
length of the local part. */

localpart = recipient;
domain = strchr(recipient, '@');
if (domain == NULL) return NULL;  /* should not occur, but avoid crash */
llen = domain++ - recipient;

/* Scan through the configured items */

for (item = string_nextinlist(&listptr, ':', buffer, sizeof(buffer));
     item != NULL;
     item = string_nextinlist(&listptr, ':', buffer, sizeof(buffer)))
  {
  BOOL match = FALSE;
  char *newaddress;

  /* Move over the first address item to the first non-quoted space */

  for (newaddress = item; *newaddress != 0 && !isspace(*newaddress);
       newaddress++)
    {
    if (*newaddress == '\"')
      {
      while (*(++newaddress) != 0)
        {
        if (*newaddress == '\\' && newaddress[1] != 0) newaddress++;
          else if (*newaddress == '\"') { newaddress++; break; }
        }
      }
    }

  /* If no new address found, just skip this item. Otherwise, terminate the
  test address - the yield of string_nextinlist() is in the given buffer, so
  it is OK to do this. Then move on to the start of the new address (again,
  skip if not there). */

  if (*newaddress == 0) continue;
  *newaddress++ = 0;
  while (isspace(*newaddress)) newaddress++;
  if (*newaddress == 0) continue;

  /* We now have an item to match as a string in item, and the additional
  address in newaddress. First handle a regular expression, which must match
  the entire incoming address. As this is a rare occurrence, don't make
  any effort to cache compiled expressions. */

  if (item[0] == '^')
    {
    re_block *reblock = NULL;
    match = match_check_string(recipient, item, &reblock, 0, TRUE, NULL);
    }

  /* If not a regular expression, either part may begin with an
  asterisk, and both parts must match. There must be an '@' in the
  address. */

  else
    {
    char *slocalpart = item;
    char *sdomain = strchr(item, '@');
    int sllen;

    /* Set up whole item expansion. */

    expand_nmax = 0;
    expand_nstring[0] = recipient;
    expand_nlength[0] = (int)strlen(recipient);

    /* No @ => complain and ignore */

    if (sdomain == NULL)
      {
      log_write(0, LOG_MAIN|LOG_PANIC, "domain missing in errors_copy entry: %s",
        item);
      continue;
      }
    else
      {
      sllen = sdomain - item;
      sdomain += 1;
      }

    /* Check the local part */

    if (slocalpart[0] == '*')
      {
      int cllen = sllen - 1;
      match = llen >= cllen &&
        strncmpic(localpart + llen - cllen, slocalpart + 1, cllen) == 0;
      if (match)
        {
        expand_nstring[++expand_nmax] = localpart;
        expand_nlength[expand_nmax] = llen - cllen;
        }
      }
    else match =
      llen == sllen && strncmpic(localpart, slocalpart, llen) == 0;

    /* If the local part matched, check the domain using the generalized
    function, which supports file lookups. */

    if (match) match =
      match_check_string(domain, sdomain, NULL, ++expand_nmax, TRUE, NULL);
    }

  /* If we have found a match, expand the new address string and
  return it. During expansion, make local part and domain available
  for insertion. This requires a copy to be made; we can't just temporarily
  terminate it, as the whole address is required for $0. */

  if (match)
    {
    char temp[256];
    strncpy(temp, localpart, llen);
    temp[llen] = 0;
    deliver_localpart = temp;
    deliver_domain = domain;
    yield = expand_string_copy(newaddress);
    deliver_domain = deliver_localpart = NULL;
    if (yield == NULL)
      log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand %s when processing "
        "errors_copy: %s", newaddress, expand_string_message);
    break;
    }
  }

DEBUG(5) debug_printf("errors_copy check returned %s\n",
  (yield == NULL)? "NULL" : yield);

expand_nmax = -1;
return yield;
}



/************************************************
*        Handle skipped syntax errors           *
************************************************/

/* This function is called by aliasfile and forwardfile when they have skipped
over one or more syntax errors in the list of addresses they generate. If there
is an address to mail to, send a message, and always write the information to
the log.

Arguments:
  dname             the director name
  filename          the file that was read
  eblock            chain of error blocks
  syntax_errors_to  address to send mail to, or NULL
  some              TRUE if some addresses were generated; FALSE if none were
  custom            custom message text

Returns:            FALSE if string expansion failed; TRUE otherwise
*/

BOOL
moan_skipped_syntax_errors(char *dname, char *filename, error_block *eblock,
  char *syntax_errors_to, BOOL some, char *custom)
{
int pid, fd;
char *s, *t;
FILE *f;
error_block *e;

for (e = eblock; e != NULL; e = e->next)
  {
  if (e->text2 != NULL)
    log_write(0, LOG_MAIN, "%s director: skipped syntax error in %s: "
      "%s in \"%s\"", dname, filename, e->text1, e->text2);
  else
    log_write(0, LOG_MAIN, "%s director: skipped syntax error in %s: "
      "%s", dname, filename, e->text1);
  }

if (syntax_errors_to == NULL) return TRUE;

s = expand_string(syntax_errors_to);
if (s == NULL)
  {
  log_write(0, LOG_MAIN, "%s director failed to expand %s: %s", dname,
    syntax_errors_to, expand_string_message);
  return FALSE;
  }

/* If we can't create a process to send the message, just forget about
it. */

pid = child_open(mailer_argv, NULL, 077, NULL, NULL, &fd,
  (debug_file != NULL)? fileno(debug_file) : -1, NULL, FALSE);

if (pid < 0)
  {
  DEBUG(2) debug_printf("Failed to create child to send message\n");
  return TRUE;
  }

f = fdopen(fd, "w");
fprintf(f, "From: Mail Delivery System <Mailer-Daemon@%s>\n",
  qualify_domain_sender);
fprintf(f, "To: %s\n", s);
fprintf(f, "Subject: syntax error(s) in address expansion\n\n");

if (custom != NULL)
  {
  t = expand_string(custom);
  if (t == NULL)
    {
    log_write(0, LOG_MAIN, "%s director failed to expand %s: %s", dname,
      custom, expand_string_message);
    return FALSE;
    }
  fprintf(f, "%s\n\n", t);
  }

fprintf(f, "The %s director discovered the following syntax error(s) in the "
  "file %s:\n\n", dname, filename);

for (e = eblock; e != NULL; e = e->next)
  {
  fprintf(f, "  %s", e->text1);
  if (e->text2 != NULL)
    fprintf(f, " in the address\n  \"%s\"", e->text2);
  fprintf(f, "\n\n");
  }

if (some)
  fprintf(f, "Other addresses were processed normally.\n");
else
  fprintf(f, "No valid addresses were generated.\n");

fclose(f);
child_close(pid, 0);  /* Waits for child to close; no timeout */

return TRUE;
}

/* End of moan.c */
