/*
**++
**  FACILITY:
**	NEWSRC
**
**  ABSTRACT:
**      Manage the per-user newsrc context file for read/unread items,
**      kill filters and marks.
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1989,1990,1991
**  MODIFICATION HISTORY:
**	V6.0-4  glass@vixvax.mgi.com (William H. Glass)  24/3/91
**	- Patch to MARK and UNMARK to correct item selection bug
**	V6.1	 8-Feb-1992	rankin@eql.caltech.edu
**	- Lint cleanup
**	V6.1	 9-Mar-92	robin@lsl.co.uk (Robin Fairbairns)
**	- newsrc treatment - write newsrc.old
**	V6.1	15-Feb-1992	rankin@eql.caltech.edu
**	- Make putl() avoid modifying constant strings
**	V6.1	13-Apr-92	Marc Shannon <synful@DRYCAS.CLUB.CC.CMU.EDU>
**	- hash table lookup for Arbitron records
**	V6.1b1	12-Aug-1992	rankin@eql.caltech.edu
**	- replace `bad_file = ferror(newsrc) | fclose(newsrc);' error check;
**	  `|' doesn't guarantee order of operation like `||' and gcc causes
**	  the fclose to execute before the ferror, resulting in an ACCVIO.
**	V6.1b7	15-May-1993	Charles Bailey  bailey@genetics.upenn.edu
**	- optimize item number lookup
**	V6.1b7	15-Jun-1993	bailey@genetics.upenn.edu
**	- set record size in grprab to NEWS_GRPFIL_RSZ, not sizeof newsgrp,
**	  since newsgrp struct contains volatile data not needed in file
**	V6.1b7	15-Jun-1993	bailey@genetics.upenn.edu
**	- fix bug in noterange() which caused wrong range to be generated if
**	  first item did not exist
**	V6.1b8  10-Sep-1993	mark.martinec@ijs.si
**	- instrument the I/O (both RMS and VAXC RTL i/o) with
**	  error checking and reporting
**	V6.1b8  22-Dec-1993	mark.martinec@ijs.si
**	- some additional error checking
**	V6.1b8  20-Apr-1994	mark.martinec@ijs.si
**	- append error messages accumulated during session to the end
**	  of the new NEWSRC file as comments in case of unexpected errors
**	V6.1b8  24-May-1994 	mark.martinec@ijs.si
**	- avoid calling clsallfiles(n) from fastscan() before files are open
**	V6.1b9	26-May-1994	bailey@genetics.upenn.edu
**	- fixed bug which caused profile DotNewsRc setting to override /DotNewsRC
**	  qualifier on News CL once startup was completed
**	V6.1b9  24-Jun-1994	bailey@genetics.upenn.edu
**	- moved code from read_reg_file() to reg_context() to restore context
**	  to group+item at which last session ended.
**	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)
**	  - added procedure get_tag_word
**	V6.1b9	22-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - rewritten function putl to avoid modifying const parameter string
**	V6.1b9	14-Oct-1994     Mark Martinec   mark.martinec@ijs.si
**	  - in write_reg_file() hopefully fixed the statement:
**	      if (l_ga->grp_topreadnum > l_ga->grp_topnum) l_ga->grp_topreadnum = l_ga->grp_topnum;
**	    which should now handle the case of newsrc file being out of sync
**	    with the highest article number within a group
**	V6.1b9	15-Jan-1995  Charles Bailey  bailey@genetics.upenn.edu
**	  - added support for grp_funread field of ga entry
**	V6.1b10	24-Aug-1995	Mark Martinec   mark.martinec@ijs.si
**	  - added some checking in read_reg_file() to survive certain
**	    syntax errors in the NEWSRC file without ACCVIO
**	V6.1b11	28-Mar-1997	Mark Martinec   mark.martinec@ijs.si
**	  - use new procedure available_proto to make possible
**	    using ANU with different TCP transport library
**	    without changing NEWSRC user files (automatically changes
**	    requested and unavailable TCP library to the one
**	    that is compiled-in with the program).
**	V6.1b11 14-Apr-1997	Mark Martinec   mark.martinec@ijs.si
**	  - replace several calls to strcpy with safer strnncpy
**	  - replace pattern strcpy(news_malloc(strlen(x)+1),x) with news_strdup
**	  - replace pattern strcat(err_oline,x) with err_oline_cat(x)
**	  - change %s to %.<n>s in sprintf to guard against overwriting buffer
**          (with somewhat arbitrary limits)
**	V6.1b12 10-Jun-1997	Mark Martinec   mark.martinec@ijs.si
**	  - hash_find() used to cause 60% of all page faults with
**	    large number (16000) of newsgroups. Added field arb_readtime
**	    to the GRP structure, avoiding to keep group names in two locations
**	    and avoiding hash+linear searches through names.
**--
**/

#ifdef vaxc
#module NEWSRC "V6.2"
#endif

#define _NEWSRC_C
#define module_name "NEWSRC"

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

time_t sys_reg_time = (time_t) 0;

extern int profile_flags;

static
int new_vals,
    new_ci,
    old_dir_lev,
    force_dir = 0;

static char new_cg_name[GRPNAMELEN];

static int dotnewsrcexists = 0;


/*
 * putl
 *
 * Support routine to write string (possibly including newlines)
 * into separate lines, each no longer than PUTL_LIMIT characters,
 * not counting the backslash character, which is inserted at the end
 * of the wrapped lines.
 */

#define PUTL_LIMIT 254	/* max 255 including the '\' at the line break */
static
int putlc = 0;     /* number of characters written to this line so far */

void putl(line,f)
  const char *line;
  FILE *f;
{
  char *lf;
  int len,l;
  int line_l;

  for (line_l=strlen(line); line_l>0; ) {
    lf = (char *) memchr(line,'\n',line_l);
    len = lf ? lf-line : line_l;
    while (len > 0) {
      l = len;
      if (putlc+l > PUTL_LIMIT) l = max(0,PUTL_LIMIT-putlc);
      fwrite(line,sizeof(char),l,f); _ck_put(f); putlc += l;
      line += l; line_l -= l; len -= l;
      if (len > 0)
        { fwrite("\\\n",sizeof(char),2,f); _ck_put(f); putlc = 0; }
      }
    if (lf) {
      fwrite("\n",sizeof(char),1,f); _ck_put(f); putlc = 0;
      line++; line_l--;
      }
    }
}
#undef PUTL_LIMIT

/*
 *  read_reg_file
 *  reg_context
 *
 *  Read the register text file and fill out the ga array
 *
 *  register file format:
 *  line 1: ({} ==> variable, [] ==> optional field)
 *  [~{nntp_server} {nntp_proto}~][>]{rtime} [{curgrp} {curitm}]]^{level} [|]@{class}
 *          {nntp_server} = name of NNTP server used
 *          {nntp_proto}  = transport protocol over which connection to
 *                          {nntp_server} was made
 *          >        = present ==> FastLoad profile setting
 *          {rtime}  = time of last scan for new newsgroups
 *          {curgrp} = name of current newsgroup, if any
 *          {curitm} = number of current item, if any
 *          {level}  = newsgroup directory type (ALL/REG/UNREG/NEW)
 *                     selected when file written
 *          |        = present ==> DotNewsRC profile setting
 *          {class}  = class selected when file written
 *
 *  line n:  (<> ==> variable, [],{} are literal)
 *  <grpname>: (<regpri>) {<classes>} [<unread>,<topnum>] #<arbtime># $<topread>,<funread>$ <readlist>
 *          <grpname> = name of newsgroup for this entry
 *          <regpri>  = registered priority of newsgroup (from REG/PRIO)
 *          <classes> = comma-separated list of classes to which this
 *                      newsgroup belongs
 *          <unread>  = count of unread items in this group
 *          <topnum>  = highest item number in this group
 *          <topread> = highest item number of a read item
 *          <funread> = lowest item number of an unread item
 *          <arbtime> = group access time used by Arbitron
 *          <readlist> = space-separated list of entries describing read items,
 *                       where each entry is one of
 *                         n        single item n
 *                         <n       all items up to in (incl n)
 *                         |n1=n2   all items in range from n1 to n2 (incl)
 *                         (values for n are item numbers)
 *
 *  reg_context finishes re-establishing the old news context
 *  (The separation into a second procedure is necessary as there is
 *  a call to init_screen after read_reg_file is invoked)
 */

/* preserve News CL setting of usedotnewsrc across
   read_reg_file into reg_context */
static int clusedotnewsrc;

