/*************************************************
*                 GreyMail                       *
*************************************************/

/* This program is a filter to sit between the Coloured Book
software and smail. It is used both for incoming and outgoing
messages.

For incoming messages, it operates between the  ybtsd daemon
and smail, and has two functions:

  (1) To obtain the sender of the message from the From: or
  Sender: line within it, and pass that on to /usr/lib/sendmail
  via the "-f" parameter in its command line.

  (2) To turn addresses round into world order (if so configured).

The input data provided by the ybtsd daemon is a JNT message file,
without the JNT header (the destination addresses) on the front.
These are passed over in the argument list. So the file starts with
the rest of the header lines.

For outgoing messages, the only function is to turn addresses
round into UK order (if so configured).

The program determines that it is in output mode by inspecting the
first argument for the string "OUTPUT-MODE". As it is called
from ybtsd in input mode, we have to accept the arguments as they
are.

Written by Philip Hazel (ph10@cus.cam.ac.uk) January 1992.
----------------------------------------------------------------- */


#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>

#define FLIP                    /* Define if address flipping wanted */

#define buffer_size  32*1024

#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

#define FALSE        0
#define TRUE         1


static int child_ended = FALSE;
static int child_pid;
static char *sender = "-froot";         /* default */
static char argsender[256];


#define skipEOL() \
  { while (ptr < endptr && *ptr != '\r' && *ptr != '\n') ptr++; \
    if (*ptr == '\r' && ptr[1] == '\n') ptr += 2; else ptr++; }



/*************************************************
*              Handle end of child process       *
*************************************************/

handle_child_end(sig)
int sig;
{
union wait status;
if (wait3(&status, WNOHANG, 0) != child_pid) return;
child_ended = TRUE;
}



/*************************************************
*             Invert an address                  *
*************************************************/

/* This is done in place */

#ifdef FLIP
static void flip(s, count)
register char *s;
int count;
{
char yield[256];
char *p = yield;
int i = count;
int j = count;
int k;

while (j > 0)
  {
  while (i > 0 && s[--i] != '.');
  if (i == 0) i--;
  for (k = i+1; k < j; k++) *p++ = s[k];
  *p++ = '.';
  j = i;
  }

*(--p) = 0;
strncpy(s, yield,count);
}
#endif


/*************************************************
*          Skipping in address routine           *
*************************************************/

/* This function skips to one of a given number of characters,
dealing with quoted strings and comment (bracketed) strings
on the way. */

char *skipto(ptr, endptr, terms)
char *ptr;
char *endptr;
char *terms;
{
while (ptr < endptr)
  {
  int c = *ptr;
  if (strchr(terms, c) != NULL) break;

  if (c == '\"')
    {
    ptr ++;
    while (ptr < endptr)
      {
      if (*ptr == '\"') break;
      if (*ptr == '\\') ptr++;
      ptr++;
      }
    }

  else if (c == '(')
    {
    int nest = 1;
    ptr ++;
    while (ptr < endptr)
      {
      if (*ptr == ')' && --nest <= 0) break;
      if (*ptr == '(') nest++;
      if (*ptr == '\\') ptr++;
      ptr++;
      }
    }

  ptr++;
  }

return ptr;
}



/*************************************************
*             Find next address in header        *
*************************************************/

