/*
**++
**  FACILITY:
**	ADD_TRANSFORM
**
**  ABSTRACT:
**	This module converts internet-style mail addresses and DECnet format
**	mail addresses into a form usable by the local VMS MAIL utility. This
**	normally takes the form of recognising local addresses, and using the
**	local username as the address, recognising addresses which map to users
**	on the local DECnet, and using a DECnet-format address, and sending all
**	other address types to a mail gateway.
**
**	This code has been set up to read an address configuration file at
**	startup, and apply the configuration rules to the internet addresses to
**	produce a correct address for the local VMS MAIL system.
**
**	You may either:
**	    - Use the DEFAULT configuration (All addresses are directed to the
**	      local PMDF mail protocol handler - in%"address", unless
**	      SMTP_MAILSHR is a defined logical name, in which case the
**	      SMTP%"address" support is used.
**
**	    - Edit the default configuration to match your requirements.
**	      (i.e. alter the struct "def_rules[]" below)
**
**	    - create a local configuration file (news_manager:news_address.cnf)
**	      and use that file to set up local address configuration rules .
**
**	The file has the following format:
**		# (in column 1) is a comment line - the line is ignored
**		internet_address rebuild_rule
**		internet_address rebuild_rule
**		internet_address rebuild_rule
**
**	      The internet address field must start in column 1 and must not
**	      contain space characters. The '*' character may be used to force
**	      a wildcard match with the address being tested. The '?' character
**	      matches any single character.
**
**	      The rebuild rule must be separated from the internet address by
**	      at least one space character. The '\' character is used as the
**	      escape character: normally it is used to specify match
**	      substitiutions - the '\' character itself is specified by the
**	      sequence '\\'.
**
**	      e.g.:
**		# this is an example address cnf file
**		*@*.anu.* \002::\001
**		*@*.anu	  \002::\001
**		*@*	  gateway::in%"\001@\002"
**		*::*	  \001::\002
**		*	  \001
**
**	      The rules are applied in order until a succesful match is made
**	      with an internet address form - then the matching rebuild rule is
**	      selected to transform that address into a form acceptable to VMS
**	      MAIL.
**
**	      Control characters in the rebuild pattern of the first successful
**	      address match correspond to substitution of the original
**	      substring which matched at the corresponding wildcard position.
**	      eg:
**		    pattern = "*@fred.*!*"
**		    rebuild = "\002-\003|BB\001|\002'"
**
**		    input   = user@fred.example!last
**			      ----	------- ----
**			      1		2	3
**		 gives:
**		    output  = example-last|BBuser|example'
**			      ------- ----   ---- -------
**			      2	      3	     1	  2
**
**	    - Edit/rewrite this module to do anything else you want - as long
**	      as you provide the same entry points, entry point parameters,
**	      and the same return values as the procedures supplied here.
**
**  AUTHOR:
**	Geoff Huston
**
**  COPYRIGHT:
**	Copyright © 1988,1989,1990,199,1992
**
**  MODIFICATION HISTORY:
**	V6.0-4	19-March-1991	reggers@ria.ccs.uwo.ca
**	  - Add support for UWO/Mail for Vax/VMS -> use unlatered internet
**	    addresses.
**	V6.1	 5 May 1991	gh
**	  - format touchups
**	V6.1b8	30-Nov-1993	Charles Bailey  bailey@genetics.upenn.edu
**	  - don't use '%' == any 1 char in match(), since rewrite rules may
**	    include mail transport specifiers as patterns to match (e.g. in%*)
**	V6.1b9	17-Aug-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to make it compile under gcc 2.6.0 with full
**	    warnings reporting turned on - with no or very few harmless warnings
**	V6.1b9	17-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to preserve the read-only nature of string literals
**	    (strategically placed 'const' attribute to string parameters)
**	V6.1b9	15-Nov-1994     Mark Martinec   mark.martinec@ijs.si
**	  - clearaddrs(): avoid dereferencing pointer
**	    after it has been news_free()-ed
**--
**/

#ifdef vaxc
#module ADD_TRANSFORM "V6.1"
#endif

#define _ADD_TRANSFORM_C

#include "newsinclude.h"
#include "newsextern.h"

#if	UWO
char *add_transform(s)	const char *s;
{	return(s);	}
#else
			/* Local Variables */
static                  /* markers used in wild card match and substitution. */
int sm[20],             /* sm is an array of start positions, em is an array */
    em[20];             /* of end position of the wildcard match points      */

static
char rtad[256];		/* the return address is built in this variable	     */

