/*
**++
**  FACILITY:
**	NEWSDIST
**
**  ABSTRACT:
**      This module manages the flow of news items to adjacent news nodes.
**      The module uses the NEWS.SYS and NEWS.DISTRIBUTION files to control
**      news item passing in the NEWS network. Optionally a distributions
**      file, NEWS.SYSD, may be used to define accepted distributions.
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1989,1990,1991
**
**
**  CONTRIBUTIONS
**
** 	MP  10/9/90 Mark Pizzolato, mark@infocomm.com
**	SIZE field added to format of SYS file flags
**
**  ENTRY POINTS:
**      sys_local_accept(newsgroups,distribution)
**      sys_accept_group(newsgroup)
**      sys_remote_send(path,newsgroups,distribution,item_file,message_id,net)
**      path_match(path,test_node)
**	flush_downstream()
**
**  VERSION HISTORY
**	V6.0-4	30/4/91	gh
**		Modify filter acceptance algorithm to match distribution
**		keywords against newsgroup filter list.
**	V6.1	 5-Feb-1992	rankin@eql.caltech.edu
**		Lint cleanup
**	V6.1	 17-Feb-1992	mark@infocomm.com
**          - Reworked sys_remote_send to minimize the number of disk I/O
**            operations that are performed.  a) keep downstream batch files
**            open across calls (to minimize the number of file opens and
**            closes buffer flushes), and b) avoid opening and reading each
**            outbound file multiple times by buffering the item file's contents
**            in a memory buffer (saving an extra copy of the item from being
**            written to SYS$SCRATCH).  Both of these changes trade off virtual
**            memory resources for performance.  This trade off is acceptable
**            since it really only affects the NEWS MANAGER process which is
**            being used for ADD processing, and does not accect the general 
**            user.  Keeping multiple downstream batch files open can be
**            disabled by defining CLOSE_BATCHFILES.
**	V6.1	11-Aug-1992	mark@infocomm.com
**          - Fixed bug in read_config that allowed Comment lines (starting
**	      with #) to be "continued", i.e. commenting out a line that ended
**	      in a "\" would also comment out all subsequent lines until one
**	      didn't end in a "\".
**	    - Fixed a bug introduced previously that miscomputed the article
**	      buffer size.  This bug was rather benign except for excessive
**	      virtual memory usage.
**	    - Restored code which minimizes the number of VAXC runtime library
**	      calls for file I/O and also minimizes the amount of CPU consumed 
**	      for batch generation.  Correctly accumulates the number of down
**	      stream articles and bytes that have been passed to each neighbor
**	      system.  Accomodated a bug in the VAXC runtime library that 
**	      didn't allow a fputs with a buffer that larger than 32kb.  It is 
**	      a rare case where an article is larger than this, but it can 
**	      and does happen.
**	V6.1b8  22-Dec-1993	mark.martinec@ijs.si
**	    - convert calls to RMS to use new sys_* macros for error handling 
**	V6.1	10-Jul-1993	munroe@dmc.com
**	    suppress addition of the #! rnews ... line if sys_type is "m"
**	V6.1b9  04-Jun-1994 munroe@dmc.com
**	  - Rig a mechanism for limiting the sizes of individual NNTP batches.
**	    The Type of Batch field can be of the form Nnumber where number is
**	    the number of articles to be included in each batch.
**	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.1b10	09-Mar-1995     Mark Pizzolato	mark@infocomm.com
**	  - Included code to keep down stream article-ID files open in the
**	    same fashion that down stream batch files are kept open for the
**	    duration of an ADD FILE/BATCH operation.
**	V6.1b11 14-Apr-1997	Mark Martinec   mark.martinec@ijs.si
**	  - replace several calls to strcpy/strcat with safer strnncpy/strnncat
**	  - replace pattern strcpy(news_malloc(strlen(x)+1),x) with news_strdup
**	  - add attribute 'const' to several char* parameters
**	  - rewrite check_distribution_string(), scannet() and aliases()
**	    to not modify their parameter
**	  - add length parameter to search_aliases()
**--
**/

#ifdef vaxc
#module NEWSDIST "V6.2"
#endif

#define _NEWSDIST_C
#define module_name "NEWSDIST"

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


/*
 *  open_id_file
 *
 *  Open an identifier file - create the file if it does not exist already.
 */

static int open_id_file(s)
  SYS_ENTRY_T *s;
{
  int status;

  s->sys_idxfab = cc$rms_fab;
  s->sys_idxfab.fab$b_fac = FAB$M_PUT;
  s->sys_idxfab.fab$l_fna = s->sys_file;
  s->sys_idxfab.fab$b_fns = strlen(s->sys_idxfab.fab$l_fna);
  s->sys_idxfab.fab$l_dna = (char *) "NEWS_MANAGER:IHAVE.IDX";
  s->sys_idxfab.fab$b_dns = strlen(s->sys_idxfab.fab$l_dna);
  s->sys_idxfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

  sysprv();

  if (!sys_open_nofnf(&s->sys_idxfab)) {
    s->sys_idxfab.fab$b_bks = 4;
    s->sys_idxfab.fab$l_fop = FAB$M_CIF;
    s->sys_idxfab.fab$w_mrs = sizeof s->sys_newsidx+4;
    s->sys_idxfab.fab$b_org = FAB$C_IDX;
    s->sys_idxfab.fab$b_rat = FAB$M_CR;
    s->sys_idxfab.fab$b_rfm = FAB$C_VAR;
    s->sys_idxfab.fab$l_xab = (char *) &s->sys_idxkey_1;

    s->sys_idxkey_1 = cc$rms_xabkey;
    s->sys_idxkey_1.xab$b_dtp = XAB$C_STG;
    s->sys_idxkey_1.xab$b_flg = XAB$M_DUP;
    s->sys_idxkey_1.xab$w_pos0 = (char *) &s->sys_newsidx.t - (char *) &s->sys_newsidx;
    s->sys_idxkey_1.xab$b_ref = 0;
    s->sys_idxkey_1.xab$b_siz0 = IDLEN+4; /* RRS added time to key */
    s->sys_idxkey_1.xab$l_nxt = (char *) &s->sys_idxpro_1;

    s->sys_idxpro_1 = cc$rms_xabpro;
    s->sys_idxpro_1.xab$w_pro = 0xEE00;
    if (!sys_create(&s->sys_idxfab)) return(nosysprv(),0);
    }

  s->sys_idxrab = cc$rms_rab;
  s->sys_idxrab.rab$b_krf = 0;
  s->sys_idxrab.rab$l_fab = &s->sys_idxfab;
  s->sys_idxrab.rab$l_ubf = (char *) &s->sys_newsidx;
  s->sys_idxrab.rab$w_usz = sizeof s->sys_newsidx;
  s->sys_idxrab.rab$l_rbf = (char *) &s->sys_newsidx;
  s->sys_idxrab.rab$w_rsz = sizeof s->sys_newsidx;

  if (!sys_connect(&s->sys_idxrab)) {
    sys_close(&s->sys_idxfab);
    nosysprv();
    return(0);
    }
  nosysprv();
  return(1);
}