static char *next_address(ptr, endptr, user, site, end)
char *ptr;
char *endptr;
char **user;
char **site;
char **end;
{

/* Skip over white space and continuation line breaks. If the
end of the list is reached, return NULL in user. */

for (;;)
  {
  while (ptr < endptr && (*ptr == ' ' || *ptr == '\t')) ptr++;
  if (ptr >= endptr) { *user = NULL; return ptr; }
  if (*ptr == '\r' || *ptr == '\n')
    {
    skipEOL();
    if (*ptr != ' ' && *ptr != '\t')
      {
      *user = NULL;
      return ptr;
      }
    }
  else break;
  }

/* There's something more in the list. Return NULL in site if
there isn't a remote address. */


*user = ptr;  /* Putative user start */

ptr = skipto(ptr, endptr, "@<,\n\r");
*site = NULL;

if (*ptr == '@')     /* No "<" expected */
  {
  ptr++;
  while (*ptr == ' ' || *ptr == '\t') ptr++;
  *site = ptr;
  ptr = skipto(ptr, endptr, ",\n\r");
  *end = ptr;
  }

else if (*ptr == '<')
  {
  ptr++;
  while (*ptr == ' ' || *ptr == '\t') ptr++;
  *user = ptr;
  ptr = skipto(ptr, endptr, "@>");
  if (*ptr == '@')
    {
    ptr++;
    while (*ptr == ' ' || *ptr == '\t') ptr++;
    *site = ptr;
    ptr = skipto(ptr, endptr, ">");
    *end = ptr++;
    ptr = skipto(ptr, endptr, ",\n\r");
    }
  }


if (*site != NULL)
  {

  /* If site ended with text in brackets, move back the pointer */

  char *p = *end - 1;
  while (p > *site && (*p == ' ' || *p == '\t')) p--;
  if (*p == ')')
    {
    int nest = 1;
    p--;
    while (p > *site)
      {
      if (p[-1] == '\\') p--;
      if (*p == '(' && --nest <= 0) { p--; break; }
      if (*p == ')') nest++;
      p--;
      }

    while (p > *site && (*p == ' ' || *p == '\t')) p--;
    *end = p + 1;
    }

  /* Remove spaces to cope with ancient mailers that put
  them there - some UA's (e.g. Elm) don't cope with them. */

  p = *user;
  while (p < *end)
    {
    p = skipto(p, *end, " \t");
    if (p < *end && p[-1] != ')')
      {
      char *q = p-1;
      while (q >= *user)
        {
        q[1] = q[0];
        q--;
        }
      q[1] = ' ';
      *user += 1;
      if (p > *site) *site += 1;
      }
    p++;
    }
  }

if (ptr < endptr && *ptr == ',') ptr++;
return ptr;
}


/*************************************************
*             Skip header field                  *
*************************************************/

static char *skip_field(ptr, endptr)
char *ptr;
char *endptr;
{
skipEOL();
while (ptr < endptr && (*ptr == ' ' || *ptr == '\t')) skipEOL();
return ptr;
}


/*************************************************
*           Process an address field             *
*************************************************/

/* This is where we can turn round addresses. */

char *address_field(ptr, endptr)
char *ptr;
char *endptr;
{
/* Deal with address flipping */

#ifdef FLIP
char *user, *site, *end;
while (*ptr++ != ':');

for (;;)
  {
  ptr = next_address(ptr, endptr, &user, &site, &end);
  if (user == NULL) break;
  if (site != NULL) flip(site, end-site);
  }

return ptr;

/* If not turning addresses, just skip the field */

#else
return skip_field(ptr, endptr);
#endif
}


/*************************************************
*         Process From: or Sender:               *
*************************************************/

static char *from_field(ptr, endptr)
char *ptr;
char *endptr;
{
char *user, *site, *end;
while (*ptr++ != ':');
ptr = next_address(ptr, endptr, &user, &site, &end);

if (site != NULL)
  {
#ifdef FLIP
  flip(site, end-site);
#endif

  /* There should always be at least one user name, but
  just in case, program defensively. */

  if (user != NULL)
    {
    int i = 2;
    int j = 2;

    strcpy(argsender, "-f");
    strncat(argsender, user, end - user);
    sender = argsender;

    /* Strip out any bracketted comments */

    for (;;)
      {
      while (sender[i] == '(')
        {
        int count = 1;
        while (sender[++i])
          {
          if (sender[i] == '(') count++;
          else if (sender[i] == ')') count--;
          if (count <= 0)
            {
            i++;
            break;
            }
          }
        }
      if ((sender[j++] = sender[i++]) == 0) break;
      }
    sender[j] = 0;
    }
  }

skipEOL();
return ptr;
}





/*************************************************
*            Entry point and main program        *
*************************************************/