int read_reg_file()
{
  char *in_line, *itm_list, *old_dir_typ;
  int systime, regprio, g = 0, ca_i = 1, ca_msize, old_unread, old_topnum;
  int newsrc_line_number;
  int status;
  FILE *dotnewsrc = NULL;
  GRP_PTR l_ga, *ca;
  char empty_str[1] = "";  /* this is used instead of a literal "" to avoid
                              the 'const' conflict with read-only literals */

  ca = (GRP_PTR *) news_malloc(((ca_msize = ga_size + 1)) * (sizeof *ca));
  for (g = 1; g <= ga_size ; ++g) {
    ga[g]->grp_flags &= ~NEWS_M_OLDGROUP;
    ga[g]->grp_topreadnum = 0;
    }
  *profile_dirstr = '\0';
  profile_display_lines = profile_display_postmark = nntp_anu_news_server;
  newsrc_line_number = 0;
  if (newsrc) { if (fseek(newsrc,0,0)) _ck_seek(newsrc); }
  else {
    if (!(newsrc = fopen(news_register,"r","mbc=40"))) {
      FILE *fpold;
      char buff[512];

      if ((fpold = fopen("SYS$LOGIN:NEWS_GROUPS.REGISTER","r","mbc=40")) != 0) {
        newsrc = fopen(news_register,"w","mbc=40");
        if (_ck_open_w(newsrc,news_register)) {
          while (fgets(buff,sizeof(buff),fpold)) {
            _ck_get(fpold);
            fputs(buff,newsrc); _ck_put(newsrc);
            }
          if (fclose(newsrc)) _ck_close(newsrc);
          newsrc = 0;
          }
        if (fclose(fpold)) _ck_close(fpold);
        fpold = 0;
        if (!(newsrc = fopen(news_register,"r","mbc=40"))) {
          news_free(ca);
          return(0);
	  }
	delete_file_versions("SYS$LOGIN:NEWS_GROUPS.REGISTER");
        }
      else {
        sysprv();
        if (!(newsrc = fopen("NEWS_MANAGER:NEWSRC.TEMPLATE","r","mbc=40"))) {
          nosysprv();
          news_free(ca);
          return(0);
          }
        first_time_user = 1;
        nosysprv();
        }
      }
    }

  if (!(in_line = fgetl(newsrc))) {
    news_free(ca);
    return(0);
    }
  newsrc_line_number++;
  old_dir_lev = 0;
  chop_str(in_line,'\n');
  chop_str(in_line,'#');

        /* previous class name is stored as "@classname" as last
           field of the first line of the register file */
  *n_class_name = '\0';
  if ((old_dir_typ = chop_str_plus(in_line,'@')) != 0) {
    strnncpy(n_class_name,n_class_name_m, old_dir_typ,-1, 0);
    lower_case(n_class_name);
    }

  if (chop_str(in_line,'|')) profile_flags |= PROFILE_DOTNEWSRC;
  usedotnewsrc = (usedotnewsrc < 2) ? profile_flags & PROFILE_DOTNEWSRC
                                    : (clusedotnewsrc = usedotnewsrc) - 2;
  if (usedotnewsrc) {
    if ( (dotnewsrc = fopen("sys$login:.newsrc","r")) ) {
      dotnewsrcexists = _ck_open_r(dotnewsrc,"sys$login:.newsrc");
      if (dotnewsrc && !dotnewsrcexists) {
        fclose(dotnewsrc);
        dotnewsrc = 0;
        }
      }
    }

  if ((old_dir_typ = chop_str_plus(in_line,'^')) != 0) {
    if (sscanf(old_dir_typ,"%d",&old_dir_lev) != 1) old_dir_lev = 0;
    }

  if (*in_line == '~') {
    ++in_line;
    if ((in_line = strchr(in_line,'~')) != 0) ++in_line;
    }

#if FAST_LOAD
  if (*in_line == '>') {
    fast_loading = 1;
    if (!all_loaded) start_group_load();
    ++in_line;
    }
#endif
  if ((new_vals = sscanf(in_line,"%X %s %d",&systime,new_cg_name,&new_ci)) >= 1) sys_reg_time = systime;

  while ((in_line = fgetl(newsrc)) != 0) {
    newsrc_line_number++;
    if (!strlen(in_line) || in_line[0] == '\n') continue;
    if (in_line[0] == '#') continue;
    if (!strcmp(in_line,"MARKLIST\n")) {
      mark_read(newsrc);
      break;
      }
    if (!strcmp(in_line,"KILLLIST\n")) {
      kill_read(newsrc);
      break;
      }
    if (!strcmp(in_line,"PROFILE\n")) {
      profile_read(newsrc);
      break;
      }
    chop_str(in_line,'\n');
    if ((itm_list = chop_str_plus(in_line,':')) != 0) itm_list++;
    if (!itm_list) { _ck_sys(NEWS$_BADRCLINE,newsrc_line_number,0); continue; }
#if FAST_LOAD
    if (fast_loading && !all_loaded && !(g = read_group(in_line))) continue;
#endif
    if (!(g = ga_exact_name(in_line))) {
      if (!init_scanning) {
        sprintf(err_oline,"Old newsgroup: %.200s, deleted from NEWSRC",in_line);
        err_line(err_oline);
	}
      continue;
      }
    l_ga = ga[g];
    if (!(l_ga->grp_flags & NEWS_M_OLDGROUP)) {
      if (ca_i >= ca_msize) ca = (GRP_PTR *) news_realloc(ca,((ca_msize += 20)) * (sizeof *ca));
      ca[ca_i++] = l_ga;
      }
    l_ga->grp_flags |= NEWS_M_OLDGROUP;   /* flag this newsgroup as not new! */

    if (!curr_g) curr_g = g;
    if ((itm_list) && (*itm_list == '(')) {
      if (*++itm_list == '-') l_ga->grp_reg = 0;
      else {
        regprio = 0;
        sscanf(itm_list,"%d",&regprio);
        if (regprio > 255) regprio = 255;
        l_ga->grp_reg = regprio;
        }
      itm_list = strchr(itm_list,')');
      if (!itm_list) { _ck_sys(NEWS$_BADRCLINE,newsrc_line_number,0); continue;}
      itm_list += 2;
      }

    if ((itm_list) && (*itm_list == '{')) {
      char *cp1, *cp2;

      ++itm_list;
      cp1 = chop_str(itm_list,'}');
      if (!cp1) { _ck_sys(NEWS$_BADRCLINE,newsrc_line_number,0); continue; }
      for (; itm_list; itm_list=cp2) {
        cp2 = chop_str_plus(itm_list,',');
        if (!*itm_list) break;
        add_class_member(itm_list,l_ga->grp_num,1);
        }
      if (cp1) itm_list = cp1 + 2;
      }

    if ((itm_list) && (*itm_list == '[')) {
      old_unread = old_topnum = 0;
      sscanf(itm_list,"[%d,%d]",&old_unread,&old_topnum);
      itm_list = strchr(itm_list,']');
      if (!itm_list) { _ck_sys(NEWS$_BADRCLINE,newsrc_line_number,0); continue;}
      itm_list += 2;
      }
    else {
      old_unread = l_ga->grp_count;
      old_topnum = l_ga->grp_topnum;
      }

    if ((itm_list) && (*itm_list == '#')) {
      int grp_read_time = 0;

      ++itm_list;
#if ARBITRON
      sscanf(itm_list,"%X",&grp_read_time);
      if (grp_read_time > l_ga->arb_readtime) l_ga->arb_readtime= grp_read_time;
#endif
      itm_list = strchr(itm_list,'#');
      if (!itm_list) { _ck_sys(NEWS$_BADRCLINE,newsrc_line_number,0); continue;}
      if (itm_list) itm_list += 2;
      else { empty_str[0] = '\0'; itm_list = empty_str; }
      }

    if ((itm_list) && (*itm_list == '$')) {
      int grp_funread = 0, grp_readno = 0;

      ++itm_list;
      sscanf(itm_list,"%d,%d",&grp_readno,&grp_funread);
      l_ga->grp_topreadnum = grp_readno;
      l_ga->grp_funread = grp_funread;
      itm_list = strchr(itm_list,'$');
      if (!itm_list) { _ck_sys(NEWS$_BADRCLINE,newsrc_line_number,0); continue;}
      if (itm_list) itm_list += 2;
      else { empty_str[0] = '\0'; itm_list = empty_str; }
      }

    if (!l_ga->grp_count) {
      l_ga->grp_unread = 0;
      continue;
      }

    if ((!(profile_flags & PROFILE_RCREG) || l_ga->grp_reg) &&
        (!usedotnewsrc || !dotnewsrcexists))
      l_ga->grp_reg_text = news_strdup(itm_list,-1);
    l_ga->grp_unread = old_unread + l_ga->grp_topnum - old_topnum;
    if (l_ga->grp_unread > l_ga->grp_count) l_ga->grp_unread = l_ga->grp_count;
    }
  if (fclose(newsrc)) _ck_close(newsrc);
  newsrc = 0;
  set_level(1);

  if (profile_filter && !reorder_groups) {
    ca_i = 1;
    for (g = 1; g <= ga_size; ++g) {
      if ((ga[g]->grp_flags & NEWS_M_OLDGROUP) && (ca_i <= ga_size)) {
        if (ca_i >= ca_msize) ca = (GRP_PTR *) news_realloc(ca,((ca_msize += 20)) * (sizeof *ca));
        ca[ca_i++] = ga[g];
        }
      }
    }

  if (usedotnewsrc && dotnewsrcexists) { 
    char *readnums, *regtext, *cp1, *cp2;
    int reg, i;

    while ((in_line = fgetl(dotnewsrc)) != 0) {
      chop_str(in_line,'\n');
      if (!(readnums = strchr(in_line,'!'))
       && !(readnums = strchr(in_line,':'))) continue;
      reg = (*readnums == ':');
      *readnums++ = '\0';
#if FAST_LOAD
      if (fast_loading && !all_loaded && !(g = read_group(in_line))) continue;
#endif
      if (!(g = ga_exact_name(in_line))) {
        if (!init_scanning) {
          sprintf(err_oline,"Old newsgroup: %.200s, deleted from .newsrc",in_line);
          err_line(err_oline);
	  }
        continue;
        }
      l_ga = ga[g];
      if (!(l_ga->grp_flags & NEWS_M_OLDGROUP)) {
        if (ca_i >= ca_msize) ca = (GRP_PTR *) news_realloc(ca,((ca_msize += 20)) * (sizeof *ca));
        ca[ca_i++] = l_ga;
        }
      l_ga->grp_flags |= NEWS_M_OLDGROUP;   /* flag this newsgroup as not new! */
      if (!curr_g) curr_g = g;
      if (reg) l_ga->grp_reg = max(1,l_ga->grp_reg);
      else l_ga->grp_reg = 0;
      blank_strip(readnums);   /* remove all blanks */
      cp1 = cp2 = readnums;    /* set up to convert .newsrc string */
      i = 0;
      while (*cp2) if (*cp2++ == '-') i++;  /* how much space do we need ? */
      i = strlen(readnums) + i + 2;
      regtext = l_ga->grp_reg_text = news_malloc(i);
      *regtext = '\0';
      /* Some newsreader programs write .newsrc with " " or "0" if no items
       * have been read in a newsgroup. Replace this with "<0" in newsrc. */
      if (!*readnums || (*readnums == '0' && *(readnums+1) == '\0'))
        strcpy(regtext,"<0"); 
      else {
        if (*cp1 == '1' && *(cp1+1) == '-') {
          *regtext++ = '<';
          readnums += 2;
          cp1 = readnums;
          }
        while (cp1 <= cp2) {  /* and convert ranges to ANU format */
          while (*cp1 && (*cp1 != ',')) cp1++;
          *cp1++ = '\0';
          if (strchr(readnums,'-')) {
            *regtext++ = '|';
            while (*readnums != '-') *regtext++ = *readnums++;
            *regtext++ = '=';
            readnums++;
            }
          while (*readnums) *regtext++ = *readnums++;
          *regtext++ = ' ';
          readnums = cp1;
          }
        *(regtext-1) = '\0';
        }
      }
    if (fclose(dotnewsrc)) _ck_close(dotnewsrc);
    }

 {
  static char local_class[] = "$local", net_class[] = "$net";

  for (g = 1; g <= ga_size; ++g) {
    if (   include_all_groups
        && !(ga[g]->grp_flags & NEWS_M_OLDGROUP)
        && (ca_i <= ga_size)) {
      if (ca_i >= ca_msize) ca = (GRP_PTR *) news_realloc(ca,((ca_msize += 20)) * (sizeof *ca));
      ca[ca_i++] = ga[g];
      }
    add_class_member(((ga[g]->grp_flags & NEWS_M_LOCAL)
		     ? local_class : net_class), ga[g]->grp_num, 0);
#if ARBITRON
    ga[g]->grp_flags &= ~NEWS_M_READTHISSESSION;
#endif
    }
  remove_duplicates_class(local_class);
  remove_duplicates_class(net_class);
 }

  if (profile_filter || reorder_groups) {
    for (g = 1; g <= ga_size; ++g) {
      if (g < ca_i) ga[g] = ca[g];
      }
    ga_size = ca_i - 1;
    }

  sysprv();
  if ((newsrc = fopen("NEWS_MANAGER:NEWS.CLASSES","r")) != 0) {
    char class[132], *cp, *cp2, *cp3;
    while ((in_line = fgetl(newsrc)) != 0) {
      chop_str(in_line,'#');
      chop_str(in_line,'\n');
      strip_compress_lower(in_line);
      if (!strlen(in_line)) continue;
      if (!(cp = chop_str_plus(in_line,' '))) continue;
      strcpy(class, (*in_line == '$') ? "" : "$");
      strnncat(class,sizeof(class),-1, in_line,-1, 0);
      if (!strcmp(class,"$local") || !strcmp(class,"$net")) continue;
      for (cp2=cp; cp2; cp2=cp3) {
        cp3 = cp2;
        while (isgraph(*cp3) && (*cp3 != ',')) ++cp3;
        if (*cp3) *cp3++ = '\0'; else cp3 = 0;
        for (g = 1; g <= ga_size; ++g) {
          if (wild_match(ga[g]->grp_name,cp2))
            add_class_member(class,ga[g]->grp_num,1);
          }
        }
      }
    if (fclose(newsrc)) _ck_close(newsrc);
    newsrc = 0;
    }
  nosysprv();
  news_free(ca);
  return(0);
}