/*
 * close_id_file
 *
 * close the index file - use sysprv - cause that was used to open it
 */

static void close_id_file(s)
  SYS_ENTRY_T *s;
{
  int status;

  sysprv();
  sys_close(&s->sys_idxfab);
  nosysprv();
}

/*
 * add_id
 *
 * Add a new entry into the file
 */

static int add_id(s, id)
  SYS_ENTRY_T *s;
  const char *id;
{
  int status;
  int i = strlen(id);

  if (i > MAXIDLEN) i = MAXIDLEN;
  s->sys_newsidx.t = time(0);
  strncpy(s->sys_newsidx.idx_id,id,i);
  if (i < MAXIDLEN) memset(&s->sys_newsidx.idx_id[i], '\0', MAXIDLEN-i);
  s->sys_idxrab.rab$w_usz = s->sys_idxrab.rab$w_rsz = MAXIDLEN+4;
  s->sys_idxrab.rab$b_rac = RAB$C_KEY;
  s->sys_idxrab.rab$l_rop = RAB$M_WAT;
  return(sys_put(&s->sys_idxrab));
}

/*
 *  NEWS.SYS and NEWS.DISTRIBUTION structure definitions
 *
 *  The following structures hold memory maps of these files
 */

/*
 *  clearfilter     - clear filter list
 *  cleardist       - clear mapped news.distribution entries
 *  clearsys        - clear mapped news.sys entries
 */

static void clearfilter(t)
  SYS_FILTER_T *t;
{
  SYS_FILTER_T *tmp;

  while (t) {
    tmp = t->sys_fnext;
    news_free(t->sys_filtnam);
    news_free(t);
    t = tmp;
    }
}

static void cleardist()
{
  DIST_ENTRY_T *tmp;

  while (distfile) {
    tmp = distfile->next;
    news_free(distfile->from_name);
    clearfilter(distfile->to_names);
    news_free(distfile);
    distfile = tmp;
    }
}

static void clearsys()
{
  SYS_ENTRY_T *tmp;

  while (sysfile) {
    tmp = sysfile->sys_enext;
    if (sysfile->sys_article_count)
      printf("- Forwarded: %4d/%d Articles/Bytes to site: %s\n", 
             sysfile->sys_article_count, sysfile->sys_article_bytes, 
	     sysfile->sys_nodename);
    if (sysfile->sys_id_count)
      printf("- Forwarded: %4d Article ID's to site: %s\n", 
             sysfile->sys_id_count, sysfile->sys_nodename);
    news_free(sysfile->sys_nodename);
    news_free(sysfile->sys_type);
    news_free(sysfile->sys_file);
    if (sysfile->sys_fp) fclose(sysfile->sys_fp);
    if (sysfile->sys_index_file) close_id_file(sysfile);
    if (sysfile->sys_d != sysfile->sys_f) clearfilter(sysfile->sys_d);
    clearfilter(sysfile->sys_f);
    clearfilter(sysfile->sys_no);
    news_free(sysfile);
    sysfile = tmp;
    }
  local_dfilter = local_filter = 0;
}

static int parse_batch_size(type)
  const char *type;
{
  int size = NEWS_BATCH_SIZE;

  if (!type || !*type || *type == 'm') return(size);     
  if (*type == 'b') ++type;
  if (*type == 'n') {
    ++type;
    size = NEWS_NNTP_BATCH_SIZE;
    }
  if (isdigit(*type)) size = atoi(type);	
  return(size);
}

/*
 *  scansys
 *
 *  Read a news.sys line of the form "node:filter:format:file^xmitformat"
 *  and store in local structure
 */

