/*
**  XALPARSE -- Xaliases file parser.
**  Copyright (c) 1987 Lennart Lovstrand
**  CIS Dept, Univ of Linkoping, Sweden
**
**  Use it, abuse it, but don't sell it.
*/

#include "sendmail.h"

#ifndef lint
static char	rcsid[] = "$Header: /usr/local/src/mail/sendmail/ida/aux/RCS/xalparse.c,v 1.3 1991/07/13 04:40:12 paul Exp $";
#endif /* !lint */

struct alias {
  bool a_in, a_out;
  char *a_name;
};

#define ANULL	(struct alias *) NULL

/*
**  XPARSE -- Parse an xaliases file, producing aliases + generics files.
**
**	This program parses a file in ``xaliases'' format, producing
**	a standard aliases file + a generics file.  The xaliases input
**	file has entry of the following format:
**		generic_list: mbox_list
**	where elements in both lists are separated by commas.  In
**	addition, each element in the mbox_list may be prefixed with
**	either or both of the ``redirection characters,'' "<" and ">".

**	In its simplest form, the generic_list has just one member and
**	the mbox_list uses no redirection characters.  This
**	corresponds exactly to the standard aliases format and
**	function.

**	The first extention is made by allowing more than one entry on
**	the left hand side, thus making "a, b: c" as shorthand for the
**	two entries "a: c" and "b: c".

**	The second extension is made by adding the previously
**	mentioned redirection characters to the addresses on the right
**	hand side.  These control in what direction the aliases should
**	be used.
**
**	etc.
*/

int iflag = FALSE, Iflag = FALSE, Oflag = FALSE;
static char *routing_host;
static bool translate_generics = FALSE;

main(argc, argv)
     int argc;
     char **argv;
{
  extern int optind;
  extern char *optarg;
  char c;
  char *Command_Name;
  FILE *xaliases, *aliases, *generics;

  routing_host = NULL;
  Command_Name = argv[0];

  while (argc > 1 && *argv[1] == '-') {
	if (argv[1][1] != 'r' && argv[1][1] != 't') break;
	if (argv[1][1] == 'r') {
		routing_host=2+argv[1];
		if ( *routing_host == 0 ) routing_host = "localhost";
		}
		else translate_generics = TRUE;
	++argv ;
	--argc ;
	}

  if (argc != 4) {
    fprintf(stderr, "usage: %s [-r[host]] [-t] xaliases aliases generics\n",
			Command_Name);
    exit(1);
  }

  if (strcmp(argv[1], "-") == 0)
    xaliases = stdin;
  else {
    xaliases = fopen(argv[1], "r");
    if (xaliases == NULL)
      perror(argv[1]), exit(2);
  }
  if (strcmp(argv[2], "-") == 0) aliases = stdout;
	else {
		aliases = fopen(argv[2], "w");
		if (aliases == NULL)
		  perror(argv[2]), exit(2);
		}
  if (strcmp(argv[3], "-") == 0) generics = stdout;
	else {
		generics = fopen(argv[3], "w");
		if (generics == NULL)
		  perror(argv[3]), exit(2);
		}
  
  parsefile(xaliases, aliases, generics);
  exit(0);
}

 /*
  *  Called for each address on the LHS of an alias entry.  It routes
  *  the address through the host in routing_host.  This puts the address in
  *  the form that the IDA sendmail rules require for building the
  *  aliases database.
  *
  *  Purely local addresses - those without any '%' or '!' or '@' are passed
  *  through without change.
  *
  *  Restrictions:  Assumes no deep nesting <<of> angle <brackets>> occurs,
  *  but does support enclosure in a single set of <angles>.  DECNET address
  *  such as NODE::USER should work, although they will have to be
  *  <bracketed> to satisfy the need for no ':'.  ('Twere better to use 
  *  USER@NODE.dnet).
  */
 char * route_addr(adr)
 char * adr;
{
  static char buf[BUFSIZ];
  int quoted_char, in_string;
  char *p, *left, *right, *rtch;

  if (routing_host == NULL) return (adr); /* No routing host - pass through */

  quoted_char = FALSE; in_string = FALSE;
  left = NULL; right = NULL, rtch = NULL;

  for (p=adr; *p; p++) {
 /* Handle ! specially, since sendmail treats \! as equivalent to ! */
      if (!in_string && rtch == NULL && *p == '!') {rtch = p; continue;}
      if (quoted_char) {quoted_char = FALSE; continue;}
          else quoted_char = (*p == '\\');
      if (!in_string) {
         if (*p == '>') {right = p; break;} /* Only comments after brackets */
         switch (*p) {
            case '<':left=p;
                     rtch=NULL; /* Everything before brackets is comment */
                     break;
            case '%':if (rtch == NULL) rtch=p;
                     break;
            case '@':if (rtch == NULL || *rtch != '@') rtch=p;
                     break;
            case ':': if (rtch == NULL && p[1] == ':') rtch=p;
                     break;
            }
          }
       if (*p == '"') in_string = !in_string;
       }

  if (rtch == NULL) return(adr); /* Local address is passed thru */
  if (left == NULL) {
     sprintf(buf,"%s@%s",adr,routing_host); /* Change 'addr' to 'addr@host' */
     if (*rtch == '@') buf[rtch-adr] = '%';  /* and u@addr to u%addr@host */
     return(buf);
     }
  if (right == NULL) return (adr); /* Unbalanced <> - pass through */
  strcpy(buf,adr);
  if (*rtch == '@') { /* Add routing to a source route */
     sprintf(buf+(left-adr)+1,"@%s%c%s",routing_host,
              (left[1] == '@') ? ',':':', left+1);
     } else { /* but just add '@host' to end of uucp or decnet address */
     sprintf(buf+(right-adr),"@%s%s",routing_host,right);
     }
  return(buf);
}