static
struct mail_address_rules {
  const char *internet; 	/* The structure which holds the address patterns  */
  const char *local;     	/* and the corresponding rewrite rules   	   */
  }
    def_rules[] = {	/* Default is to pass all addresses to local PMDF */
	{"in%*",    "in%\001"},
	{"psi%*",   "psi%\001"},
	{"@*",	    "@\001"},
	{"*@*",	    "in%\"\001@\002\""},
	{"*::*",    "in%\"\002@\001\""},
	{"*",	    "\001"},
	{"",""}},
      smtp_rules[] = {	/* SMTP mailer rules */
	{"smtp%*",  "smtp%\001"},
	{"psi%*",   "psi%\001"},
	{"@*",	    "@\001"},
	{"*::*",    "\001::\002"},
	{"*@*",	    "smtp%\"\001@\002\""},
	{"*",	    "\001"},
	{"",""}},
      st_rules[] = {	/* Software Tools mailer rules */
	{"st%*",    "st%\001"},
	{"psi%*",   "psi%\001"},
	{"@*",	    "@\001"},
	{"*::*",    "\001::\002"},
	{"*@*",	    "st%\"\001@\002\""},
	{"*",	    "\001"},
	{"",""}};

struct mail_address_rules_rw {
  char *internet; 	/* The structure which holds the address patterns  */
  char *local;     	/* and the corresponding rewrite rules   	   */
  }   *addrs = 0;	/* Pointer to loaded rule set */


/*
 *  clearaddrs
 *
 *  Release memory allocated to hold address transform rule set
 */

static void clearaddrs()
{
  int i;

  if (addrs
      && ((struct mail_address_rules *) addrs != def_rules)
      && ((struct mail_address_rules *) addrs != smtp_rules)
      && ((struct mail_address_rules *) addrs != st_rules)) {
    for (i=0; ;i++) {
      char c = *(addrs[i].internet);
      news_free(addrs[i].internet);
      news_free(addrs[i].local);
      if (!c) break;
      };
    news_free(addrs);
    }
  addrs = 0;
}

/*
 *  read_cnf
 *
 *  Load the address transform configuration tables into memory.
 */

static void read_cnf()
{
  FILE *fpr;
  char input_line[256], result[256], *pttrn;
  char *c = NULL;
  int size = 0, mlsize = 0;

  if ((fpr = fopen(ADDRESS_FILE,"r")) != 0) {	/* Check if file is accessible  */
    char *c1, *c2;

    while (fgets(input_line,256,fpr)) {	      /* read file line-by-line */
      if ((c = strchr(input_line,'#')) != 0) *c = '\0';   /* strip comment */
      if ((c = strchr(input_line,'\n')) != 0) *c = '\0';  /* strip trailing lf char */
      c = input_line;
      while (isspace(*c)) c++;
      if (!*c) continue;
      pttrn = c;				  /* pull out address pattern */
      while (*c && !isspace(*c)) c++;
      while (isspace(*c)) *c++ = '\0';
      if (!*c) continue;
      c1 = c;		 /* pick out '\...' substrings and convert to numeric */
      c2 = result;
      while (*c1) {
	if (*c1 != '\\') *c2++ = *c1++;
	else {
	  c1++;
	  if (isdigit(*c1)) {	 /* found '\nnn' substring - convert as octal */
	    char *s = c1, sav;
	    int tmp;

	    while (isdigit(*s)) s++;
	    sav = *s;
	    *s = '\0';
	    if (sscanf(c1,"%o",&tmp) == 1) *c2++ = tmp;
	    *s = sav;
	    c1 = s;
	    }
	  else *c2++ = *c1++;	       /* else simply use next char literally */
	  }
	}
      *c2 = '\0';
      c = result;
						       /* now add to rule set */
      if (!mlsize) addrs = (struct mail_address_rules_rw *) news_malloc((mlsize = 5) * (sizeof *addrs));
      else if (size >= mlsize) addrs = (struct mail_address_rules_rw *) news_realloc(addrs,(mlsize += 5) * (sizeof *addrs));
      strcpy((addrs[size].internet = (char *) news_malloc(strlen(pttrn) + 1)),pttrn);
      strcpy((addrs[size].local = (char *) news_malloc(strlen(c) + 1)),c);
      ++size;
      }
    _ck_get(fpr);
				  /* if no rules read, then load default rule */
    if (!size) {
      if (!mlsize) addrs = (struct mail_address_rules_rw *) news_malloc((mlsize = 2) * (sizeof *addrs));
      else if (size >= mlsize) addrs = (struct mail_address_rules_rw *) news_realloc(addrs,(mlsize += 2) * (sizeof *addrs));
      strcpy(input_line,"*");
      strcpy((addrs[size].internet = (char *) news_malloc(strlen(input_line) + 1)),input_line);
      strcpy((addrs[size].local = (char *) news_malloc(strlen(c) + 1)),"\001");
      ++size;
      }
					      /* add emtpy (terminating) rule */
    *input_line = '\0';
    c = input_line;
    if (!mlsize) addrs = (struct mail_address_rules_rw *) news_malloc((mlsize = 5) * (sizeof *addrs));
    else if (size >= mlsize) addrs = (struct mail_address_rules_rw *) news_realloc(addrs,(mlsize += 1) * (sizeof *addrs));
    strcpy((addrs[size].internet = (char *) news_malloc(strlen(input_line) + 1)),input_line);
    strcpy((addrs[size].local = (char *) news_malloc(strlen(c) + 1)),c);
    if (fclose(fpr)) _ck_close(fpr);
    }
				       /* no .cnf file - use default rule set */
  else if (!access("SYS$SHARE:SMTP_MAILSHR.EXE",0) || news_getenv("SMTP_MAILSHR",0))
    addrs = (struct mail_address_rules_rw *) smtp_rules;
  else if (!access("SYS$SHARE:ST_MAILSHR.EXE",0) || news_getenv("ST_MAILSHR",0))
    addrs = (struct mail_address_rules_rw *) st_rules;
  else
    addrs = (struct mail_address_rules_rw *) def_rules;
}