static void scansys(s)
  const char *s;
{
  SYS_ENTRY_T *tmp,
              *t1 = sysfile;
  SYS_FILTER_T *fmp,
               *f1 = 0;
  char *ss, *node, *filters = 0, *type = 0, *file = 0, *dist = 0;
  char *cp1, *cp2, *cp3, *np1;
  int cp1_l;

  /* the following three strings should actually be 'const' strings,
     but since 'filters', 'type' and 'file' are not pointers to const strings,
     we have to use r/w strings and make sure each time they contain the
     intended value.      14-Sep-1994  mark.martinec@ijs.si  */
  static char null_str[2] = "\0"; /* two nulls not really needed, but just in case */
  static char asterisk_str[2] = "*";
  static char b_str[2]        = "b";

  ss = news_strdup(s,-1);
  blank_strip(ss);
  if (!strlen(ss)) { news_free(ss); return; }
  node = ss;
  if ((filters = strchr(node,':')) != 0) {
    *filters++ = '\0';
    if ((type = strchr(filters,':')) != 0) {
      *type++ = '\0';
      if ((file = strchr(type,':')) != 0) {
        *file++ = '\0';
	if ((dist = strchr(file,':')) != 0) *dist++ = '\0';
        }
      }
    }
  if (!*node) { news_free(ss); return; }
  tmp = (SYS_ENTRY_T *) news_malloc(sizeof *tmp);
  tmp->sys_no = 0;
  tmp->sys_fp = 0;
  tmp->sys_index_file = 0;
  if ((cp1 = strchr(node,'/')) != 0) {
    *cp1++ = '\0';
    for (; cp1; cp1=cp2) {
      if ((cp2 = strchr(cp1,',')) != 0) *cp2++ = '\0';
      cp1_l = strlen(cp1);
      if (cp1_l > 0) {
        fmp = (SYS_FILTER_T *) news_malloc(sizeof *fmp);
        fmp->sys_filtnam = news_strdup(cp1,cp1_l);
        fmp->sys_fnext = 0;
        if (!tmp->sys_no) tmp->sys_no = f1 = fmp;
        else { f1->sys_fnext = fmp; f1 = fmp; }
        }
      }
    }
  tmp->sys_nodename = news_strdup(node,-1);
  tmp->sys_f = 0;
  if (!file)
    { null_str[0]='\0'; file = null_str; }
  else {
    cp1 = file;
    if ((cp2 = strchr(file,'^')) != 0) {
      *cp2++ = '\0';
      if ((!type) || (*type == 'b')) type = cp2;
      }
    }
  tmp->sys_batch_size = parse_batch_size(type);
  if (!type || !*type)
    { b_str[0]='b'; b_str[1]='\0'; type = b_str; }
  tmp->sys_type = news_strdup(type,-1);
  cp1 = file;
  while ((cp1 = strchr(cp1,'/')) != 0) *cp1++ = ':';
  tmp->sys_file = news_strdup(file,-1);
  if (!filters || !*filters)
    { asterisk_str[0]='*'; asterisk_str[1]='\0'; filters = asterisk_str; }
  for (cp1=filters; cp1; cp1=cp2) {
    if ((cp2 = strchr(cp1,',')) != 0) *cp2++ = '\0';
    for (cp3=cp1; *cp3; cp3++)
      { *cp3 = isgraph(*cp3) ? tolower(*cp3) : '_'; }
    np1 = cp1;
    for (cp3=cp1; *cp3; ) {        /* convert all occurrences of "all" to "*" */
      if (   !strncmp(cp3,"all",3)
          && ((*(cp3 + 3) == '.') || !(*(cp3 + 3)))
          && ((cp3 == cp1) || (*(cp3 - 1) == '.'))) {
        *np1++ = '*';
        cp3 += 3;
	}
      else *np1++ = *cp3++;
      }
    *np1 = *cp3;

    cp1_l = strlen(cp1);
    if (cp1_l > 0) {
      fmp = (SYS_FILTER_T *) news_malloc(sizeof *fmp);
      fmp->sys_filtnam = news_strdup(cp1,cp1_l);
      fmp->sys_fnext = 0;
      if (!tmp->sys_f) tmp->sys_f = f1 = fmp;
      else { f1->sys_fnext = fmp; f1 = fmp; }
      }
    }
  if (!tmp->sys_f) {
    fmp = (SYS_FILTER_T *) news_malloc(sizeof *fmp);
    fmp->sys_filtnam = news_strdup("*",1);
    fmp->sys_fnext = 0;
    tmp->sys_f = fmp;
    }
  if (!dist || !*dist) tmp->sys_d = tmp->sys_f;
  else {
    tmp->sys_d = 0;
    for (cp1=dist; cp1; cp1=cp2) {
      if ((cp2 = strchr(cp1,',')) != 0) *cp2++ = '\0';
      for (cp3=cp1; *cp3; cp3++)
        { *cp3 = isgraph(*cp3) ? tolower(*cp3) : '_'; }
      np1 = cp1;
      for (cp3=cp1; *cp3; ) {      /* convert all occurrences of "all" to "*" */
        if (   !strncmp(cp3,"all",3)
            && ((*(cp3 + 3) == '.') || !(*(cp3 + 3)))
            && ((cp3 == cp1) || (*(cp3 - 1) == '.'))) {
          *np1++ = '*';
          cp3 += 3;
	  }
        else *np1++ = *cp3++;
        }
      *np1 = *cp3;
      cp1_l = strlen(cp1);
      if (cp1_l > 0) {
        fmp = (SYS_FILTER_T *) news_malloc(sizeof *fmp);
        fmp->sys_filtnam = news_strdup(cp1,cp1_l);
        fmp->sys_fnext = 0;
        if (!tmp->sys_d) tmp->sys_d = f1 = fmp;
        else { f1->sys_fnext = fmp; f1 = fmp; }
        }
      }
    if (!tmp->sys_d) tmp->sys_d = tmp->sys_f;
    }
  tmp->sys_enext = 0;
/* wcf 7/22/92 */
  tmp->sys_article_count = tmp->sys_article_bytes = tmp->sys_id_count = tmp->sys_estimated_article_count = 0;
  if (!sysfile) sysfile = tmp;
  else {
    while (t1->sys_enext) t1 = t1->sys_enext;
    t1->sys_enext = tmp;
    }
  if (!strcmp(news_pathname,node) || !strcmp("me",node)) {
    local_filter = tmp->sys_f;
    local_dfilter = tmp->sys_d;
    }
  news_assert(null_str[0]     == '\0' && null_str[1]     == '\0');
  news_assert(asterisk_str[0] == '*'  && asterisk_str[1] == '\0');
  news_assert(b_str[0]        == 'b'  && b_str[1]        == '\0');
  news_free(ss);
  }