int reg_context()
{
  int i, new_cg = 0;
  struct dir_class *ch;

  line_editing = (profile_flags & PROFILE_LINEEDIT) != 0;
  if (clusedotnewsrc) {
    usedotnewsrc = clusedotnewsrc - 2;
    clusedotnewsrc = 0;
  }
  else usedotnewsrc = (profile_flags & PROFILE_DOTNEWSRC) != 0;
  curr_class = 0;
  if (*n_class_name) {
    for (ch = c_head; ch; ch = ch->c_next) {
      if (!strcmp(n_class_name,ch->c_name)) { curr_class = ch; break; }
      }
    }

  if ((new_vals >= 2) && (new_cg = ga_exact_name(new_cg_name))) curr_g = new_cg;
  else new_vals = 2;

  if (smg_active) {
    if (force_dir) do_dir(force_dir,0);
    else {
      char *gotenv, try_command[256];
      int try_command_l;

      if ((!*profile_dirstr) && (gotenv = news_getenv("NEWS_DEFAULT_DIRECTORY",0))) {
	strnncpy(try_command,sizeof(try_command),
                 "DIRECTORY/",-1, &try_command_l);
	strnncat(try_command,sizeof(try_command),try_command_l,
                 gotenv,-1, &try_command_l);
	if (cli$dcl_parse(c$dsc(try_command),CLICMDTBL,0,0,0) & 1)
	  strnncpy(profile_dirstr,profile_dirstr_m, gotenv,-1, 0);
	}
      if (*profile_dirstr) {
	strnncpy(try_command,sizeof(try_command),
                 "DIRECTORY/",-1, &try_command_l);
	strnncat(try_command,sizeof(try_command),try_command_l,
                 profile_dirstr,-1, &try_command_l);
	if (cli$dcl_parse(c$dsc(try_command),CLICMDTBL,0,0,0) & 1) {
          cli$dispatch();
          return(0);
          }
	else *profile_dirstr = '\0';
        }
      if ((!*profile_dirstr) && old_dir_lev)
        do_dir(old_dir_lev,curr_class != (struct dir_class *) 0);
      }
    if (new_cg && !ga[new_cg]->grp_display_indx) {
      ga[new_cg]->grp_display_indx = ++grp_display_size;
      if (smg_active) screen_map_dir();
      else newsgroup_dir();
      }  
    }

  if (new_cg) cur_set_grp(new_cg);
  if ((new_vals == 3) && (ga[curr_g]->grp_count)) {
    set_level(2);
    new_ci = max(new_ci,min(ga[curr_g]->grp_funread,ga[curr_g]->grp_topnum));
    if (find_itm_by_num(curr_g,new_ci,&i) || get_itm_by_num(curr_g,new_ci,&i))
      cur_set_itm(curr_g,i);
    if (new_ci <= ga[curr_g]->grp_ia[1].itm_num)
      return(cur_up_itm(curr_g,ga[curr_g]->grp_count,0));
    return(0);
    }
  if (new_vals < 2) selnew(1);
  return(0);
}

/*
 *  write_reg_file
 *
 *  Write the register text file from the ga array
 */

static char *cvrtformat(s) char *s;
{
  static char *cvtbuffer = 0;
  char *c, *cv;

  if (strchr(s,'.')) return(s);
  if (cvtbuffer) news_free(cvtbuffer);
  cv = cvtbuffer = news_malloc(strlen(s)+3);
  
  for (c=s; *c==' '; c++) {}
  for (; *c; c++) {
    if (*c == ' ') *cv++ = ',';
    else if (*c == '=') *cv++ = '-';
    else if (isdigit(*c)) *cv++ = *c;
    else if (*c == '<') {
      c++;
      if (*c == '0') continue;
      *cv++ = '1';
      *cv++ = '-';
      *cv++ = *c;
      }
    }
  *cv = '\0';
  return(cvtbuffer);
}

int get_device_class(f)
  FILE *f;
{
  $DESCRIPTOR(dev_name_dsc,0);
  unsigned short iosb[4];
  struct stat buff;
  static unsigned int devclass = 0;
  int status;

#ifdef __DECC
#pragma member_alignment save
#pragma nomember_alignment   /* no member alignment - this is a VMS structure */
#endif
  struct { unsigned short buflen;
           unsigned short code;
           unsigned int * bufadr;
           unsigned int * rlenadr;
           unsigned int end_of_list; } itmlst
             = { sizeof devclass, DVI$_DEVCLASS, &devclass, 0, 0 };
#ifdef __DECC
#pragma member_alignment restore
#endif

  if (fstat(fileno(f),&buff)) {
    report_cio_error_routine(NEWS$_STATFAIL,f,0,module_name_str,__LINE__);
    return 0;
    }
  else {
    dev_name_dsc.dsc$a_pointer = buff.st_dev;
    dev_name_dsc.dsc$w_length = strlen(buff.st_dev);
    _c$cks(sys$getdviw(0,0,&dev_name_dsc,&itmlst,iosb,0,0,0));
    _c$cks(iosb[0]);
    return devclass;
    }
  return 0;
}