/*
 *  load_cnf
 */

static void *load_cnf()
{
  static int c_mod = 0,
	     c_check_time = 0;
  struct stat c_stat;
  int st;

  sysprv();
  if (!c_mod || (c_check_time < time(0))) {
    c_mod = 1;
    c_check_time = (time((time_t *) 0) + RECHECK_TIMER);
    st = stat(ADDRESS_FILE,&c_stat);
    if (st || (c_stat.st_mtime != c_mod)) {
      clearaddrs();
      c_mod = 1;
      read_cnf();
      if (!st) c_mod = c_stat.st_mtime;
      }
    }
  nosysprv();
  return((void *) addrs);
}

/*
 *  transform
 *
 *  Build the equivalence string using the rebuilding rule corresponding
 *  to the successfully matched pattern. sm and em now contain the
 *  wildcard match points in the original input string
 */

static char *transform(locname,i)
  char *locname; int i;
{
  int j = 0,
      k,
      r = 0;
  char lc;

  while ((lc = addrs[i].local[j]) != 0) {
    if (lc < 20) {
      for (k = sm[lc - 1]; k <= em[lc - 1]; ++k) rtad[r++] = locname[k];
      }
    else rtad[r++] = addrs[i].local[j];
    j++;
    }
  rtad[r] = '\0';
  return(rtad);
}

/*
 *  match
 *
 *  Recursive match procedure which performs wildcard matching.
 */

static int match(l,p,i,li)
  const char *l, *p;
  int i, li;
{
  if (!*l) {
    if (!*p) return(1);
    else if (*p == '*') {
      sm[i] = 0;
      em[i] = -1;
      return(match(l,p+1,i+1,li));
      }
    else return(0);
    }
  if (*p == '*') {
    sm[i] = li;
    em[i] = li - 1;
    while (!match(l,p+1,i+1,li)) {
      l++;
      li++;
      em[i] = li - 1;
      if (!*l) {
	if (!*(p+1)) return(1);
	else return(0);
	}
      }
    return(1);
    }
  /* don't use '%' == any 1 char here, since rewrite rules may include
     mail transport specifiers as patterns to match (e.g. in%*)
     30-Nov-1993  Charles Bailey  bailey@genetics.upenn.edu  */
  if (*p == '?') return(match(l+1,p+1,i,li+1));
  if (*p == '^') return ((*l == *(p+1)) && match(l+1,p+2,i,li+1));
  return((*l == *p) && match(l+1,p+1,i,li+1));
}

/*  ENTRY POINT
 *
 *  add_transform
 *
 *  Returns the VMS Mail equivalent address of the supplied internet address.
 */

char *add_transform(s)	const char *s;
{
  static char locname[256];
  char *p;
  int i = 0;

					       /* Extract <..> format address */
  if ((p = strchr(s,'<')) != 0) {
    strcpy(locname,++p);
    if ((p = strchr(locname,'>')) != 0) *p = '\0';
    if ((p = strchr(locname,'\n')) != 0) *p = '\0';
    }
  else {
						      /* strip leading blanks */
    while (isspace(*s)) s++;
    strcpy(locname,s);
						     /* strip trailing fields */
			       /* (this should be a personal name if present) */
    if ((p = strchr(locname,' ')) != 0) *p = '\0';
    if ((p = strchr(locname,'\t')) != 0) *p = '\0';
    if ((p = strchr(locname,'(')) != 0) *p = '\0';
    }
						     /* convert to lower case */
  p = locname;
  while (*p) { *p = tolower(*p); p++; }
				/* no transform rules loaded - return address */
  if (!load_cnf()) return(locname);
				    /* find a match, and return the transform */
  while (*(addrs[i].internet)) {
    if (match(locname,addrs[i].internet,0,0)) return(transform(locname,i));
    i++;
    }
  return(locname);
}
#endif /* !UWO */