static void scandist(s)
  const char *s;
{
  SYS_ENTRY_T *tmp = 0;
  SYS_FILTER_T *fmp,
               *f1 = 0,
               *savd = 0;
  char *cp1, *cp2, *cp3, *np1;
  char *ss, *node, *filters = 0;
  int cp1_l;
  static char asterisk_str[2];   /* "*" */

  ss = news_strdup(s,-1);
  blank_strip(ss);
  if (!strlen(ss)) return;
  node = ss;
  if ((filters = strchr(node,':')) != 0) *filters++ = '\0';
  if (!*node) return;
  if (!filters || !*filters)
    { asterisk_str[0]='*'; asterisk_str[1]='\0'; filters = asterisk_str; }

  for (tmp = sysfile; tmp; tmp = tmp->sys_enext)
    { if (!strcmp(tmp->sys_nodename,node)) break; }

  if (tmp && strlen(filters)) {
    if (tmp->sys_d != tmp->sys_f) savd = tmp->sys_d;
    tmp->sys_d = 0;
    for (cp1=filters; cp1; cp1=cp2) {
      if ((cp2 = strchr(cp1,',')) != 0) *cp2++ = '\0';
      for (cp3=cp1; *cp3; cp3++)
        { *cp3 = isgraph(*cp3) ? tolower(*cp3) : '_'; }
      np1 = cp1;
      for (cp3=cp1; *cp3; ) {      /* convert all occurrences of "all" to "*" */
        if (   !strncmp(cp3,"all",3)
            && ((*(cp3 + 3) == '.') || !(*(cp3 + 3)))
            && ((cp3 == cp1) || (*(cp3 - 1) == '.'))) {
          *np1++ = '*';
          cp3 += 3;
	  }
	else *np1++ = *cp3++;
        }
      *np1 = *cp3;

      cp1_l = strlen(cp1);
      if (cp1_l) {
        fmp = (SYS_FILTER_T *) news_malloc(sizeof *fmp);
        fmp->sys_filtnam = news_strdup(cp1,cp1_l);
        fmp->sys_fnext = 0;
        if (!tmp->sys_d) tmp->sys_d = f1 = fmp;
        else { f1->sys_fnext = fmp; f1 = fmp; }
        }
      }
    if (!tmp->sys_d) {
      if (savd) tmp->sys_d = savd;
      else tmp->sys_d = tmp->sys_f;
      }
    else if (savd) clearfilter(savd);
    if (!strcmp(news_pathname,node)) local_dfilter = tmp->sys_d;
    if (!strcmp("me",node)) local_dfilter = tmp->sys_d;
    }
  news_free(ss);
}

/*
 *  scannet
 *
 *  Read a news.distribution line of the form   node node,node,...
 *  and store in local structure
 */

static void scannet(s)
  const char *s;			/* s is compressed, stripped string */
{
  const char *cp1;
  SYS_FILTER_T *fmp, *f1 = 0;
  DIST_ENTRY_T *tmp, *t1 = distfile;
  int node_l, cp1_l;

  node_l = strcspn(s," ,\t\n");		/* scan accross node word */
  if (node_l < 1) return;		/* handle the case of the string " " */
  tmp = (DIST_ENTRY_T *) news_malloc(sizeof *tmp);
  tmp->from_name = news_strdup(s,node_l);
  tmp->to_names = 0;
  for (cp1 = s+node_l; ; cp1 += cp1_l) {	/* now handle following words */
    cp1 += strspn(cp1," \t\n");			/* scan accross whitespace */
    if (!*cp1) break;
    cp1_l = strcspn(cp1," ,\t");
    if (cp1_l > 0) {
      fmp = (SYS_FILTER_T *) news_malloc(sizeof *fmp);
      fmp->sys_filtnam = news_strdup(cp1,cp1_l);
      fmp->sys_fnext = 0;
      if (!tmp->to_names) tmp->to_names = f1 = fmp;
      else { f1->sys_fnext = fmp; f1 = fmp; }
      }
    }
  tmp->next = 0;
  if (!distfile) distfile = tmp;
  else {
    while (t1->next) t1 = t1->next;
    t1->next = tmp;
    }
}

/*
 *  read_config
 *
 *  Read entries from a config file and call a scan routine for each line
 */

static int read_config(filename,scanfunc)
  const char *filename;
  void (*scanfunc)(const char *);
{
  FILE *fp;
  char *in_line,
       *cp;

  if (!(fp = fopen(filename,"r"))) return(0);
  while ((in_line = fgetlc(fp)) != 0) {
    if ((cp = strchr(in_line,'\n')) != 0) *cp = '\0';
    strip_compress_lower(in_line);
    if (!*in_line) continue;
    (*scanfunc)(in_line);
    }
  fclose(fp);
  return(1);
}

/*
 *  readsys
 *
 *  Read the news.sys file, and pass all re-formatted lines to scansys().
 */

static void readsysd(force)
  int force;
{
  static int s_mod = 0,
             s_check_time = 0;
  struct stat s_stat;
  int st;

  if (force || !s_mod || (s_check_time < time(0))) {
    if (force) s_mod = 1;
    s_check_time = (time(0) + RECHECK_TIMER);
    st = stat(SYSD_FILE,&s_stat);
    if (st || (s_stat.st_mtime != s_mod)) {
      s_mod = 1;
      if (!st && read_config(SYSD_FILE,scandist)) s_mod = s_stat.st_mtime;
      }
    }
}

SYS_ENTRY_T *readsys()
{
  static int s_mod = 0,
             s_check_time = 0;
  struct stat s_stat;
  int st,
      force_sysd = 0;

  if (nntp_client) return(0);
  sysprv();
  if (!s_mod || (s_check_time < time(0))) {
    s_check_time = (time(0) + RECHECK_TIMER);
    st = stat(SYS_FILE,&s_stat);
    if (st || (s_stat.st_mtime != s_mod)) {
      clearsys();
      s_mod = 1;
      if (!st && read_config(SYS_FILE,scansys)) {
        force_sysd = 1;
        s_mod = s_stat.st_mtime;
        }
      }
    }
  readsysd(force_sysd);
  nosysprv();
  return(sysfile);
}

/*
 *  readnet
 *
 *  Read the news.distribution file, and pass all reconstructed lines to
 *  net_scan().
 */

static DIST_ENTRY_T *
readnet()
{
  static int d_mod = 0,
             d_check_time = 0;
  struct stat s_stat;
  int st;

  if (nntp_client) return(0);
  sysprv();
  if (!d_mod || (d_check_time < time(0))) {
    d_check_time = (time(0) + RECHECK_TIMER);
    st = stat(DIST_FILE,&s_stat);
    if (st || (s_stat.st_mtime != d_mod)) {
      cleardist();
      d_mod = 1;
      if (!st && read_config(DIST_FILE,scannet)) d_mod = s_stat.st_mtime;
      }
    }
  nosysprv();
  return(distfile);
}