int write_reg_file()
{
  char in_line[512], n_r_c[256], n_r_n[256], *cp;
  int f_itm,
      l_itm,
      i = 1,
      g;
  ITM_PTR l_ia;
  GRP_PTR l_ga;
  int newsrc_device_class = 0;
  FILE *dotnewsrc = NULL;
  int dotnewsrc_device_class = 0;

  if (usedotnewsrc) {
    dotnewsrcexists = (dotnewsrc = fopen("sys$login:.newsrc","w")) != 0;
    if (!_ck_open_w(dotnewsrc,"sys$login:.newsrc")) dotnewsrcexists = 0;
    if (dotnewsrcexists) dotnewsrc_device_class = get_device_class(dotnewsrc);
    }

  log_to_usage_file (1,0);
  log_to_usage_file (0,0);

  if (!(newsrc = fopen(news_register,"r"))) newsrc = fopen(news_register,"w");
  else i = 0;
  if (!_ck_open_r(newsrc,news_register)) return(0);
  strnncpy(n_r_n,sizeof(n_r_n), fgetname(newsrc,n_r_c,1),-1, 0);
  newsrc_device_class = get_device_class(newsrc);
  if (fclose(newsrc)) _ck_close(newsrc);
  newsrc = 0;

  if (i && (newsrc_device_class == DC$_DISK))
    { if (delete(n_r_c)) _ck_delete(n_r_c); }

  if (!(cp = strrchr(n_r_n,'.'))) cp = strchr(n_r_n,';');
  if (cp) strcpy(cp,".tmp");

  chop_str(n_r_c,';');

  newsrc = fopen(n_r_n,"w","mbc=40");
  if (!_ck_open_w(newsrc,n_r_n)) return(0);
  newsrc_device_class = get_device_class(newsrc);

  if (nntp_client)
    { if (fprintf(newsrc,"~%s %d~",nntp_node,nntp_proto) < 0) _ck_put(newsrc); }
#if FAST_LOAD
  if (profile_flags & PROFILE_FASTLOAD)
    { if (fprintf(newsrc,">") < 0) _ck_put(newsrc); }
#endif
  /* write out the system time */
  if (fprintf(newsrc,"%lX",sys_reg_time) < 0) _ck_put(newsrc);
  /* write out the current group and item only if the group is actually
     displayed (i.e. ignore curr_g if the newsgroup dir is empty so we
     don't claim that the first newsgroup in ga will be 'current'
     when News is next invoked - curr_g defaults to 1 if there is no
     current newsgroup) */
  if (curr_g && ga[curr_g]->grp_display_indx) {
    if (fprintf(newsrc," %s",ga[curr_g]->grp_name) < 0) _ck_put(newsrc);
    if ((news_context > 1) && (curr_i > 0)) {
      if (fprintf(newsrc," %d",ga[curr_g]->grp_ia[curr_i].itm_num) < 0)
        _ck_put(newsrc);
      }
    }
  if (fprintf(newsrc,"^%d",cur_dir_type+1) < 0) _ck_put(newsrc);
  if (profile_flags & PROFILE_DOTNEWSRC)
    { if (fprintf(newsrc,"|") < 0) _ck_put(newsrc); }
  if (curr_class)
    { if (fprintf(newsrc," @%s",curr_class->c_name) < 0) _ck_put(newsrc); }
  for (g = 1; g <= ga_size; ++g) {
    if (ga[g]->grp_reg && ga[g]->grp_unread) {
      if (fprintf(newsrc,"#") < 0) _ck_put(newsrc);
      break;
      }
    }
  putl("\n",newsrc);

                        /* write out an entry for every newsgroup */
  for (g = 1; g <= ga_size; ++g) {
    if (ga[g]->grp_flags & NEWS_M_MAILGROUP) continue;
    l_ga = ga[g];
    l_ia = l_ga->grp_ia;


    if (usedotnewsrc && dotnewsrcexists) {
      if (fprintf(dotnewsrc,"%s%c ",l_ga->grp_name,(l_ga->grp_reg?':':'!')) < 0)
        _ck_put(dotnewsrc);
      if (!l_ia) {
        if (l_ga->grp_reg_text) {
          putl(cvrtformat(l_ga->grp_reg_text),dotnewsrc);
          putl("\n",dotnewsrc);
          }
        else putl("0\n",dotnewsrc);
        }
      else {                  /* Otherwise write out the gory details */
        f_itm = 0;
        for (i=1; i <= l_ga->grp_count; i++) {
          if ((l_ia[i].itm_flags & NEWS_M_UNREAD) ||
              (i > 1 && l_ia[i].itm_num - 1 != l_ia[i-1].itm_num)) {
            l_itm = i > 1 ? l_ia[i-1].itm_num : l_ia[1].itm_num - 1;
            *in_line = '\0';
            if (!f_itm) {
              if (l_itm > 1) sprintf(in_line,"1-%d",l_itm);
              else sprintf(in_line,"1");
              }
            else if (f_itm != l_itm) sprintf(in_line,",%d-%d",f_itm,l_itm);
            else sprintf(in_line,",%d",l_itm);
            if (*in_line) putl(in_line,dotnewsrc);
            while ((i <= l_ga->grp_count) && (l_ia[i].itm_flags & NEWS_M_UNREAD)) ++i;
            if (i <= l_ga->grp_count)
              f_itm = l_ia[i].itm_num;
            else {
              sprintf(in_line,"\n");
              putl(in_line,dotnewsrc);
              i = 0;
              break;
              }
            }
          }
        if (i > l_ga->grp_count) {
          l_itm = l_ia[i-1].itm_num;
          *in_line = '\0';
          if (!f_itm) {
            if (l_itm > 1) sprintf(in_line,"1-%d\n",l_itm);
            else sprintf(in_line,"1\n");
            }
          else if (f_itm != l_itm) sprintf(in_line,",%d-%d\n",f_itm,l_itm);
          else sprintf(in_line,",%d\n",l_itm);
          if (*in_line) putl(in_line,dotnewsrc);
          }
        }
      }

#if FAST_LOAD
    if (!ga[g]->grp_reg && fast_loading) {
#if ARBITRON
      if (!(l_ga->grp_flags & NEWS_M_READTHISSESSION)) {
        if (!l_ga->grp_count) continue;
        if (l_ga->grp_topreadnum < l_ga->grp_firstnum) continue;
        }
#else
      continue;
#endif
      }
#endif
                        /* write out name, reg prio, unread count, topnum */
    sprintf(in_line,"%.200s: (%d) ",l_ga->grp_name,l_ga->grp_reg);
    putl(in_line,newsrc);

    if (l_ga->grp_topreadnum > l_ga->grp_topnum)
      l_ga->grp_topreadnum = l_ga->grp_topnum;
    sprintf(in_line,"%.400s[%d,%d]",class_list(l_ga->grp_num),l_ga->grp_unread,l_ga->grp_topnum);
    putl(in_line,newsrc);

#if ARBITRON
    if (l_ga->grp_flags & NEWS_M_READTHISSESSION)
         sprintf(in_line," #%lX#",time(0));
    else sprintf(in_line," #%X#",l_ga->arb_readtime);
    putl(in_line,newsrc);
#endif

    if (l_ga->grp_funread > l_ga->grp_topnum+1) {
   /* news_assert_nonfatal(l_ga->grp_funread <= l_ga->grp_topnum+1); */
      l_ga->grp_funread = l_ga->grp_topnum+1;
      }
    sprintf(in_line," $%d,%d$",l_ga->grp_topreadnum,l_ga->grp_funread);
    putl(in_line,newsrc);

    if ((profile_flags & PROFILE_RCREG) && !l_ga->grp_reg) {
      putl(" <0\n",newsrc);
      continue;
      }

    if (!l_ga->grp_count) {
      sprintf(in_line," <%d\n",l_ga->grp_topnum);
      putl(in_line,newsrc);
      continue;
      }

                        /* if not mapped into memory then check if the
                           old reg string still exists - if so then
                           write it out. */
    if (!l_ia) {
      if (l_ga->grp_reg_text) {
        putl(" ",newsrc);
        putl(l_ga->grp_reg_text,newsrc);
        putl("\n",newsrc);
        }
      else putl(" <0\n",newsrc);
      continue;
      }

                        /* Otherwise write out the gory details */

    f_itm = 0;
    for (i=1; i <= l_ga->grp_count; i++) {
      if ((l_ia[i].itm_flags & NEWS_M_UNREAD) ||
          (i > 1 && l_ia[i].itm_num - 1 != l_ia[i-1].itm_num)) {
        l_itm = i > 1 ? l_ia[i-1].itm_num : l_ia[1].itm_num - 1;
        *in_line = '\0';
     /* news_assert_nonfatal(l_itm <= l_ga->grp_topnum); */
        l_itm = min(l_itm, l_ga->grp_topnum);
        if (!f_itm) sprintf(in_line," <%d",l_itm);
        else if (f_itm != l_itm) sprintf(in_line," |%d=%d",f_itm,l_itm);
        else sprintf(in_line," %d",l_itm);
        if (*in_line) putl(in_line,newsrc);
        while ((i <= l_ga->grp_count) && (l_ia[i].itm_flags & NEWS_M_UNREAD)) ++i;
        if (i <= l_ga->grp_count)
          f_itm = l_ia[i].itm_num;
        else {
          sprintf(in_line,"\n");
          putl(in_line,newsrc);
          i = 0;
          break;
          }
        }
      }
    if (i > l_ga->grp_count) {
      l_itm = l_ia[i-1].itm_num;
      *in_line = '\0';
   /* news_assert_nonfatal(l_itm <= l_ga->grp_topnum); */
      l_itm = min(l_itm, l_ga->grp_topnum);
      if (!f_itm) sprintf(in_line," <%d\n",l_itm);
      else if (f_itm != l_itm) sprintf(in_line," |%d=%d\n",f_itm,l_itm);
      else sprintf(in_line," %d\n",l_itm);
      if (*in_line) putl(in_line,newsrc);
      }
    }
  mark_write(newsrc);
  kill_write(newsrc);
  profile_write(newsrc);
  if (problems_encountered) messages_write(newsrc);
  putlc = 0;
  if (fclose(newsrc)) _ck_close(newsrc);
  if (usedotnewsrc && dotnewsrcexists)
    if (fclose(dotnewsrc)) _ck_close(dotnewsrc);
  newsrc = 0;
/*
 *  this was the old code:
 *
 *    (* ferror() and fclose() must not be combined into a single expression *)
 *  bad_file  = ferror(newsrc);	(* get status *)
 *  bad_file |= fclose(newsrc);	(* close file *)
 *  if (bad_file) {	(* if any error occured writing or flushing, abort *)
 *    sprintf(err_oline,"ERROR: unable to update NEWSRC; status %d",bad_file);
 *    write_err_line(err_oline);   (* show the error *)
 *    delete_file_versions(n_r_n); (* cleanup tmp files *)
 *    sleep(5); 		(* give some time to see the error msg *)
 *    return(bad_file);		(* return with status *)
 *    }
 */
  if (newsrc_device_class == DC$_DISK) {
    if (rename(n_r_n,n_r_c))	/* rename tmp to newsrc name */
      _ck_rename(n_r_n,n_r_c);
    delete_file_versions(n_r_n);  /* delete all tmp versions */
    strcpy(n_r_n,n_r_c);          /* fin with tmp name - save newsrc name */
    if (problems_encountered) {   /* issue warning */
      struct dsc$descriptor_s fn_dsc = { 0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0 };
      fn_dsc.dsc$w_length = strlen(n_r_n); fn_dsc.dsc$a_pointer = n_r_n;
      report_sys_error_routine(NEWS$_CHECKNEWSRC,(int)&fn_dsc,0,0,0);
      if (usedotnewsrc) {
        char dot[] = "sys$login:.newsrc";
        fn_dsc.dsc$w_length = sizeof dot - 1; fn_dsc.dsc$a_pointer = dot;
        report_sys_error_routine(NEWS$_CHECKNEWSRC,(int)&fn_dsc,0,0,0);
         }
      }
    else {
      int ll;
      strnncat(n_r_c,sizeof(n_r_c),-1, ";-1",3, &ll);    /* purge newsrc file */
      delete_file_versions(n_r_c);
      ll -= 3; n_r_c[ll] = '\0';                   /* chop off version number */
      strnncat(n_r_n,sizeof(n_r_n),-1, ";1",2, 0);
      if (rename(n_r_c,n_r_n)) _ck_rename(n_r_c,n_r_n);/* rename newsrc to ;1 */
      if (usedotnewsrc && dotnewsrcexists) {
        delete_file_versions("sys$login:.newsrc;-1");
        if (rename("sys$login:.newsrc;","sys$login:.newsrc;1"))
            _ck_rename("sys$login:.newsrc;","sys$login:.newsrc;1");
        }
      }
    }
  return(0);
}