int main(argc, argv)
int argc;
char **argv;
{
char *buffer = (char *)malloc(buffer_size);
char *ptr, *endptr;
char *next_prog = "/usr/local/smail/bin/smail";

int output_mode = FALSE;
int sender_found = FALSE;
int from_seen = FALSE;
int count;
int fd_input, fd_output;
int fd_pipe[2];

/* Check the first argument and set the mode. Re-arrange
arguments if necessary, taking care to copy the final
null pointer. */

if (strcmp(argv[1], "OUTPUT-MODE") == 0)
  {
  int i;
  output_mode = TRUE;
  for (i = 2; i <= argc; i++) argv[i-1] = argv[i];
  argc--;
  next_prog = "/usr/sunlink/cba/hhsend";
  }


/* Duplicate stdin to make a new fd on which to read the data */

fd_input = dup(0);

/* Set up a pipe for transferring the data */

if (pipe(fd_pipe))
  {
  printf("Greymail: pipe creation failed\n");
  return EXIT_FAILURE;
  }

/* Close stdin, dup the reading end of the new pipe onto it, and
then close the original pipe reading descriptor. */

close(0);
dup2(fd_pipe[0], 0);
close(fd_pipe[0]);

/* Read the first part of the message into the buffer */

count = read(fd_input, buffer, buffer_size);

/* Search for a From: or Sender: field, optionally inverting
any other address fields on the way */

ptr = buffer;
endptr = ptr + count;

/* Process message header fields, until a blank line is reached,
after either "From:" or "Sender:".  */

while (ptr < endptr)
  {
  if ((*ptr == '\r' || *ptr == '\n') && from_seen) break;

  if (strncasecmp(ptr, "From:", 5) == 0)
    {
    if (!sender_found) ptr = from_field(ptr, endptr);
      else ptr = address_field(ptr, endptr);
    from_seen = TRUE;
    }

  else if (strncasecmp(ptr, "Sender:", 7) == 0)
    {
    ptr = from_field(ptr, endptr);
    sender_found = from_seen = TRUE;
    }

  else if (strncasecmp(ptr, "To:",               3) == 0 ||
           strncasecmp(ptr, "Resent-To:",       10) == 0 ||
           strncasecmp(ptr, "Apparently-To:",   14) == 0 ||
           strncasecmp(ptr, "Apparently-From:", 16) == 0 ||
           strncasecmp(ptr, "cc:",               3) == 0 ||
           strncasecmp(ptr, "Resent-cc:",       10) == 0 ||
           strncasecmp(ptr, "bcc:",              4) == 0 ||
           strncasecmp(ptr, "Resent-bcc:",      11) == 0 ||
           strncasecmp(ptr, "Return-Path:",     12) == 0 ||
           strncasecmp(ptr, "Reply-To:",         9) == 0)
     ptr = address_field(ptr, endptr);

  else ptr = skip_field(ptr, endptr);
  }


/* Now fork a new process, first setting up a handler for
when it finishes, in case it does so unexpectedly. */

signal(SIGCHLD, handle_child_end);

child_pid = fork();
if (child_pid < 0)
  {
  printf("Greymail: fork failed %d\r\n", child_pid);
  return EXIT_FAILURE;
  }

/* Code for the parent process. */

if (child_pid)
  {
  close(0);           /* Close read end of the pipe */
  while (count > 0)
    {
    write(fd_pipe[1], buffer, count);
    count = read(fd_input, buffer, buffer_size);
    }
  close(fd_pipe[1]);
  close(fd_input);
  if (!child_ended) wait();
  return EXIT_SUCCESS;
  }

/* Code for the child process. Execv into the required program,
with the -ba argument replaced by an -f argument, and the -oM argument
removed. (The argument munging applies only to incoming mail.) */

else
  {
  close(fd_pipe[1]);   /* Close write end of the pipe */

  if (!output_mode)
    {
    int i;
    for (i = 1; i < argc; i++)
      {
      if (strcmp(argv[i], "-ba") == 0) argv[i] = sender;
      if (strncmp(argv[i], "-oM", 3) == 0) argv[i]  = "-bm";  /* harmless */
      }
    }

  if (execv(next_prog, argv)) return EXIT_FAILURE;
  /* if (execv("/usr/local/smail/bin/smail", argv)) return EXIT_FAILURE; */
  /* if (execv("./checkout", argv)) return EXIT_FAILURE; */
  }
}

/* End of greymail.c */