/*
 *  test_accept
 *
 *  Test if the newsgroup groups and distribution dist is accepted
 *  using the filter f
 */

static int test_accept(newsgroups,distribution,f,d)
  const char *newsgroups, *distribution;
  SYS_FILTER_T *f, *d;
{
  char *cp1,
       *cp2,
       locname[NGRP_SIZE],
       wname[NGRP_SIZE],
       *line;
  int accept = 0, ngaccept, used_f_sav, ng_len, dist_len;
  SYS_FILTER_T *f_sav = f, *d_sav = d;

  if (!f) return(1);

  ng_len = newsgroups ? strlen(newsgroups) : 0;
  dist_len = distribution ? strlen(distribution) : 0;
  line = news_malloc(max(ng_len,dist_len) + 1);
  if (ng_len > 0) memcpy(line,newsgroups,ng_len);
  line[ng_len] = '\0';
  cp1 = line;

  do {
    if ((cp2 = strchr(cp1,',')) != 0) *cp2++ = '\0';
    util_cvrt(locname,cp1);
    ngaccept = 0;
    for (f=f_sav; f; f=f->sys_fnext) {
      if (*f->sys_filtnam == '!') {
        if (!strcmp("all",f->sys_filtnam + 1)) ngaccept = -1;
        else if (!strcmp(locname,f->sys_filtnam + 1)) { ngaccept = -1; break; }
        else if (wild_match(locname,f->sys_filtnam + 1)) ngaccept = -1;
        else {
          int ll;
          strnncpy(wname,sizeof(wname), f->sys_filtnam + 1, -1, &ll);
          strnncat(wname,sizeof(wname),ll, ".*",2, 0);
          if (wild_match(locname,wname)) ngaccept = -1;
          }
        }
      else {
        if (!strcmp("all",f->sys_filtnam)) ngaccept = 1;
        else if (!strcmp(locname,f->sys_filtnam)) { ngaccept = 1; break; }
        else if (wild_match(locname,f->sys_filtnam)) ngaccept = 1;
        else {
          int ll;
          strnncpy(wname,sizeof(wname), f->sys_filtnam,-1, &ll);
          strnncat(wname,sizeof(wname),ll ,".*",2, 0);
          if (wild_match(locname,wname)) ngaccept = 1;
          }
        }
      }
    } while ((ngaccept<=0) && (cp1 = cp2));
  if (ngaccept<=0) {news_free(line); return(0);}
  if ((!distribution) || (!*distribution)){news_free(line);return(ngaccept>0);}
  accept = 0;
  strcpy(line,distribution);
/*
 *  I get a lot of article in junk because of mal-formed Distribution:
 *  headers.  A lot of them are junked because of trailing whitespace,
 *  or using ' ' rather then ',' to delimit distributions. The Kludge
 *  that follows fixes these up.  The actual article isn't changed.
 */
  strip_compress_lower(line);
  for (cp1=line; *cp1; ++cp1)
    if (*cp1 == ' ') *cp1 = ',';
/* end of Kludge */
  if (!strcmp(line,"all")) {news_free(line); return(1);}
  cp1 = line;
  do {
    if ((cp2 = strchr(cp1,',')) != 0) *cp2++ = '\0';
    util_cvrt(locname,cp1);
    used_f_sav = 0;
    ngaccept = 0;
    for (d=d_sav; d; ) {
      if (*d->sys_filtnam == '!') {
        if (!strcmp("all",d->sys_filtnam + 1)) ngaccept = -1;
        else if (!strcmp(locname,d->sys_filtnam + 1)) { ngaccept = -1; break; }
        else if (wild_match(locname,d->sys_filtnam + 1)) ngaccept = -1;
        else {
          int ll;
          strnncpy(wname,sizeof(wname), d->sys_filtnam + 1, -1, &ll);
          strnncat(wname,sizeof(wname),ll, ".*",2, 0);
          if (wild_match(locname,wname)) ngaccept = -1;
          }
        }
      else {
        if (!strcmp("all",d->sys_filtnam)) ngaccept = 1;
        else if (!strcmp(locname,d->sys_filtnam)) { ngaccept = 1; break; }
        else if (wild_match(locname,d->sys_filtnam)) ngaccept = 1;
        else {
          int ll;
          strnncpy(wname,sizeof(wname), d->sys_filtnam,-1, &ll);
          strnncat(wname,sizeof(wname),ll, ".*",2, 0);
          if (wild_match(locname,wname)) ngaccept = 1;
	  else {
            strnncat(locname,sizeof(locname),-1, ".",1, 0);
            if (!strncmp(locname,d->sys_filtnam,strlen(locname))) ngaccept = 1;
            locname[strlen(locname) - 1] = '\0';
            }
          }
        }
      d = d->sys_fnext;
		/* some brain-dead news systems put the top level
		   hierarchy in as the distribution keyword - so the
		   newsgroup filter should also be matched against
		   the distribution string */
      if (!d && (d_sav != f_sav) && !used_f_sav) {
        d = f_sav;
        used_f_sav = 1;
        }
      }
    } while ((ngaccept<=0) && (cp1 = cp2));
  news_free(line);
  return(ngaccept>0);
}

/*
 *  sys_local_accept
 *
 *  Determine if newsgroups and distribution is accepted by the
 *  host node's filter
 */

int sys_local_accept(newsgroups,distribution)
  const char *newsgroups, *distribution;
{
  if (!readsys()) return(1);
  if (!local_filter) return(1);
  return(test_accept(newsgroups,distribution,local_filter,local_dfilter));
}

/*
 *  sys_accept_group
 *
 *  Determine if the newsgroup is accepted by the host node's filter.
 */

int sys_accept_group(newsgroup)
  const char *newsgroup;
{
  SYS_FILTER_T *f;

  if (!readsys()) return(1);
  if (!(f = local_filter)) return(1);
  return(test_accept(newsgroup,0,f,0));
}