int quadcmp(f1,f0,s1,s0)
    unsigned int f1,f0,s1,s0;
{
    if (f1 > s1) return(1);
    if (f1 < s1) return(-1);
    if (f0 > s0) return(1);
    if (f0 < s0) return(-1);
    return(0);
}
/*
 *  find_tag
 *
 *  Return a pointer to the record which has a matching tag - start the
 *  search from tag record c
 */

struct marks *find_tag(t,c,wild)
    const char *t;
    struct marks *c;
    int wild;
{
    for (; c; c = c->m_next) {
      if ((wild) && (wild_match(c->m_tag,t))) return(c);
      if ((!wild) && (!strcmp(c->m_tag,t))) return(c);
      }
    return(0);
}

/*
 *  find_itm
 *
 *  Find the item index value itm in the item list associated with the
 *  tag record c
 */

static int find_itm(c,grp,itm)
  struct marks *c;
  unsigned int itm, grp;
{
  int i = 0;

  while ((i < c->m_size) && ((c->m_itms[i] != itm) || (c->m_grps[i] != grp))) ++i;
  return(((i < c->m_size) && (c->m_itms[i] == itm) && (c->m_grps[i] == grp)) ? i + 1 : 0);
}

/*
 * get_tag_word
 *
 * Allocates storage for a resultant string, copies parameter string
 * until (and excluding) the first blank (or the whole string if it
 * contains no blanks) into a new location and converts it to lower case.
 *
 * If the parameter string is null or empty, a default string is used
 * instead (undergoing the same case conversion and word extraction).
 *
 * Function returns a pointer to the new string.  It is up to the
 * calling procedure to deallocate storage for the returned string.
 *
 * It is primarily intended to preserve the read-only attribute
 * of the original string in procedures 'mark_*' and to simplify processing.
 */

char * get_tag_word(str, default_str)
  const char *str;
  const char *default_str;
{
  char *new_str;
  char *p;
  int len;

  if (!str || str[0] == '\0') str = default_str;
  if (!str) return NULL;

  p = strchr(str,' ');
  len = p ? p-str : strlen(str);

  new_str = news_strdup(str,len);
  lower_case(new_str);
  return new_str;
}

/*
 *  mark_read
 *
 *  Read the mark item list from a text file - check for the presence of
 *  all marked items.
 */

void mark_read(f)
  FILE *f;
{
  int status;
  char *in_line, *cpd, *cp;
  unsigned int gi[2];
  unsigned int g, i;

  if ( !nntp_client ) {
    itmrab.rab$l_kbf = (char *) gi;
    itmrab.rab$b_ksz = 8;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;
  }

  while ((in_line = fgetl(f)) != 0) {
    if (!strlen(in_line) || in_line[0] == '\n') continue;
    if (in_line[0] == '#') continue;
    if (!strcmp(in_line,"KILLLIST\n"))  {
      kill_read(f);
      break;
      }
    if (!strcmp(in_line,"PROFILE\n")) {
      profile_read(f);
      break;
      }
    cp = chop_str_plus(in_line,' ');
    while (cp) {
      cpd = cp;
      cp = chop_str_plus(cpd,' ');
      if (*cpd == 'X') {
        cpd++;
        if (sscanf(cpd,"%d_%d",&g,&i) == 2) {
          gi[0] = i;
          gi[1] = g;
          if (nntp_client || sys_find_nornf(&itmrab)) mark_set(g,i,in_line);
          }
        }
      else if (sscanf(cpd,"%d",&i)  == 1) {
        g = i >> 16;
        i = i & 0xFFFF;
        gi[0] = i;
        gi[1] = g;
        if (nntp_client || sys_find_nornf(&itmrab)) mark_set(g,i,in_line);
        }
      }
    }
}

/*
 *  mark_write
 *
 *  Write the mark list out to a text file
 */

void mark_write(f)
  FILE *f;
{
  struct marks *c;
  char il[512];
  int i, init = 0;

  for (c = m_head; c; c = c->m_next) {
    if (c->m_size) {
      if (!init++) putl("MARKLIST\n",f);
      strnncpy(il,sizeof(il), c->m_tag,-1, 0);
      putl(il,f);
      for (i = 0; i < c->m_size; ++i) {
        sprintf(il," X%u_%u",c->m_grps[i],c->m_itms[i]);
        putl(il,f);
        }
      putl("\n",f);
      }
    }
}

/*
 *  mark_set
 *
 *  Set item (g,i) to have tag "tag"
 */

void mark_set(g,i,tag)
  unsigned int g,i;
  const char *tag;
{
  int indx, j;
  struct marks *c = m_head;
  char *tag_word = get_tag_word(tag,"mark");

  if (!(c = find_tag(tag_word,c,0))) {
    c = (struct marks *) news_malloc(sizeof *c);
    c->m_next = m_head ;
    m_head = c;
    c->m_tag = tag_word;    /* we'll take the already made copy of the string */
    c->m_itms = (unsigned int *) news_malloc((c->m_malloc = 10) * (sizeof *(c->m_itms)));
    c->m_grps = (unsigned int *) news_malloc((c->m_malloc) * (sizeof *(c->m_grps)));
    c->m_size = 1;
    c->m_type = 0;
    c->k_grp_name = 0;
    c->k_itm_title = 0;
    c->k_itm_from = 0;
    c->k_itm_header = 0;
    *(c->m_itms) = i;
    *(c->m_grps) = g;
    /* must not free tag_word, it is already in use */
    return;
    }
  if (!find_itm(c,g,i)) {
    if (c->m_malloc == c->m_size) {
      c->m_itms = (unsigned int *) news_realloc(c->m_itms,(c->m_malloc += 10) * (sizeof *(c->m_itms)));
      c->m_grps = (unsigned int *) news_realloc(c->m_grps,(c->m_malloc) * (sizeof *(c->m_grps)));
      }
    indx = 0;
    while ((indx < c->m_size) && (quadcmp(g,i,c->m_grps[indx],c->m_itms[indx]) > 0)) ++indx;
    for (j = c->m_size; j > indx; --j) c->m_itms[j] = c->m_itms[j-1] ;
    for (j = c->m_size; j > indx; --j) c->m_grps[j] = c->m_grps[j-1] ;
    c->m_itms[indx] = i;
    c->m_grps[indx] = g;
    ++(c->m_size);
    }
  news_free(tag_word); return;
}

/*
 *  mark_clear
 *
 *  Clear all items with a mark of tag value "tag"
 */

void mark_clear(tag,h)
  char *tag;
  struct marks *h;
{
  struct marks *p = 0, *c = h;

  if (!c) return;
  if (tag) {
    lower_case(tag);
    chop_str(tag,' ');
    while ((c = find_tag(tag,c,1)) != 0) {
      if (c != h) {
        for (p=h;  p->m_next != c;  p=p->m_next) {}
        }
      else if (h == m_head) h = m_head = m_head->m_next;
      else h = mk_head = mk_head->m_next;
      if (c->m_itms) news_free(c->m_itms);
      if (c->m_tag) news_free(c->m_tag);
      if (c->k_grp_name) news_free(c->k_grp_name);
      if (c->k_itm_title) news_free(c->k_itm_title);
      if (c->k_itm_from) news_free(c->k_itm_from);
      if (c->k_itm_header) news_free(c->k_itm_header);
      if (p) p->m_next = c->m_next;
      p = c;
      c = c->m_next;
      news_free(p);
      }
    return;
    }
  while (c) {
    if (c->m_itms) news_free(c->m_itms);
    if (c->m_tag) news_free(c->m_tag);
    if (c->k_grp_name) news_free(c->k_grp_name);
    if (c->k_itm_title) news_free(c->k_itm_title);
    if (c->k_itm_from) news_free(c->k_itm_from);
    if (c->k_itm_header) news_free(c->k_itm_header);
    p = c;
    c = c->m_next;
    news_free(p);
    }
  if (h == m_head) m_head = 0;
  else mk_head = 0;
}

/*
 *  mark_del
 *
 *  Delete the entry for (g,i) with tag "tag"
 */