parsefile(xaliases, aliases, generics)
     FILE *xaliases, *aliases, *generics;
{
  extern char *index();
  char line[BUFSIZ];
  struct alias *rhs[BUFSIZ], *lhs[BUFSIZ];
  struct alias **a, **r, **first;

  while (readline(line, sizeof(line), xaliases) >= 0) {
    parseline(line, lhs, rhs);

    for (first = rhs; *first != ANULL; first++)
      if ((*first)->a_in)
	break;
    if (*first != ANULL || first == rhs)
      for (a = lhs; *a != ANULL; a++) {
	fprintf(aliases, "%s:%s", route_addr((*a)->a_name),
		(*first == ANULL) ? "" : (*first)->a_name );
	for (r = first+1; *r != ANULL; r++)
	  if ((*r)->a_in)
	    fprintf(aliases, ",%s", (*r)->a_name);
	fprintf(aliases, "\n");
      }

    for (first=rhs; *first != ANULL; first++)
      if ((*first)->a_out)
        if (translate_generics)
           {
            char *p;
            for (p=(*first)->a_name; *p; p++)
                 if (isupper(*p)) *p = tolower(*p);
            fprintf(generics, "%s\t%s\n",
                     (*first)->a_name,lhs[0]->a_name);
            }
           else break;

    if (*first != ANULL) {
      fprintf(generics, "%s\t%s", lhs[0]->a_name, (*first)->a_name);
      for (r = first+1; *r != ANULL; r++)
	if ((*r)->a_out)
	  fprintf(generics, " %s", (*r)->a_name);
      fprintf(generics, "\n");
    }

    freebufs(lhs, rhs);
  }
}

/**
 **	PEEKC -- Return the next char to be read.
 **/

peekc(stream)
     FILE *stream;
{
  int c;

  c = getc(stream);
  if (c != EOF)
    ungetc(c, stream);
  return c;
}

/**
 **	READLINE -- Read a (logical) line and return the # of chars read
 **/

readline(buf, bufsiz, stream)
     char *buf;
     int bufsiz;
     FILE *stream;
{
  int len;
  char *sharp;

  if (fgets(buf, bufsiz, stream) == NULL)
    return -1;
  buf[strlen(buf)-1] = '\0';

  /*
  if ((sharp = index(buf, '#')) != NULL)
    *sharp = '\0';
  */
  if (buf[0] == '#')
    buf[0] = '\0';

  len = strlen(buf);
  if (isspace(peekc(stream)))
    return len + readline(&buf[len], bufsiz-len, stream);
  else
    return len;
}

/**
 **	PARSETHING
 **/


#define LHS	1
#define RHS	2

char *
parsething(line, thing, side)
     char *line;
     struct alias **thing;
     int side;
{
  register char *s, *d;
  register bool
    insideroute = FALSE,
    insidestring = FALSE,
    quotedchar = FALSE;
  bool i_mark, o_mark;
  char buf[BUFSIZ];

  s = line;
  d = buf;

  while (*s != '\0' && isspace(*s)) s++;
  if (side == RHS) {
    if (o_mark = (*s == '<')) s++;
    if (i_mark = (*s == '>')) s++;
    i_mark = i_mark || !o_mark;			/* default to '>' */
    while (*s != '\0' && isspace(*s)) s++;
  }

  for (;*s != '\0'; s++) {
    /* exit if non-quoted comma (or colon & LHS) */
    if (!insidestring && !quotedchar && !insideroute &&
	*s == ',' || ((side == LHS) && *s == ':'))
      break;

    /* copy if not unquoted whitespace */
    if (insidestring || quotedchar || !isspace(*s))
      *d++ = *s;

    /* special quote character handling */
    if (quotedchar)
      quotedchar = FALSE;
    else {
      quotedchar = (*s == '\\');
      if (!insidestring)
	if (*s == '<')
	  insideroute = TRUE;
	else if (*s == '>')
	  insideroute = FALSE;
      if (*s == '"')
	insidestring = !insidestring;
    }
  }
  while (d > buf && isspace(d[-1])) d--;
  *d = '\0';

  if (d == buf && *s == '\0') {
    *thing = ANULL;
    return NULL;
  } else {
    *thing = (struct alias *) malloc(sizeof(struct alias));
    (*thing)->a_in = i_mark;
    (*thing)->a_out = o_mark;
    (*thing)->a_name = malloc(strlen(buf) + 1);
    strcpy((*thing)->a_name, buf);
    return s;
  }
}

/**
 **	PARSELINE
 **/

parseline(line, lhs, rhs)
     char *line;
     struct alias **lhs, **rhs;
{
  line--;

  while ((line = parsething(line+1, lhs++, LHS)) != NULL)
    if (*line == ':')
      break;
  *lhs = NULL;

  if (line != NULL)
    while ((line = parsething(line+1, rhs++, RHS)) != NULL);
  *rhs = ANULL;
}

/**
 **	FREEBUFS
 **/

freebufs(lhs, rhs)
     struct alias **lhs, **rhs;
{
  while (*lhs != ANULL) {
    free((*lhs)->a_name);
    free(*lhs);
    lhs++;
  }
  while (*rhs != ANULL) {
    free((*rhs)->a_name);
    free(*rhs);
    rhs++;
  }
}