static void get_itm_file_name(id,name)
  const char *id;
  char **name;
{
  int status;
  static char itm_name[512];
  char pref[512];

  util_idcpy(pref,id);
  grprab.rab$l_kbf = (char *) &(newsitm.itm_grp);
  grprab.rab$b_ksz = 4;
  grprab.rab$b_krf = 1;
  grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
  grprab.rab$b_rac = RAB$C_KEY;

  itmrab.rab$l_kbf = pref;
  itmrab.rab$b_ksz = IDLEN + 4;
  itmrab.rab$b_krf = 1;
  itmrab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
  itmrab.rab$b_rac = RAB$C_KEY;
  if (!sys_get_nornf(&itmrab)) {
      itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
      if (!sys_get_nornf(&itmrab) || strcmp(pref,newsitm.itm_id))
	return;
    }
  if (!sys_get_nornf(&grprab)) return;
  sprintf(itm_name,Itm_template,util_dir(newsgrp.grp_name),newsitm.itm_num);
  *name = itm_name;
}

/*
 *  sys_remote_send
 *
 *  Determine if a news item should be sent to an adjacent net site.
 *  If this is a network feed, then parameter net is set to 1: if so then read
 *  the NEWS.DISTRIBUTION file for the distribution lists:
 *      - If this file is not found, then post to all neighbours
 *      - If the path of the item does not contain a node name
 *        then post to all neighbours, otherwise set from_node to the
 *        node which sent the stuff to this node
 *      - Consult the distribution file to see if there is an entry
 *        for from_node - if so then use this to_list as the forwarding
 *        list, (if the list is empty then return. If the from_node node
 *        is not in the list, then send to all neighbours in the SYS
 *        file .
 */

void sys_remote_send(path,newsgroups,distribution,item_file,message_id,net)
  const char *path, *newsgroups, *distribution, *item_file, *message_id;
  int net;
{
  char from_node[FILE_SIZE], *cp;
  int b_size = 0, n_char = 0, nodistentry = 0, b_lines = 0, i, line_size;
  SYS_ENTRY_T *s;
  DIST_ENTRY_T *f = 0;
  SYS_FILTER_T *netdist, *nopath;
  struct stat infile_stat;
  FILE *fpo, *fpi;
#define ART_BUFSIZE_DEFAULT 8192
#define ART_BUFLINES_DEFAULT 64
  static char *data = NULL;
  static int data_size = 0;
  static int *lines = NULL;
  static int lines_size = 0;

  *from_node = '\0';
  if (!(s = readsys())) return;
  sysprv();
  if (stat(item_file,&infile_stat)) {
    nosysprv();
    return;
    }
  nosysprv();
  if (net) {
    if ((f = readnet()) != 0) {
#if DISTFILE
      nodistentry = 1;
#endif
      if ((cp = strchr(path,'!')) != 0) {
        *cp = '\0';
        strnncpy(from_node,sizeof(from_node), (*path==' ')?path+1:path, -1, 0);
        *cp = '!';
        lower_case(from_node);
        while (f && (strcmp(f->from_name,from_node))) f = f->next;
        if (f && !f->to_names) return;
        }
      else {
        f = 0;
        nodistentry = 0;
        }
      }
    }

  if (!f && nodistentry) return;

  sysprv();
  do {
    if (f) {
      netdist = f->to_names;
      while (netdist && (strcmp(netdist->sys_filtnam,s->sys_nodename)))
        netdist = netdist->sys_fnext;
      if (!netdist) continue;
      }
    if (path_match(path,s->sys_nodename)) continue;

      /* check for /node,node filter entries in the path line */
    for (nopath=s->sys_no; nopath; nopath=nopath->sys_fnext) {
      if (path_match(path,nopath->sys_filtnam)) break;
      }
    if (nopath) continue;

    if (!strlen(s->sys_file)) continue;
    if (test_accept(newsgroups,distribution,s->sys_f,s->sys_d)) {
      if (*s->sys_type == 'n') {
	++s->sys_id_count;
        if (*(s->sys_type + 1) == 'x') {
	  if (s->sys_index_file == 0)
	    s->sys_index_file = open_id_file(s);
	  if (s->sys_index_file) {
            add_id(s,message_id);
#ifdef CLOSE_BATCHFILES
            close_id_file(s);
	    s->sys_index_file = 0;
#endif
            }
          }
        else {
	  if (!(fpo = s->sys_fp)) {
	    if (!(fpo = fopen(s->sys_file,"a","shr=nil","mbc=64"))) {
	      sleep(1); /* assume append open due to multiple write - wait a bit -
			  if the append still fails try to open a new version
			  before giving up. */
	      if (!(fpo = fopen(s->sys_file,"a","shr=nil","mbc=64")))
		fpo = fopen(s->sys_file,"w","shr=nil","mbc=64");
	      }
	    else {
	      /*
	      ** Assume that each entry in the collection file is about 40 bytes
	      ** long.  From inspection this seems to be about the right number.
	      ** Only do this calculation once, when the file is opened.  After
	      ** that it's redundant.
	      */
	      
	      fstat(fileno(fpo), &s->sys_statb);
	      s->sys_estimated_article_count = (s->sys_statb.st_size + 39) / 40 ;
	      }
#ifndef CLOSE_BATCHFILES
	    s->sys_fp = fpo;
#endif
	    }
	  if ( s->sys_batch_size &&
             ((s->sys_id_count+s->sys_estimated_article_count)
                 % s->sys_batch_size)) {
	    /*
	    ** By our calculations, the number of articles in the current batch
	    ** file has overflowed the number specified in the news.sys file
	    ** entry.  Close the current batch, open a new one and adjust the
	    ** estimated article count so that we will write the correct number
	    ** of news items into the next batch.
	    */
	    
	    fclose(fpo) ;
	    fpo = fopen(s->sys_file,"w","shr=nil","mbc=64");
	    s->sys_estimated_article_count = - s->sys_id_count ;
#ifndef CLOSE_BATCHFILES
	    s->sys_fp = fpo;
#endif
	    }
          if (fpo) {
            fprintf(fpo,"%s\n",message_id);
	    if (!s->sys_fp)
	      fclose(fpo);
	    }
          }
        }
      else {
	++s->sys_article_count;
        if (b_size == 0) {
	  if (!data) {
	    data = news_calloc(1, ART_BUFSIZE_DEFAULT);
	    data_size = ART_BUFSIZE_DEFAULT;
	    lines = news_malloc(sizeof(*lines)*ART_BUFLINES_DEFAULT);
	    lines_size = ART_BUFLINES_DEFAULT;
	    }
          if (!(fpi = fopen(item_file,"r","mbc=64"))) continue;
          b_size = 0;
	  data[IO_SIZE] = '\0';
          while (fgets(&data[b_size],IO_SIZE,fpi)) {
	    if (lines_size < ++b_lines)
	      lines = news_realloc(lines, sizeof(*lines)*(lines_size *= 2));
            b_size += lines[b_lines-1] = strlen(&data[b_size]);
	    if (data_size < (b_size + IO_SIZE))
	      data = news_realloc(data, data_size *= 2);
	    data[b_size + IO_SIZE] = '\0';
            }
          fclose(fpi);
          }
	s->sys_article_bytes += b_size;

        n_char = (*(s->sys_type + 1) == 'n');
	if (s->sys_fp)
	  fpo = s->sys_fp;
	else {
	  if ( *s->sys_type == 'm' )
	    fpo = fopen(s->sys_file,"w","mbc=64","alq=30","deq=30","shr=nil");
	  else
	    if (!(fpo = fopen(s->sys_file,"a","mbc=64","shr=nil")))
	      fpo = fopen(s->sys_file,"w","mbc=64","alq=30","deq=30","shr=nil");
	  if (fpo)
	    fstat(fileno(fpo), &s->sys_statb);	    
	  }
	if ((s->sys_statb.st_size + infile_stat.st_size) > s->sys_batch_size) {
	  if (fpo)
	    fclose(fpo);
	  fpo = fopen(s->sys_file,"w","mbc=64","alq=30","deq=30","shr=nil");
	  s->sys_statb.st_size = 0;
	  }
	if (!fpo) continue;
#ifndef CLOSE_BATCHFILES
	if ( *s->sys_type != 'm' )
          s->sys_fp = fpo;
#endif
        if (*s->sys_type == 'f') {
	    if (strncmp(item_file, Itm_device, sizeof(Itm_device)-1))
	       get_itm_file_name(message_id,&item_file);
	    if (*(s->sys_type + 1) != 'c') {
	       fprintf(fpo,"%s\n",item_file);
	       s->sys_statb.st_size += 1 + strlen(item_file); 
	      }
            else {
	       fprintf(fpo,"%s %6d\n",item_file, b_size);
	       s->sys_statb.st_size += 8 + strlen(item_file); 
            }		
	  }
        else {
	  /* The rnews information is necessary only for non-mail feeds. */
	  if (*s->sys_type != 'm') {
            if (n_char) fprintf(fpo,"N");
            fprintf(fpo,"#! rnews %d\n",b_size);
	    }
	    
	  /* The infile_stat.st_size is a close enough approximation to use */
	  s->sys_statb.st_size += infile_stat.st_size; 
	  if (n_char)
#define MAX_FPUTSIZE 32750
	    for (i=0, b_size = 0; i < b_lines; ++i) {
	      fprintf(fpo,"N");
	      for (line_size = lines[i]; line_size > 0; ) {
		int write_size = line_size;
		if (write_size > MAX_FPUTSIZE) write_size = MAX_FPUTSIZE;
		fwrite(&data[b_size], write_size, 1, fpo);
		b_size += write_size;
		line_size -= write_size;
		if (line_size) fprintf(fpo, "\nN");
		}
	      }
	  else {
	    int wlen, dlen;
	    char savec = 0, *ep = 0, *dp = data;

	    for (dlen=b_size; dlen>0; dlen-=wlen) {
	      wlen = dlen;
	      if (wlen > MAX_FPUTSIZE) {
		ep = dp + MAX_FPUTSIZE;
		while (*ep != '\n') --ep;
		savec = ep[1];
		ep[1] = '\0';
		wlen = 1 + (ep-dp);
		}
	      fputs(dp,fpo);
	      if (dlen != wlen) ep[1] = savec;
	      dp += wlen;
	      }
	    }
          }
        if (!s->sys_fp)
          fclose(fpo);
        }
      }
    } while ((s = s->sys_enext) != 0);
  nosysprv();
}