void mark_del(g,i,tag)
  unsigned int g,i;
  const char *tag;
{
  int indx, j;
  struct marks *c = m_head;

  if (tag) {
    char *tag_word = get_tag_word(tag,NULL);
    while ((c = find_tag(tag_word,c,1)) && (indx = find_itm(c,g,i))) {
      for (j = indx; j < c->m_size; ++j) c->m_itms[j-1] = c->m_itms[j] ;
      for (j = indx; j < c->m_size; ++j) c->m_grps[j-1] = c->m_grps[j] ;
      if (!--(c->m_size)) {
        struct marks *t;
        if (c == m_head) m_head = m_head->m_next;
        else {
          struct marks *t;
          for (t = m_head;  t->m_next != c;  t = t->m_next) {};
          t->m_next = c->m_next;
          }
        if (c->m_itms) news_free(c->m_itms);
        if (c->m_tag) news_free(c->m_tag);
        if (c->k_grp_name) news_free(c->k_grp_name);
        if (c->k_itm_title) news_free(c->k_itm_title);
        if (c->k_itm_from) news_free(c->k_itm_from);
        if (c->k_itm_header) news_free(c->k_itm_header);
	t = c;
	c = c->m_next;
        news_free(t);
        }
      }
    news_free(tag_word); return;
    }
  while (c) {
    if ((indx = find_itm(c,g,i)) != 0) {
      for (j = indx; j < c->m_size; ++j) c->m_itms[j-1] = c->m_itms[j] ;
      for (j = indx; j < c->m_size; ++j) c->m_grps[j-1] = c->m_grps[j] ;
      if (!--(c->m_size)) {
        struct marks *t;
        if (c == m_head) m_head = m_head->m_next;
        else {
          for (t = m_head;  t->m_next != c;  t = t->m_next) {};
          t->m_next = c->m_next;
          }
        if (c->m_itms) news_free(c->m_itms);
        if (c->m_tag) news_free(c->m_tag);
        if (c->k_grp_name) news_free(c->k_grp_name);
        if (c->k_itm_title) news_free(c->k_itm_title);
        if (c->k_itm_from) news_free(c->k_itm_from);
        if (c->k_itm_header) news_free(c->k_itm_header);
        t = c;
        c = c->m_next;
        news_free(t);
        continue;
        }
      }
    c = c->m_next;
    }
}

/*
 *  mark_find
 *
 *  Find the next item AFTER g,i with tag "tag" - return (g,i) in *ret
 */

int mark_find(g,i,tag,retg,retm)
  unsigned int g,i;
  const char *tag;
  unsigned int *retg, *retm;
{
  struct marks *c = m_head;
  unsigned int high_gval = 0, high_mval = 0, low_gval = 0, low_mval = 0;
  int indx;
  char *tag_word = get_tag_word(tag,"*");

  while ((c = find_tag(tag_word,c,1)) != 0) {
    if (c->m_size) {
      if (   (quadcmp(g,i,c->m_grps[0],c->m_itms[0]) > 0)
          && (   (!(low_mval + low_gval))
              || (quadcmp(low_gval,low_mval,c->m_grps[0],c->m_itms[0]) > 0))) {
        low_gval = c->m_grps[0];
        low_mval = c->m_itms[0];
        }
      indx = 0;
      while (   (indx < c->m_size)
             && (quadcmp(g,i,c->m_grps[indx],c->m_itms[indx]) >= 0))
        indx++;
      if (   (indx < c->m_size)
          && (   (!(high_gval + high_mval))
              || (quadcmp(high_gval,high_mval,c->m_grps[indx],c->m_itms[indx]) > 0))) {
        high_gval = c->m_grps[indx];
        high_mval = c->m_itms[indx];
        }
      }
    c = c->m_next;
    }
  news_free(tag_word);
  if (high_gval + high_mval) {
    *retg = high_gval;
    *retm = high_mval;
    return(1);
    }
  if (low_gval + low_mval) {
    *retg = low_gval;
    *retm = low_mval;
    return(1);
    }
  return(0);
}

/*
 *  mark_list
 *
 *  Return a list of tags for a news item
 */

char mark_list_ret[256];

char *
mark_list(g,m)
    unsigned int g,m;
{
    struct marks *c;

    *mark_list_ret = '\0';
    for (c = m_head; c; c = c->m_next) {
        if (find_itm(c,g,m)) {
            if (!*mark_list_ret) strcpy(mark_list_ret,"Mark: ");
            else strnncat(mark_list_ret,sizeof(mark_list_ret),-1, ",",1, 0);
            strnncat(mark_list_ret,sizeof(mark_list_ret),-1, c->m_tag,-1, 0);
            }
        }
    return(mark_list_ret);
}

/*
 *  mark_show
 *
 *  Generate a display of marked items
 */

void mark_show(tag,h,one,kill,ls)
  const char *tag;
  struct marks *h;
  int one, kill, ls;
{
  struct marks *c = h;
  int i, j, init = 0, gi = 0;
  unsigned int lst_grp = 0, locitm, g;
  ITM_PTR iap = 0;
  char *tag_word = get_tag_word(tag,"*");

  while ((c = find_tag(tag_word,c,1)) != 0) {
    lst_grp = 0;
    if (c->m_type || c->m_size) {
      if (!init) {
        start_header(T_DISPLAY_LOOP,SHOW_MARK);
        if (!c->m_type) {
          strcpy(err_oline,"MARKed Newsitems listing for TAG ");
          err_oline_cat(tag_word);
          put_line(err_oline,0,T_DISPLAY_LOOP);
          }
        else {
          put_line("KILL Filter display:",0,T_DISPLAY_LOOP);
          put_line("",0,T_DISPLAY_LOOP);
          put_line("Newsgroup S:subject F:from H:header",0,T_DISPLAY_LOOP);
          }
        put_line("",0,T_DISPLAY_LOOP);
        end_header(T_DISPLAY_LOOP);
        init = 1;
        }
      put_line("",0,T_DISPLAY_LOOP);
      strcpy(err_oline,"TAG: "); err_oline_cat(c->m_tag);
      put_line(err_oline,0,T_DISPLAY_LOOP);
      if (!c->m_type) {
        for (i = 0; i < c->m_size; ++i) {
          locitm = c->m_itms[i];
          if ((g = c->m_grps[i]) != lst_grp) {
	    if (!(gi = ga_locate(g))) continue;
	    strcpy(err_oline,"  Newsgroup: "); err_oline_cat(ga[gi]->grp_name);
            put_line(err_oline,0,T_DISPLAY_LOOP);
            if (!ga[gi]->grp_ia) map_items(gi);
            if (!(iap = ga[gi]->grp_ia)) continue;
            lst_grp = g;
            }
          if (find_itm_by_num(gi,locitm,&j)) {
            sprintf(err_oline,"    %-5d %-*.*s",locitm,
                    displ_subjlen,displ_subjlen,iap[j].itm_title);
            put_line(err_oline,0,T_DISPLAY_LOOP);
            }
          else {
            sprintf(err_oline,"    %-5d -Item not located-",locitm);
            put_line(err_oline,0,T_DISPLAY_LOOP);
            }
          }
        }
      else {
        strcpy(err_oline,c->k_grp_name);
        if (c->k_itm_title) {
          err_oline_cat(" S:");
          err_oline_cat(c->k_itm_title);
          }
        if (c->k_itm_from) {
          err_oline_cat(" F:");
          err_oline_cat(c->k_itm_from);
          }
        if (c->k_itm_header) {
          err_oline_cat(" H:");
          err_oline_cat(c->k_itm_header);
          }
        put_line(err_oline,0,T_DISPLAY_LOOP);
        }
      }
    if (one) break;
    c = c->m_next;
    }
  if (!init) {
    if (!kill) {
      strcpy(err_oline,"MARK - NO Items marked with TAG = ");
      err_oline_cat(tag_word);
      err_line(err_oline);
      }
    else err_line("Show Kill - No Kill Filters defined");
    }
  else put_line("",ls,T_DISPLAY_LOOP);

  news_free(tag_word); return;
}

/*
 *  mark_group_list
 *
 *  Generate a display of marked items
 */

void mark_group_list(gr)
    int gr;
{
    struct marks *c = m_head;
    const char *tag;
    int i,
        j,
        init = 0;
    unsigned int locitm,
                 g,
                 gn;
    ITM_PTR iap;

    gn = ga[gr]->grp_num;
    tag = "*";
    while ((c = find_tag(tag,c,1)) != 0) {
        init = 0;
        if (c->m_size) {
            for (i = 0; i < c->m_size; ++i) {
                locitm = c->m_itms[i];
                g = c->m_grps[i];
                if (g == gn) {
                    if (!init) {
                        strcpy(err_oline,"MARKed Articles in this Newsgroup with TAG: ");
                        err_oline_cat(c->m_tag);
                        put_line(err_oline,0,T_DISPLAY_LOOP);
                        init = 1;
                        }
                    if (!ga[gr]->grp_ia) map_items(gr);
                    if (!(iap = ga[gr]->grp_ia)) {
                        sprintf(err_oline,"    %-5d -Item not located-",locitm);
                        put_line(err_oline,0,T_DISPLAY_LOOP);
                        continue;
                        }
                    if (find_itm_by_num(gr,locitm,&j)) {
                        sprintf(err_oline,"    %-5d %-*.*s",locitm,
                                displ_subjlen,displ_subjlen,iap[j].itm_title);
                        put_line(err_oline,0,T_DISPLAY_LOOP);
                        }
                    else {
                        sprintf(err_oline,"    %-5d -Item not located-",locitm);
                        put_line(err_oline,0,T_DISPLAY_LOOP);
                        }
                    }
                }
            }
        c = c->m_next;
        }
}