/*
 *  path_match
 *
 *  Determine if the incoming net item has been seen on this node
 *  already - check the path value
 */

int path_match(path,test_node)
  const char *path, *test_node;
{
#if DIST_PATH_CHECK
  char match_node[NODE_SIZE];
  int match_node_l;
  char *lpath;

  lpath = news_strdup(path,-1);
  lower_case(lpath);
  strcpy(match_node,"*!");
  strnncat(match_node,sizeof(match_node),-1, test_node,-1, &match_node_l);
  strnncat(match_node,sizeof(match_node),match_node_l, "!*",2, &match_node_l);
  if (wild_match(lpath,match_node)) {news_free(lpath); return(1);}
  match_node[1] = ' ';
  if (wild_match(lpath,match_node)) {news_free(lpath); return(1);}
  if (wild_match(lpath,match_node+2)) {news_free(lpath); return(1);}
  news_free(lpath);
  return(0);
#else
  return(0);
#endif
}

static void clearals()
{
  ALIAS_PTR next;

  for (; aliases_list; aliases_list=next) {
    next = aliases_list->a_next;
    news_free(aliases_list->ngname);
    news_free(aliases_list->alias_name);
    news_free(aliases_list);
    }
}

static void scanalias(s)
  const char *s;
{
  ALIAS_PTR tmp;
  char onews[132], anews[132];

  if (sscanf(s,"%s %s",onews,anews) == 2) {
    for (tmp = aliases_list;  tmp && tmp->a_next;  tmp = tmp->a_next) {}
    if (tmp) {
      tmp->a_next = (ALIAS_PTR) news_malloc(sizeof *tmp);
      tmp = tmp->a_next;
      }
    else tmp = aliases_list = (ALIAS_PTR) news_malloc(sizeof *tmp);
    tmp->ngname = news_strdup(onews,-1);
    tmp->alias_name = news_strdup(anews,-1);
    tmp->a_next = 0;
    }
}