static int dmark()
{
  int sg = 0, si = 0, i, j, count = 0;
  char tag[80];
  unsigned short tag_len;
  $DESCRIPTOR(tag_dsc,tag);

  if (cli$get_value(c$dsc("TAG"),&tag_dsc,&tag_len) & 1) tag[tag_len] = '\0';
  else strcpy(tag,"mark");

  if (!getnoterange()) return(err_line("Error: MARK no item specified"),0);

  for (j=0; d_itm[j].fitm; j++) {
    sg = d_itm[j].ngrp;
    if (d_itm[j].fitm < 0) {
      for (i = 1; i <= ga[sg]->grp_count; ++i) {
	mark_set(ga[sg]->grp_num,ga[sg]->grp_ia[i].itm_num,tag);
        if (!count++) si = ga[sg]->grp_ia[i].itm_num;
        }
      }
    else {
      if (!d_itm[j].litm) d_itm[j].litm = d_itm[j].fitm;
      for (i = d_itm[j].fitm; i <= d_itm[j].litm; ++i) {
        mark_set(ga[sg]->grp_num,i,tag);
        if (!count++) si = i;
        }
      }
    }
  if (count <= 0) strcpy(err_oline,"Error: MARK no item specified");
  else {
    if (count > 1) sprintf(err_oline,"%d Items",count);
    else {
      if ((sg == curr_g) && (si == curr_i)) strcpy(err_oline,"Current Item");
      else if (sg == curr_g) sprintf(err_oline,"Item %d",si);
      else sprintf(err_oline,"Item %.200s:%d",ga[sg]->grp_name,si);
      }
    err_oline_cat(" marked with Tag: "); err_oline_cat(tag);
    }
  err_line(err_oline);
  return(0);
}

int
do_mark()
{
  return(unwind_display(I_DISPLAY_LOOP,dmark));
}

static int dunmark()
{
  int sg = 0, si = 0, i, j, count = 0;
  char tag[80], *tag_param = 0;
  unsigned short tag_len;
  $DESCRIPTOR(tag_dsc,tag);

  if (!getnoterange()) return(err_line("Error: MARK no item specified"),0);

  if (cli$get_value(c$dsc("TAG"),&tag_dsc,&tag_len) & 1) {
    tag[tag_len] = '\0';
    tag_param = tag;
    }

  if (!tag_param) strcpy(tag,"<all>");
  for (j=0; d_itm[j].fitm; j++) {
    sg = d_itm[j].ngrp;
    if (d_itm[j].fitm < 0) {
      for (i = 1; i <= ga[sg]->grp_count; ++i) {
	mark_del(ga[sg]->grp_num,ga[sg]->grp_ia[i].itm_num,tag_param);
        if (!count++) si = ga[sg]->grp_ia[i].itm_num;
	}
      }
    else {
      if (!d_itm[j].litm) d_itm[j].litm = d_itm[j].fitm;
      for (i = d_itm[j].fitm; i <= d_itm[j].litm; ++i) {
        mark_del(ga[sg]->grp_num,i,tag_param);
        if (!count++) si = i;
        }
      }
    }
  if (count <= 0) strcpy(err_oline,"Error: UNMARK no item specified");
  else {
    if (count > 1) sprintf(err_oline,"%d Items",count);
    else {
      if ((sg == curr_g) && (si == curr_i)) strcpy(err_oline,"Current Item");
      else if (sg == curr_g) sprintf(err_oline,"Item %d",si);
      else sprintf(err_oline,"Item %.200s:%d",ga[sg]->grp_name,si);
      }
    err_line(" - mark cleared, Tag: "); err_oline_cat(tag);
    }
  err_line(err_oline);
  return(0);
}

int
do_unmark()
{
  return(unwind_display(I_DISPLAY_LOOP,dunmark));
}

static int dms_1()
{
  char tag[80];
  unsigned short tag_len;
  $DESCRIPTOR(tag_dsc,tag);

  if (cli$get_value(c$dsc("TAG"),&tag_dsc,&tag_len) & 1) tag[tag_len] = '\0';
  else strcpy(tag,"*");
  mark_show(tag,m_head,0,0,1);
  return(0);
}

int
do_markshow()
{
  return(unwind_display(I_DISPLAY_LOOP,dms_1));
}

int do_markclear()
{
  char tag[80];
  unsigned short tag_len;
  $DESCRIPTOR(tag_dsc,tag);

  if (cli$get_value(c$dsc("TAG"),&tag_dsc,&tag_len) & 1) {
    tag[tag_len] = '\0';
    mark_clear(tag,m_head);
    strcpy(err_oline,"ALL items cleared with Tag: "); err_oline_cat(tag);
    }
  else {
    mark_clear(0,m_head);
    strcpy(err_oline,"ALL Marks cleared");
    }
  err_line(err_oline);
  return(0);
}

int getnoteid()
{
  int locnum, i;
  char note[80];
  unsigned short note_len;
  $DESCRIPTOR(note_dsc,note);

  if (cli$get_value(c$dsc("NOTEID"),&note_dsc,&note_len) & 1) {
    if (!curr_g || !(ga[curr_g]->grp_count) || (news_context < 2) || !note_len)
      return(-1);
    note[note_len] = '\0';
    lower_case(note);
    if (isdigit(*note)) {
      if ( (sscanf(note,"%d",&locnum) == 1) &&
           find_itm_by_num(curr_g,locnum,&i) ) return(i);
      return(-1);
      }
    else if (!strncmp(note,"first",min(note_len,5))) return(1);
    else if (!strncmp(note,"last",min(note_len,4))) return(ga[curr_g]->grp_count);
    else if (!strcmp(note,".")) return(curr_i);
    else if (!strcmp(note,"*")) return(ga[curr_g]->grp_count);
    else return(-1);
    }
  return(0);
}

int noterange(mark,nr,all,deflt,cg,ci)
  const char *mark, *nr, *deflt;
  int all, cg, ci;
{
  char s[256], *cp2;
  const char *cp1;
  int di, pi = 0, li, fi, i;
  unsigned int g, m, fg, fm, ret;
  ITM_PTR iap;

  d_itm[0].fitm = 0;
  if (*mark) {
    for (cp1 = (*mark==1) ? "*" : mark;  cp1;  cp1 = cp2 ? cp2+1 : NULL) {
      int cp1_l;
      cp2 = strchr(cp1,',');
      cp1_l = cp2 ? cp2-cp1 : strlen(cp1);
      strnncpy(s,sizeof(s), cp1,cp1_l, 0);
      fg = fm = g = m = 0;
      while (mark_find(g,m,s,&g, &m)) {
        if (!fg) { fg = g; fm = m; }
        else if ((fg == g) && (fm == m)) break;
        if (!(ret = ga_locate(g))) continue;
        if (!ga[ret]->grp_count) continue;
        if (!ga[ret]->grp_ia) map_items(ret);
        if (!find_itm_by_num(ret,m,&i)) continue;
        d_itm[pi].fitm = ga[ret]->grp_ia[i].itm_num;
        d_itm[pi].litm = 0;
        d_itm[pi].ngrp = ret;
        d_itm[++pi].fitm = 0;
        }
      }
    return(d_itm[0].fitm);
    }

  d_itm[0].fitm = 0;
  if ((ci == curr_i) && (ci > 0) && (cg == curr_g) && (news_context > 1))
    d_itm[0].fitm = ga[cg]->grp_ia[ci].itm_num;
  d_itm[0].litm = 0;
  d_itm[0].ngrp = cg;
  d_itm[1].fitm = 0;
  if (!cg) return(0);

  if (!*nr) {
    if (all) d_itm[0].fitm = -1;
    else if (*deflt) {
      strnncpy(s,sizeof(s), deflt,-1, 0);
      parse_items(s,d_itm,cg,ci);
      }
    }
  else {
    strnncpy(s,sizeof(s), nr,-1, 0);
    parse_items(s,d_itm,cg,ci);
    }

  map_items(cg);
  if ((!(ga[cg]->grp_count)) || (!(iap = ga[cg]->grp_ia)))
    return(d_itm[0].fitm = d_itm[0].litm = 0);
  for (pi = di = 0; (li = d_itm[di].litm), (fi = d_itm[di].fitm) ; ++di) {
    if ((fi > 0) && ((!li) || (li == fi)) && find_itm_by_num(cg,fi,&i)) {
      d_itm[pi].litm = 0;
      d_itm[pi].ngrp = cg;
      d_itm[pi++].fitm = iap[i].itm_num;
      }
    else if (fi < 0) {
      d_itm[0].fitm = -1;
      d_itm[0].ngrp = cg;
      d_itm[0].litm = d_itm[1].fitm = 0;
      return(-1);
      }
    else if (fi <= ga[cg]->grp_topnum) {
      int nfi, nli;

      find_itm_by_num(cg,fi,&i);
      nfi = iap[i].itm_num;
      nli = find_itm_by_num(cg,li,&i) ? iap[i].itm_num : iap[max(i-1,1)].itm_num;
      if (nfi == nli) nli = 0;
      d_itm[pi].litm = nli;
      d_itm[pi].ngrp = cg;
      d_itm[pi++].fitm = nfi;
      }
    }
  d_itm[pi].fitm = 0;
  return(d_itm[0].fitm);
}

int getnoterange()
{
  char s[256], m[256], nr[256];
  int all = 0;
  unsigned short s_len;
  $DESCRIPTOR(s_dsc,s);

  *m = '\0';
  if (cli$present(c$dsc("MARKER")) & 1) {
    if (!(cli$get_value(c$dsc("MARKER"),&s_dsc,&s_len) & 1)) *m = 1;
    else {
      do {
        if (*m) strnncat(m,sizeof(m),-1, ",",1, 0);
        strnncat(m,sizeof(m),-1, s,s_len, 0);
        } while (cli$get_value(c$dsc("MARKER"),&s_dsc,&s_len) & 1);
      }
    return(noterange(m,nr,all,"",curr_g,curr_i));
    }
  else *m = '\0';

  *nr = '\0';
  if (cli$get_value(c$dsc("NOTERANGE"),&s_dsc,&s_len) == CLI$_ABSENT) {
    if (cli$present(c$dsc("ALL")) & 1) all = 1;
    else all = 0;
    }
  else {
    do {
      if (*nr) strnncat(nr,sizeof(nr),-1, ",",1, 0);
      strnncat(nr,sizeof(nr),-1, s,s_len, 0);
      } while (cli$get_value(c$dsc("NOTERANGE"),&s_dsc,&s_len) & 1);
    }
  return(noterange(m,nr,all,"",curr_g,curr_i));
}