ALIAS_PTR read_aliases()
{
  static int a_mod = 0,
             a_check_time = 0;
  struct stat a_stat;
  int st;

  sysprv();
  if (!a_mod || (a_check_time < time(0))) {
    a_mod = 1;
    a_check_time = (time(0) + RECHECK_TIMER);
    st = stat(ALIAS_FILE,&a_stat);
    if (st || (a_stat.st_mtime != a_mod)) {
      clearals();
      a_mod = 1;
      if (!st && read_config(ALIAS_FILE,scanalias)) a_mod = a_stat.st_mtime;
      }
    }
  nosysprv();
  return(aliases_list);
}

static ALIAS_PTR search_aliases(const char *n, int n_l)
{
  struct als *tmp;
  n_l = (n_l >= 0) ? n_l : strlen(n);

  for (tmp = aliases_list; tmp; tmp = tmp->a_next) {
    if ((strlen(tmp->ngname) == n_l) &&
        !memcmp(tmp->ngname,n,n_l)) break;
    }
  return(tmp);
}

static char *ret_aliases = NULL;

char *aliases(newsgroups, newsgroups_l)
  const char *newsgroups;
  int newsgroups_l;                /* length of string if known; otherwise -1 */
{
  const char *cp1, *cp2;
  ALIAS_PTR alias;
  int ret_aliases_l, cp1_l;
  newsgroups_l = (newsgroups_l >= 0) ? newsgroups_l : strlen(newsgroups);

  read_aliases();

  if ( ret_aliases ) news_free(ret_aliases);    /* clean up old return values */

  if (!aliases_list) {
    ret_aliases = news_strdup(newsgroups,newsgroups_l);
    return(ret_aliases);
    }

  ret_aliases = news_strdup("",0); ret_aliases_l = 0;
  for (cp1=newsgroups; cp1; cp1=cp2) {
    cp2 = strchr(cp1,',');
    cp1_l = cp2 ? cp2-cp1 : strlen(cp1);
    if ((alias = search_aliases(cp1,cp1_l)) != 0) {
      int name_l = strlen(alias->alias_name);
      ret_aliases = news_realloc(ret_aliases,ret_aliases_l+name_l+1);
      strnncat(ret_aliases,ret_aliases_l+name_l+1,ret_aliases_l,
               alias->alias_name,name_l, &ret_aliases_l);
      }
    else {
      ret_aliases = news_realloc(ret_aliases,ret_aliases_l+cp1_l+1);
      strnncat(ret_aliases,ret_aliases_l+cp1_l+1,ret_aliases_l,
               cp1,cp1_l, &ret_aliases_l);
      }
    if (cp2) {
      ret_aliases = news_realloc(ret_aliases,ret_aliases_l+1+1);
      strnncat(ret_aliases,ret_aliases_l+1+1,ret_aliases_l,
               ",",1, &ret_aliases_l);
      cp2++;
      }
    }
  return(ret_aliases);
}

/*
 *  scandistwords
 *
 *  Read a news.distribution line of the form   node node,node,...
 *  and store in local structure
 */

static NAME_LIST *distwords = 0;

static void scandistwords(s)
  const char *s;
{
  char *cp;
  NAME_LIST *tmp;

  if ((cp = strchr(s,' ')) != 0) *cp = '\0';
  tmp = (NAME_LIST *) news_malloc(sizeof *tmp);
  tmp->sys_filtnam = news_strdup(s,-1);
  tmp->sys_fnext = distwords;
  distwords = tmp;
}

/*
 *  readdist
 *
 *  Read the distribution keyword file, and pass all reconstructed lines to
 *  dist_scan().
 */

static NAME_LIST *readdist()
{
  static int d_mod = 0, d_check_time = 0;
  struct stat s_stat;
  int st;

  sysprv();
  if (!d_mod || (d_check_time < time(0))) {
    d_check_time = (time(0) + RECHECK_TIMER);
    st = stat(DISTRIBUTIONS_FILE,&s_stat);
    if (st || (s_stat.st_mtime != d_mod)) {
      clearfilter(distwords);
      d_mod = 1;
      if (!st && read_config(DISTRIBUTIONS_FILE,scandistwords)) d_mod = s_stat.st_mtime;
      }
    }
  nosysprv();
  return(distwords);
}

int check_distribution_string(s)
  const char *s;
{
  NAME_LIST *d, *dt;
  const char *cp1, *cp2;
  int cp1_l;

  if (!(d = readdist())) return(1);
  for (cp1 = s; cp1; cp1 = (cp2 ? cp2+1 : NULL) ) {
    cp2 = strchr(cp1,',');
    cp1_l = cp2 ? cp2-cp1 : strlen(cp1);
    for (dt = d; dt; dt = dt->sys_fnext) {
      if ((strlen(dt->sys_filtnam) == cp1_l) &&
          !memcmp(dt->sys_filtnam,cp1,cp1_l)) return(1);
      }
    }
  return(0);
}

void flush_downstream(msg)
  int msg;
{
  SYS_ENTRY_T *tmp;

  sysprv();
  for (tmp=sysfile; tmp; tmp=tmp->sys_enext) {
    if ((msg) && (tmp->sys_article_count)) {
      printf("- Forwarded: %4d/%d Articles/Bytes to site: %s\n", 
             tmp->sys_article_count, tmp->sys_article_bytes, tmp->sys_nodename);
      tmp->sys_article_count = 0;
      tmp->sys_article_bytes = 0;
    }
    if ((msg) && (tmp->sys_id_count)) {
      printf("- Forwarded: %4d Article ID's to site: %s\n", 
             tmp->sys_id_count, tmp->sys_nodename);
      tmp->sys_id_count = 0;
    }
    if (tmp->sys_fp) {
      fclose(tmp->sys_fp);
      tmp->sys_fp = NULL;
    }
    if (tmp->sys_index_file) {
      close_id_file(tmp);
      tmp->sys_index_file = 0;
    }
    }
  nosysprv();

}