int scannoterange(m,nr,all)
  char *m, *nr;
  int *all;
{
  int retval = 0;
  char s[256];
  unsigned short s_len;
  $DESCRIPTOR(s_dsc,s);

  *m = '\0';
  if (cli$present(c$dsc("MARKER")) & 1) {
    ++retval;
    if (!(cli$get_value(c$dsc("MARKER"),&s_dsc,&s_len) & 1)) *m = 1;
    else {
      do {
        if (*m) strnncat(m,sizeof(m),-1, ",",1, 0);
        strnncat(m,sizeof(m),-1, s,s_len, 0);
        } while (cli$get_value(c$dsc("MARKER"),&s_dsc,&s_len) & 1);
      }
    }
  else *m = 0;

  *nr = '\0';
  if (cli$get_value(c$dsc("NOTERANGE"),&s_dsc,&s_len) == CLI$_ABSENT) {
    if (cli$present(c$dsc("ALL")) & 1) {
      *all = 1;
      ++retval;
      }
    else *all = 0;
    }
  else {
    ++retval;
    do {
      if (*nr) strnncat(nr,sizeof(nr),-1, ",",1, 0);
      strnncat(nr,sizeof(nr),-1, s,s_len, 0);
      } while (cli$get_value(c$dsc("NOTERANGE"),&s_dsc,&s_len) & 1);
    }
  return(retval);
}

static int scanlist(g)
  char *g;
{
  char *cp, *cp1;

  if (!*scangroups) return(1);
  for (cp=scangroups; cp; cp=cp1) {
    cp1 = chop_str(cp,',');
    if (wild_match(g,cp)) {
      if (cp) *cp = ',';
      return(1);
      }
    if (cp1) *cp1++ = ',';
    }
  return(0);
}

static void msg(const char *str, const char *node)
{
  int ll;
  strnncpy(err_oline,err_oline_m, str,-1, &ll);
  if (node && *node) {
    strnncat(err_oline,err_oline_m,ll, " (Network server:",-1, &ll);
    strnncat(err_oline,err_oline_m,ll, node,-1, &ll);
    strnncat(err_oline,err_oline_m,ll, ")",1,   0);
    }
}

int fastscan()
{
#define clsallfiles(n) {				\
		         if (!nntp_client) {		\
			   sysprv();			\
			   sys_close(&grpfab);		\
			   sys_close(&itmfab);		\
			   nosysprv();			\
			   }				\
			 if (fclose(newsrc)) _ck_close(newsrc); \
			 newsrc = 0; return(n);		\
		       }

  int status;
  char *in_line, ngrp[GRPNAMELEN], *cp, node[256];
  unsigned int tpnum, itmnum[2];
  int proto = 0,
      topnum,
      nntp_client = 0;

  if (initial_classname) return(0);
  *node = '\0';
  if (newsrc) { if (fseek(newsrc,0,0)) _ck_seek(newsrc); }
  else if (!(newsrc = fopen(news_register,"r","mbc=40"))) return(0);

  if (!(in_line = fgetl(newsrc))) {
    if (fclose(newsrc)) _ck_close(newsrc);
    newsrc = 0; return(0);
    }

  if ((cp = strchr(in_line,'#')) && !*scangroups) {
    if (*in_line == '~') {
      if ((cp = strchr(in_line+1,'~')) != 0) {
        *cp-- = '\0';
        *(cp - 1) = '\0';
        strnncpy(node,sizeof(node), in_line+1,-1, 0);
        lower_case(node);
        }
      else return(0);
      }

    msg("\tNEWS: There is NEW News", node);
    _c$cks(lib$put_output(c$dscl(&err_oline_d)));
    if (fclose(newsrc)) _ck_close(newsrc);
    newsrc = 0; return(1);
    }
  	/* client newsrc first line = ~node protnum~... */
  if (*in_line == '~') {
    if ((cp = strchr(in_line+1,'~')) != 0) {
      *cp-- = '\0';
      *(cp - 1) = '\0';
      strnncpy(node,sizeof(node), in_line+1,-1, 0);
      proto = atoi(cp);
      nntp_client = 1;
      proto = available_proto(proto);
      }
    else return(0);
    }
  else {
    kid_valid = 0;
    if (init_lock()) exit(1);
    kid_valid = 1;
    itmfab = cc$rms_fab;
    itmfab.fab$b_fac = FAB$M_GET;
    itmfab.fab$l_fna = (char *) ITM_FILENAME;
    itmfab.fab$b_fns = strlen(itmfab.fab$l_fna);
    itmfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

    itmrab = cc$rms_rab;
    itmrab.rab$l_fab = &itmfab;
    itmrab.rab$b_krf = 0;
    itmrab.rab$b_ksz = 8;
    itmrab.rab$l_kbf = (char *) itmnum;
    itmrab.rab$l_ubf = (char *) &newsitm;
    itmrab.rab$w_usz = sizeof newsitm;
    itmrab.rab$l_rop = RAB$M_RRL | RAB$M_NLK | RAB$M_KGE;
    itmrab.rab$b_rac = RAB$C_KEY;

    grpfab = cc$rms_fab;
    grpfab.fab$b_fac = FAB$M_GET;
    grpfab.fab$l_fna = (char *) GRP_FILENAME;
    grpfab.fab$b_fns = strlen(grpfab.fab$l_fna);
    grpfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

    grprab = cc$rms_rab;
    grprab.rab$b_krf = 0;
    grprab.rab$l_kbf = ngrp;
    grprab.rab$b_ksz = GRPNAMELEN_F;
    grprab.rab$l_fab = &grpfab;
    grprab.rab$l_ubf = (char *) &newsgrp;
    grprab.rab$w_usz = NEWS_GRPFIL_RSZ;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_KEY;

    sysprv();
    if (!sys_open(&itmfab)) {
      nosysprv();
      if (fclose(newsrc)) _ck_close(newsrc);
      newsrc = 0; return(0);
      }
    if (!sys_connect(&itmrab)) {
      sys_close(&itmfab);
      nosysprv();
      if (fclose(newsrc)) _ck_close(newsrc);
      newsrc = 0; return(0);
      }
    if (!sys_open(&grpfab)) {
      sys_close(&itmfab);
      nosysprv();
      if (fclose(newsrc)) _ck_close(newsrc);
      newsrc = 0; return(0);
      }
    if (!sys_connect(&grprab)) {
      sys_close(&grpfab);
      sys_close(&itmfab);
      nosysprv();
      if (fclose(newsrc)) _ck_close(newsrc);
      newsrc = 0; return(0);
      }
    nosysprv();
    }
  while ((in_line = fgetl(newsrc)) != 0) {
    if (!strlen(in_line) || in_line[0] == '\n') continue;
    if (!(cp = strchr(in_line,'('))) clsallfiles(0);
    if (!strncmp(cp,"(0)",3)) continue;
    if (!(cp = strchr(in_line,':'))) clsallfiles(0);
    *cp = '\0';
    util_cvrt(ngrp,in_line);
    if (*scangroups && !scanlist(ngrp)) continue;

    if (nntp_client) {
      topnum = get_group_topnum(node,proto,ngrp);
      if (topnum == -4) {
        if (fclose(newsrc)) _ck_close(newsrc);
        newsrc = 0; return(0);
	}
      if (topnum <= 0) continue;
      }
    else {
      if (!sys_get_nornf(&grprab)) continue;
      topnum = newsgrp.grp_topnum;
      }

    *cp = ':';
    if ((cp = strchr(in_line,'[')) &&
        (cp = strchr(cp,',')) &&
        (sscanf(cp,",%d]",&tpnum) == 1)) {
      if (tpnum < topnum) {
        msg("\tNEWS: There is NEW News", node);
        _c$cks(lib$put_output(c$dscl(&err_oline_d)));
        clsallfiles(1);
        }
      }

    if (!(cp = strrchr(in_line,' '))) clsallfiles(0);
    cp++;
    if (((*cp == '|') && (cp = strchr(cp,'='))) || (*cp == '<')) cp++;
    if (!cp || !isdigit(*cp)) continue;

    itmnum[1] = newsgrp.grp_num;
    if (sscanf(cp,"%d",&itmnum[0]) != 1) continue;
    ++itmnum[0];

    if (nntp_client && (itmnum[0] > (topnum+ 1))) {
      msg("\tNEWS: There is NEW News", node);
      _c$cks(lib$put_output(c$dscl(&err_oline_d)));
      clsallfiles(1);
      }
    if (!nntp_client && sys_get_nornf(&itmrab) && (newsitm.itm_grp == newsgrp.grp_num)) {
      _c$cks(lib$put_output(c$dsc("\tNEWS: There is NEW News")));
      clsallfiles(1);
      }
    }
  clsallfiles(0);
  return 1;
}

int check_groups(all,outp)
  int all, outp;
{
  int g, found = 0, fl = 1;

  if (initial_classname) set_class(n_class_name);
  if (outp && all) fl = 0;
  for (g = 1; g <= ga_size; ++g) {
    if (ga[g]->grp_unread && ga[g]->grp_reg) {
      if (initial_classname && !class_check(g)) continue;
      if (*scangroups && !scanlist(ga[g]->grp_name)) continue;
      if (outp) {
        if (!fl) {
          msg("NEWS: There are unseen items", nntp_node);
          err_oline_cat(":");
          err_line(err_oline);
          }
        fl = 1;
        if (all) sprintf(err_oline,"  %.200s: %d",
                         ga[g]->grp_name, ga[g]->grp_unread);
        else msg("\tNEWS: There is NEW News", nntp_node);
        err_line(err_oline);
        }
      ++found;
      if (!all) break;
      }
    }
  if (!found) {
    if (all && outp) {
      msg("NEWS: No unseen items", nntp_node);
      err_line(err_oline);
      }
    }
  else {
    force_dir = 3;
    new_vals = 1;
    *n_class_name = '\0';
    }
  return(found);
}
