/*
**++
**  FACILITY:
**      NEWSFILES
**
**  ABSTRACT:
**      This module performs the IO functions for NEWS
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1988,1989,1990,1991,1993
**
**  VERSION HISTORY:
**	V6.0-1	 4-Nov-1990	glass@mgi.com
**	  - open_groups: access to restricted newsgroups correction
**	V6.0-4	25-May-1991	volz@process.com
**	  - Added TCPware support
**	V6.1	 6-Feb-1992	rankin@eql.caltech.edu
**	  - Lint cleanup
**	V6.1	15-Feb-1992	rankin@eql.caltech.edu
**	  - Optimizations to speed up startup, particularly for non-ANU-NEWS
**	    servers which may provide groups out of alphabetical order.
**	V6.1	29-Jun-1992	reggers@julian.uwo.ca
**	  - Added compile time option to suppress connect messages during NNTP
**	    client startup
**	V6.1b6	28-Feb-1993	mark.martinec@ijs.si
**	  - fix strncmp match for generic TCP  (broken by V6.0-4,
**	    so that /PROTOCOL=TCP matched TCPWARE instead of generic TCP)
**	V6.1b6	2-Mar-1993	saul@hnrc.tufts.edu
**	  - RMS optimizations
**	    (see FAST_SKIM.PATCH / 930302_NEWSFILES.PATCH for details)
**	V6.1b7	27-May-93	munroe@dmc.com
**	    rms$errno isn't returned properly from most of the rms_ routines.  I
**	    need the information in order to debug something that is preventing
**	    items from being written.
**	V6.1b7	15-May-1993	Charles Bailey  bailey@genetics.upenn.edu
**	  - add idmap members to newsgrp struct for message-ID handling
**	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,
**	     and zero portion of struct grp beyond end of data in file
**	V6.1b8  22-Dec-1993	mark.martinec@ijs.si
**	   - convert calls to RMS to use new sys_* macros for error handling 
**	V6.1b8  21-Mar-1994	mark.martinec@ijs.si
**	   - new routine closefiles_opt() to handle all three varieties
**	     of closing files under one routine.
**	V6.1b8  12-Apr-1994  mark.martinec@ijs.si
**	   - rms_open() and rms_create receive an extra argument
**	     to disable some (more common) cases of signalling errors
**	V6.1b9   1-Jun-1994  mark.martinec@ijs.si
**	   - set variable brdcst_col in get_input_general() (which knows
**	     the actual size of the prompt) as a temporary remedy for
**	     cursor repositioning problems after displaying broadcast messages.
**	     The true solution would be to move the call to display_brdcst()
**	     out of the AST routine.  The problem remains as the code stands now
**	     because during data entry in smg$read_composed_line()
**	     the exact cursor position is not known outside SMG.
**	V6.1b9  14-Jun-1994	saul@hnrc.tufts.edu
**	   - modifications to some of my rms performance patches:
**	     separate out potentially dangerous deferred write
**	     add latent support for "fast delete" on item file
**	     change logical names to conform to NEWS_ standard
**	V6.1b9	17-Aug-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to make it compile under gcc 2.6.0 with full
**	    warnings reporting turned on - with no or very few harmless warnings
**	V6.1b9	17-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to preserve the read-only nature of string literals
**	    (strategically placed 'const' attribute to string parameters)
**	V6.1b9	 9-Mar-1995	Mark Martinec   mark.martinec@ijs.si
**	  - call new site-specific routine try_to_make_some_free_space()
**	    when file creation fails (specially due to insufficient
**	    contiguous free space on NEWS_DEVICE)
**    6.1b10  5-Sep-1996     Mark Martinec   mark.martinec@ijs.si
**	  - new routines:  new_group, cnvgroup_inmemory_to_filerec,
**	    and cnvgroup_filerec_to_inmemory.  For the description
**	    of changes see NEWSDEFINE.H .
**	  - hard-code the largest CPU consumer in the NNTP client:
**	      sscanf(cp,"%s %d %d %s",newsg,&last,&first,mod)
**	V6.1b11	28-Mar-1997	Mark Martinec   mark.martinec@ijs.si
**	  - use new procedures available_proto, proto_name and name_to_proto
**	    to replace frequent code patterns and 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).
**	  - abort with NEWS$_NONNTP if NNTP client mode is requested
**	    but the client support code is not compiled-in.
**	V6.1b11 14-Apr-1997	Mark Martinec   mark.martinec@ijs.si
**	  - change %s to %.<n>s in sprintf to guard against overwriting buffer
**	  - replace several calls to strcpy with safer strnncpy
**	  - replace pattern strcpy(news_malloc(strlen(x)+1),x) with news_strdup
**	V6.1b11  7-May-1997	Mark Martinec   mark.martinec@ijs.si
**	  - all modules: decouple group name size (new constant GRPNAMELEN)
**	    from the size of item titles (SUBJLEN)
**	  - decouple the in-memory grp_name size (GRPNAMELEN) from the
**	    size of the groups file record field (GRPNAMELEN_F).
**	    Now the file record may contain group name which is
**	    not '\0'-terminated, thus gaining one character
**	    in the group name length, and ANU in a NNTP client mode
**	    may access groups with longer names than local database
**	    would accomodate.
**	V6.1b11  3-Jun-1997	Mark Martinec   mark.martinec@ijs.si
**	  - rms_get(): parameter size is now supposed to be sizeof(buffer)
**	    (inconsistent use lead to a buffer overflow in NEWSPOST)
**	  - initng(): preserve group names longer than GRPNAMELEN_F;
**--
*/

#ifdef vaxc
#module NEWSFILES "V6.2"
#endif

#define _NEWSFILES_C
#define module_name "NEWSFILES"

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

#if NAKED_INCLUDES
#include ttdef
#include tt2def
#else
#include <ttdef.h>
#include <tt2def.h>
#endif

int rms$errno = 0;

extern int profile_flags;

#if !NNTP_CLIENT_ONLY
static
struct XABKEY xabkey_1,
              xabkey_2,
              xabkey_3,
              xabkey_4;

static
struct XABPRO xabpro_1,
              xabpro_2;
#endif

/*
 * Like malloc, but allocates in larger contiguous chunks of memory
 * to keep newsgroup names close together and without unnecessary
 * overhead, thus reducing paging.
 * Allocated strings can not be deallocated - don't call free() !
 *
 * Special feature: called with negative 'size' returns NULL and causes
 * the subsequent extents to be smaller.
 */
char *grpname_malloc(int size)
{
  static char *mem = NULL;
  static int mem_m = 0; /* malloced size of mem */
  static int mem_l = 0; /* used size in mem */
  static int mem_chunk_size = 10*512;
  char *ra;

  if (size < 0) {
    mem_chunk_size = 2*512;
    return(NULL);
  }
  if (!mem || mem_l+size > mem_m) {
    mem = news_malloc(mem_m=max(size,mem_chunk_size));
    mem_l = 0;
    }
  ra = mem+mem_l; mem_l += size;
  return(ra);
}


GRPF_PTR cnvgroup_inmemory_to_filerec(GRPF_PTR pnewsgrp, const GRP_PTR gp)
{
  memset(pnewsgrp, 0, sizeof(GRPF));
  /* copy field by field because of possible different alignment
   * between the file record and the dynamic memory structure
   */
  strncpy(pnewsgrp->grp_name, gp->grp_name, GRPNAMELEN_F);
  pnewsgrp->grp_num      = gp->grp_num;
  pnewsgrp->grp_topnum   = gp->grp_topnum;
  pnewsgrp->grp_count    = gp->grp_count;
  pnewsgrp->grp_credate  = gp->grp_credate;
  pnewsgrp->grp_entdate  = gp->grp_entdate;
  pnewsgrp->grp_life     = gp->grp_life;
  pnewsgrp->grp_itmlife  = gp->grp_itmlife;
  pnewsgrp->grp_srvcache = gp->grp_srvcache;
  pnewsgrp->grp_flags    = gp->grp_flags;
  pnewsgrp->grp_firstnum = gp->grp_firstnum;
  pnewsgrp->grp_srvproto = gp->grp_srvproto;
  strnncpy(pnewsgrp->grp_srvnode,sizeof(pnewsgrp->grp_srvnode),
           gp->grp_srvnode,-1, 0);
  strnncpy(pnewsgrp->grp_topic,sizeof(pnewsgrp->grp_topic),
           gp->grp_topic,-1, 0);
  strnncpy(pnewsgrp->grp_notice,sizeof(pnewsgrp->grp_notice),
           gp->grp_notice,-1, 0);
  return pnewsgrp;
}

/*
 * Copy corresponding fields from the file record (NEWS.GROUPS) and
 * initialize the remaining fields (which are not present in the file) to 0.
 * Returns the new structure (same as the first parameter).
 */
GRP_PTR cnvgroup_filerec_to_inmemory(GRP_PTR gp, const GRPF_PTR pnewsgrp)
{ int j;
  const char *nm;
  memset(gp, 0, sizeof(GRP));
  /* copy field by field because of possible different alignment
   * between the file record and the dynamic memory structure
   */
  /* grp_name in the file record field need not be '\0'-terminated */
  for (j=0, nm=pnewsgrp->grp_name; j<GRPNAMELEN_F && *nm; j++, nm++) {};
  gp->grp_name = grpname_malloc(j+1);
  memcpy(gp->grp_name, pnewsgrp->grp_name, j); gp->grp_name[j] = '\0';
  gp->grp_num      = pnewsgrp->grp_num;
  gp->grp_topnum   = pnewsgrp->grp_topnum;
  gp->grp_count    = pnewsgrp->grp_count;
  gp->grp_credate  = pnewsgrp->grp_credate;
  gp->grp_entdate  = pnewsgrp->grp_entdate;
  gp->grp_life     = pnewsgrp->grp_life;
  gp->grp_itmlife  = pnewsgrp->grp_itmlife;
  gp->grp_srvcache = pnewsgrp->grp_srvcache;
  gp->grp_flags    = pnewsgrp->grp_flags;
  gp->grp_firstnum = pnewsgrp->grp_firstnum;
  gp->grp_srvproto = pnewsgrp->grp_srvproto;
  /* the following three fields are dynamically allocated: */
  gp->grp_srvnode = gp->grp_topic = gp->grp_notice = NULL;
  j = strlen(pnewsgrp->grp_srvnode);
  if (j) gp->grp_srvnode = news_strdup(pnewsgrp->grp_srvnode,j);
  j = strlen(pnewsgrp->grp_topic);
  if (j) gp->grp_topic   = news_strdup(pnewsgrp->grp_topic,  j);
  j = strlen(pnewsgrp->grp_notice);
  if (j) gp->grp_notice  = news_strdup(pnewsgrp->grp_notice, j);
  return gp;
}

/*
 * Allocate memory for the new in-memory group structure
 * and copy corresponding fields from the file record.
 * Returns the new structure.
 */
GRP_PTR new_group(GRPF_PTR pnewsgrp)
{
  return cnvgroup_filerec_to_inmemory((GRP_PTR) news_malloc(sizeof(GRP)),
                                      pnewsgrp);
}

struct rms_file *rms_open(nam,default_nam,bypass_signalling_errors)
  const char *nam, *default_nam;
  int bypass_signalling_errors;  /* bypass signalling of some
                                    of the more common errors */
{
  int status;
  struct rms_file *i;

  i = (struct rms_file *) news_malloc(sizeof *i);
  i->f = cc$rms_fab;
  i->f.fab$b_fac = FAB$M_GET;
  i->f.fab$l_fna = (char *) nam;
  i->f.fab$b_fns = strlen(nam);
  if (default_nam) {
    i->f.fab$l_dna = (char *) default_nam;
    i->f.fab$b_dns = strlen(default_nam);
    }
  i->r = cc$rms_rab;
  i->r.rab$l_fab = &(i->f);

/* ST 2/2/93 - turn on multibuffering and read ahead */
  i -> r.rab$l_rop |= RAB$M_RAH;
  i -> r.rab$b_mbf = 2;

  if ((status=sys$open(&(i->f))) & 1) {
    if ((status=sys$connect(&(i->r))) & 1)
      return(i);
    else {
      rms$errno = i->r.rab$l_stv;
      if (!bypass_signalling_errors)
        report_rms_error_routine(NEWS$_CONFAIL,status,&(i->r),
                                 module_name_str,__LINE__);
      sys_close(&(i->f));
      news_free(i);
      return ((struct rms_file *) 0);
      }
    }
  else {
    rms$errno = i->f.fab$l_stv;
    if (!bypass_signalling_errors)
      report_rms_error_routine(NEWS$_OPNFAIL,status,&(i->f),
                               module_name_str,__LINE__);
    news_free(i);
    return((struct rms_file *) 0);
    }
  news_assert_nonfatal(0); return ((struct rms_file *) 0); /* should never happen */
}

int rms_get(b,size,i)
  char *b;
  int size;
  struct rms_file *i;
{
  int status;

  i->r.rab$l_ubf = b;
  i->r.rab$w_usz = size-1;
  if (sys_get_nornf(&(i->r))) b[i->r.rab$w_rsz] = '\0';
  rms$errno = i->r.rab$l_stv ;
  return(status & 1);
}

struct rms_file * rms_create(nam,prot,bypass_signalling_errors)
  const char *nam;
  unsigned int prot;
  int bypass_signalling_errors;  /* bypass signalling of some
                                    of the more common errors */
{
  int status;
  struct rms_file *i;

  i = (struct rms_file *) news_malloc(sizeof *i);
  i->f = cc$rms_fab;
  i->f.fab$b_fac = FAB$M_PUT;
  i->f.fab$l_fna = (char *) nam;
  i->f.fab$b_fns = strlen(nam);
  i->f.fab$b_org = FAB$C_SEQ;
  i->f.fab$b_rat = FAB$M_CR;
  i->f.fab$b_rfm = FAB$C_VAR;
  i->f.fab$l_alq = 4;
  i->f.fab$w_deq = 32;
  i->f.fab$l_fop |= FAB$M_TEF;
  i->f.fab$l_xab = (char *) &(i->p);
  i->p = cc$rms_xabpro;
  i->p.xab$w_pro = prot;

  i->r = cc$rms_rab;
  i->r.rab$l_fab = &(i->f);

/* ST 2/2/93 - turn on multibuffering and write behind */
    i -> r.rab$l_rop |= RAB$M_WBH;
    i -> r.rab$b_mbf = 2;

  sysprv();
  status = sys$create(&(i->f));
  if (! (status & 1)) {
    if ((i->f).fab$l_sts == RMS$_CRE)
      if (try_to_make_some_free_space((i->f).fab$l_stv,(i->f).fab$l_alq,nam,0)) {
        status = sys$create(&(i->f));
        if (status&1) stat_make_space_retry_success++;
        }
    }
  if ( !(status & 1) ) {
    rms$errno = i->f.fab$l_stv ;
    if (!bypass_signalling_errors)
      report_rms_error_routine(NEWS$_CREFAIL,status,&(i->f),
                               module_name_str,__LINE__);
    news_free(i);
    nosysprv();
    return ((struct rms_file *) 0);
    }
  else {
    if ((status=sys$connect(&(i->r))) & 1) {
      nosysprv();
      return(i);
      }
    else {
      rms$errno = i->r.rab$l_stv;
      if (!bypass_signalling_errors)
        report_rms_error_routine(NEWS$_CONFAIL,status,&(i->r),
                                 module_name_str,__LINE__);
      sys_close(&(i->f));
      news_free(i);
      nosysprv();
      return((struct rms_file *) 0);
      }
    }
  news_assert_nonfatal(0); return ((struct rms_file *) 0); /* should never happen */
}

int rms_put(s,i)
  const char *s;
  struct rms_file *i;
{
  int status;

  i->r.rab$l_rbf = (char *) s;
  i->r.rab$w_rsz = strlen(s);
  sys_put(&(i->r)) ;
  rms$errno = i->r.rab$l_stv ;
  return(status & 1);
}

void rms_close(i)
  struct rms_file *i;
{
  int status;

  sys_close(&(i->f));
  rms$errno = i->f.fab$l_stv ;
  news_free(i);
}

/*
 *  openfiles
 *
 *  Open the item and uaf files for sharing
 */

void openfiles(nntp_sts)
  int nntp_sts;
{
#define REG_OPEN_OPT "mbc=32","mbf=2","rop=rah"
  char *in_line = 0, *gotenv, *cp, tmp[256];

  if (newsrc) fseek(newsrc,0,0);
  else newsrc = fopen(news_register,"r",REG_OPEN_OPT);

  if (   newsrc
      && (in_line = fgetl(newsrc)) != 0
      && *in_line == '~'
      && (cp = strchr(in_line+1,'~')) != 0) {
    char newsrcnode[132];

    if (nntp_sts < 0) {
      nntp_client = 0;
      fclose(newsrc); newsrc = 0;
      strcpy(news_register,"SYS$LOGIN:NEWSRC");
      if (   (newsrc = fopen(news_register,"r",REG_OPEN_OPT)) != 0
	  && (in_line = fgetl(newsrc)) != 0
	  && *in_line == '~') {
        fclose(newsrc); newsrc = 0;
	strcpy(news_register,"SYS$LOGIN:NEWSRC.LOCALDB");
	}
      }
    else {
      *cp-- = '\0';
      *(cp - 1) = '\0';
      strnncpy(newsrcnode,sizeof(newsrcnode), in_line+1,-1, 0);
      lower_case(newsrcnode);
      if (nntp_sts == 2 || nntp_sts == 4) {
        if (strcmp(newsrcnode,nntp_node)) {
	  fclose(newsrc); newsrc = 0;
	  strcpy(news_register,"SYS$LOGIN:NEWSRC#");
          strnncat(news_register,news_register_m,-1, nntp_node,-1, 0);
	  while ((cp = strchr(news_register,'.')) != 0) *cp = '-';
	  if ((cp = strchr(news_register,'#')) != 0) *cp = '.';
          if (   (newsrc = fopen(news_register,"r",REG_OPEN_OPT)) != 0
	      && (in_line = fgetl(newsrc)) != 0
	      && *in_line == '~' && (cp = strchr(in_line+1,'~')) != 0) {
            *cp-- = '\0';
            *(cp - 1) = '\0';
            }
          else cp = 0;
          }
	}
      else strnncpy(nntp_node,nntp_node_m, newsrcnode,-1, 0);

      if (cp && nntp_sts < 3) {
	nntp_proto = atoi(cp);
        nntp_proto = available_proto(nntp_proto);
        if (!nntp_sts) nntp_sts = 4; else nntp_sts += 2;
	}
      nntp_client = 1;
      }
    }
  else if (newsrc && nntp_client) {
		/* newsrc file exists, but is not configured as a server
		   - AND NO LOGICALS FOR THE LOCAL DB EXIST */
    if (nntp_sts <= 0) {
      printf("NEWS: Logical names (NEWS_ROOT, NEWS_DEVICE) not defined\n");
      exit(1);
      }
    fclose(newsrc);
    newsrc = 0;
    strcpy(news_register,"SYS$LOGIN:NEWSRC#");
    strnncat(news_register,news_register_m,-1, nntp_node,-1, 0);
    while ((cp = strchr(news_register,'.')) != 0) *cp = '-';
    if ((cp = strchr(news_register,'#')) != 0) *cp = '.';
    if (   (nntp_sts < 3) && (newsrc = fopen(news_register,"r",REG_OPEN_OPT)) 
									    != 0
	&& (in_line = fgetl(newsrc)) != 0
	&& *in_line == '~' && (cp = strchr(in_line+1,'~')) != 0) {
      *cp-- = '\0';
      *(cp - 1) = '\0';
      }
    else cp = 0;
    }
  else if (newsrc && !in_line) {
    fclose(newsrc);
    newsrc = 0;
    }

#if FAST_LOAD
  if (newsrc) {
    fseek(newsrc,0,0);
    if ((in_line = fgetl(newsrc)) != 0)
      fast_loading = (strchr(in_line,'>') != 0);
    }
#endif

  if (nntp_client) {
    unsigned short len;
    $DESCRIPTOR(dsc,tmp);

    if (!*nntp_node) {
      if ( (gotenv = news_getenv("NEWS_NNTP_SERVER",0)) )
        strnncpy(nntp_node,nntp_node_m, gotenv,-1, 0);
      else {
        len = 0;
        if (first_retr_call) get_input(&dsc,c$dsc("NEWS-SERVER Node: "),&len);
        if (!len) {
          printf("NEWS: No NNTP server node configured\n");
          exit(1);
          }
        tmp[len] = '\0';
        strnncpy(nntp_node,nntp_node_m, tmp,-1, 0);
        }
      lower_case(nntp_node);
      }
    if (nntp_sts < 3) {
      if ( (gotenv = news_getenv("NEWS_NNTP_PROTOCOL",0)) )
        strnncpy(tmp,sizeof(tmp), gotenv,-1, 0);
      else {
        len = 0;
        if (first_retr_call) get_input_dflt(&dsc,c$dsc("NEWS-SERVER Protocol: "),&len,c$dsc("DECNET"),0);
        if (!len) strcpy(tmp,"DECNET");
        else tmp[len] = '\0';
	}
      nntp_proto = name_to_proto(tmp,len);
      if (nntp_proto < 0) {
        printf("NEWS: Not a valid NNTP transport protocol code\n");
        exit(1);
        }
      nntp_proto = available_proto(nntp_proto);
      }
    }
#if !NNTP_CLIENT_ONLY
  else {
    int status;
/* ST 2/2/93 RMS Performance Patch */
	int buff_count=0;
	char *buff_count_ptr;
	int item_performance = FALSE;
	buff_count_ptr = news_getenv("NEWS_ITEMFILE_BUFFERCOUNT",0);
	if (buff_count_ptr != NULL)
	{
	    sscanf(buff_count_ptr,"%d",&buff_count);
	    item_performance = TRUE;
	}
    kid_valid = 0;
    if (init_lock()) exit(1);
    kid_valid = 1;
    itmfab = cc$rms_fab;
    itmfab.fab$b_bks = 3;
    itmfab.fab$b_fac = FAB$M_GET | FAB$M_PUT | FAB$M_UPD | FAB$M_DEL ;
    itmfab.fab$l_fna = (char *) ITM_FILENAME;
    itmfab.fab$b_fns = strlen(itmfab.fab$l_fna);
    itmfab.fab$l_fop = FAB$M_CIF;

    if (news_getenv("NEWS_ITEMFILE_DFW",0) != 0)
	    	itmfab.fab$l_fop |= FAB$M_DFW; /* deferred write */
    if (news_getenv("NEWS_ITEMFILE_RTV",0) != 0)
	itmfab.fab$b_rtv = 255;	       /* map the entire file */

    itmfab.fab$w_mrs = sizeof newsitm;
    itmfab.fab$b_org = FAB$C_IDX;
    itmfab.fab$b_rat = FAB$M_CR;
    itmfab.fab$b_rfm = FAB$C_FIX;
    itmfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;
    itmfab.fab$l_xab = (char *) &xabkey_1;

    xabkey_1 = cc$rms_xabkey;
    xabkey_1.xab$b_dtp = XAB$C_BN8;
    xabkey_1.xab$b_flg = 0;
    xabkey_1.xab$w_pos0 = (char *) &newsitm.itm_num - (char *) &newsitm;
    xabkey_1.xab$b_ref = 0;
    xabkey_1.xab$b_siz0 = 8;
    xabkey_1.xab$l_nxt = (char *) &xabkey_2;

    xabkey_2 = cc$rms_xabkey;
    xabkey_2.xab$b_dtp = XAB$C_STG;
    xabkey_2.xab$b_flg = 0;
    xabkey_2.xab$w_pos0 = (char *) &newsitm.itm_id - (char *) &newsitm;
    xabkey_2.xab$w_pos1 = (char *) &newsitm.itm_grp - (char *) &newsitm;
    xabkey_2.xab$b_ref = 1;
    xabkey_2.xab$b_siz0 = IDLEN;
    xabkey_2.xab$b_siz1 = 4;
    xabkey_2.xab$l_nxt = (char *) &xabpro_1;

    xabpro_1 = cc$rms_xabpro;
    xabpro_1.xab$w_pro = 0xEE00;
    
    itmrab = cc$rms_rab;
    itmrab.rab$l_fab = &itmfab;
    itmrab.rab$b_krf = 0;
    itmrab.rab$b_ksz = 8;
    itmrab.rab$l_ubf = (char *) &newsitm;
    itmrab.rab$w_usz = sizeof newsitm;
    itmrab.rab$l_rbf = (char *) &newsitm;
    itmrab.rab$w_rsz = sizeof newsitm;

/* ST 3/2/93  - use up to 127 buffers for the item file */
    if (item_performance) 
	itmrab.rab$b_mbf = min(buff_count,127);

    if (news_getenv("NEWS_ITEMFILE_FASTDELETE",0) != 0)
	itmrab.rab$l_rop |= RAB$M_FDL;

    sysprv();
    if (!sys_open_nofnf(&itmfab)) {
#ifndef V58_UPGRADE_MANUAL
      if (!no_priv()) { if (v58_upgrade()) sys_create(&itmfab); }
      else {
	printf("\tThis seems to be a first time NEWS execution in a new news\n");
	printf("\tenvironment.\n\n");
	printf("\tIf this is true, you aren't running from a process which is\n");
	printf("\tan authorized NEWS manager, therefore the NEWS database files\n");
	printf("\tcan not be created.  Make sure that this command is invoked\n");
	printf("\tfrom a VMS account which has the NEWS Manager Identifier, and\n");
	printf("\talso make sure that the NEWS program is installed with\n");
	printf("\tthe correct privileges prior to running this program again.\n");
	printf("\tIf you have already run authorize to grant the NEWS Manager\n");
	printf("\tIdentifier to this account, try logging out and then back in\n");
	printf("\tto make sure that your process actually has the identifier.\n\n");
        _ck_sys(NEWS$_CHECKENV,0,0);
      }
#else
      puts("You need to perform an upgrade of the group and item files so that");
      puts("they can be read by this version of NEWS");
#endif
      }
    if (status == RMS$_CREATED) {
      printf("\tNEWS - first time installation...\n");
      first_time();
      printf("\tNEWS - create new ITEM file\n");
      }
/* 6/7/94 - saul@hnrc.tufts.edu add global buffer handling */
      {
	int buff_count=0;
	char *buff_count_ptr;
	buff_count_ptr = news_getenv("NEWS_ITEMFILE_GLOBAL_BUFFERCOUNT",0);
	if (buff_count_ptr != NULL)
	{
	    sscanf(buff_count_ptr,"%d",&buff_count);

/* the file's global buffer count gets loaded on open by RMS, use the
    maximum of the stored value and the logical defined value */

	    if (itmfab.fab$w_gbc < buff_count) itmfab.fab$w_gbc = buff_count;
	}
    }
    sys_connect(&itmrab);
    { int old_itmrop = itmrab.rab$l_rop;
      itmrab.rab$l_rop |= RAB$M_RRL | RAB$M_NLK;
      if (sys_get_nornf(&itmrab)) {
        if (itmrab.rab$w_rsz < sizeof newsitm) {
          v59_file = 1;
#ifndef V60_UPGRADE_MANUAL
          if (!no_priv()) {
            sys_close(&itmfab);
            v60_upgrade();
            sys_open(&itmfab);
            sys_connect(&itmrab);
            v59_file = 0;
            }
#endif
          }
        }      
      /* restoring probably unnecessary, but trying to keep the changes local */
      itmrab.rab$l_rop = old_itmrop;
      }
    nosysprv();

    /* open the index newsgroup descriptor file for full access */
/* ST 3/2/93 - RMS Performance Patches */
{
	int buff_count=0;
	char *buff_count_ptr;
	int group_performance = FALSE;
	buff_count_ptr = news_getenv("NEWS_GROUPFILE_BUFFERCOUNT",0);
	if (buff_count_ptr != NULL)
	{
	    sscanf(buff_count_ptr,"%d",&buff_count);
	    group_performance = TRUE;
	}

    grpfab = cc$rms_fab;
    grpfab.fab$b_bks = 3;
    grpfab.fab$b_fac = FAB$M_GET | FAB$M_PUT | FAB$M_UPD | FAB$M_DEL ;
    grpfab.fab$l_fna = (char *) GRP_FILENAME;
    grpfab.fab$b_fns = strlen(grpfab.fab$l_fna);
    grpfab.fab$l_fop = FAB$M_CIF;


/* 6/6/94 saul@hnrc.tufts.edu - optionally turn on other performance
				enhancements */

    if (news_getenv("NEWS_GROUPFILE_DFW",0) != 0)
	    	grpfab.fab$l_fop |= FAB$M_DFW; /* deferred write */
    if (news_getenv("NEWS_GROUPFILE_RTV",0) != 0)
		grpfab.fab$b_rtv = 255;	       /* map the entire file */

    grpfab.fab$w_mrs = NEWS_GRPFIL_RSZ;
    grpfab.fab$b_org = FAB$C_IDX;
    grpfab.fab$b_rat = FAB$M_CR;
    grpfab.fab$b_rfm = FAB$C_FIX;
    grpfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;
    grpfab.fab$l_xab = (char *) &xabkey_3;

    xabkey_3 = cc$rms_xabkey;
    xabkey_3.xab$b_dtp = XAB$C_STG;
    xabkey_3.xab$b_flg = 0;
    xabkey_3.xab$w_pos0 = (char *) &newsgrp.grp_name - (char *) &newsgrp;
    xabkey_3.xab$b_ref = 0;
    xabkey_3.xab$b_siz0 = GRPNAMELEN_F;
    xabkey_3.xab$l_nxt = (char *) &xabkey_4;

    xabkey_4 = cc$rms_xabkey;
    xabkey_4.xab$b_dtp = XAB$C_BN4;
    xabkey_4.xab$b_flg = 0;
    xabkey_4.xab$w_pos0 = (char *) &newsgrp.grp_num - (char *) &newsgrp;
    xabkey_4.xab$b_ref = 1;
    xabkey_4.xab$b_siz0 = 4;
    xabkey_4.xab$l_nxt = (char *) &xabpro_2;

    xabpro_2 = cc$rms_xabpro;
    xabpro_2.xab$w_pro = 0xEE00;

    grprab = cc$rms_rab;
    grprab.rab$l_fab = &grpfab;
    grprab.rab$l_ubf = (char *) &newsgrp;
    grprab.rab$w_usz = NEWS_GRPFIL_RSZ;
    grprab.rab$l_rbf = (char *) &newsgrp;
    grprab.rab$w_rsz = NEWS_GRPFIL_RSZ;

/* ST 3/2/93 RMS Performance */
    if (group_performance)
	grprab.rab$b_mbf = min(buff_count,127);
}

    if (news_getenv("NEWS_GROUPFILE_FASTDELETE",0) != 0)
	grprab.rab$l_rop |= RAB$M_FDL;

    sysprv();
    if (no_priv()) sys_open(&grpfab);
    else sys_create(&grpfab);

/* 6/7/94 - saul@hnrc.tufts.edu add global buffer handling */
      {
	int buff_count=0;
	char *buff_count_ptr;
	buff_count_ptr = news_getenv("NEWS_GROUPFILE_GLOBAL_BUFFERCOUNT",0);
	if (buff_count_ptr != NULL)
	{
	    sscanf(buff_count_ptr,"%d",&buff_count);

/* the file's global buffer count gets loaded on open by RMS, use the
    maximum of the stored value and the logical defined value */

	    if (grpfab.fab$w_gbc < buff_count) grpfab.fab$w_gbc = buff_count;
	}
    }
    if (status != RMS$_CREATED) {
      sys_connect(&grprab);
      news_assert(xabkey_3.xab$b_cod == XAB$C_KEY && xabkey_3.xab$b_ref == 0);
      if (xabkey_3.xab$b_siz0 != GRPNAMELEN_F) {
        printf("Group name size mismatch.\n");
        printf("  Expected key0 size (compile-time constant GRPNAMELEN_F): %d bytes.\n", GRPNAMELEN_F);
        printf("  Actual key0 size in the file NEWS_ROOT:NEWS.GROUPS:      %d bytes.\n", xabkey_3.xab$b_siz0);
        printf("  Either match the constant GRPNAMELEN_F in NEWSDEFINE.H\n");
        printf("  to the file key size and recompile, or convert file\n");
        printf("  NEWS_ROOT:NEWS.GROUPS using GROUPS_CONVERT_V62.COM.\n\n");
        exit(NEWS$_GRPNAMKSZ);
        }
      }
    else {
      sys_connect(&grprab);
      printf("\tNEWS - create new GROUP file\n");
      memset((char *) &newsgrp, 0, sizeof(newsgrp));
      strcpy(newsgrp.grp_name, "NEWSGROUP INDEX");
      newsgrp.grp_life = GRP_TIME;
      newsgrp.grp_itmlife = EXP_TIME;
      time((time_t *) &newsgrp.grp_credate);
      newsgrp.grp_entdate = newsgrp.grp_credate;
      newsgrp.grp_flags |= NEWS_M_NNTPCACHE;
      newsgrp.grp_srvcache = 2;
      grprab.rab$w_rsz = NEWS_GRPFIL_RSZ;
      sys_put(&grprab);
      }
    nosysprv();
    }
#endif /* !NNTP_CLIENT_ONLY */
#if FAST_LOAD
  if (fast_loading && !all_loaded) read_reg_file();
  else
#endif
  open_groups(DIR_ALL);
}

/*
 *  open_groups
 * 
 *  Set up the newsgroup array
 */

#if NNTP_CLIENT

static int initng_count = 0,
	   initng_sorted = 0;	/* True => ga[] is sorted by group name */

static int initng(cp)
  const char *cp;
{
  register int g;
  int first, last, retval = 0;
  int j,ok;
  char newsg[GRPNAMELEN],mod[132];
  int newsg_l; /* strlen(newsg) */

#if !SILENT_CLIENT_STARTUP
  if (first_retr_call && !(++initng_count % 25)
#if FAST_LOAD
                      && (!fast_loading)
#endif
						) fputc('.',stdout);
#endif

  /* The following sscanf statement is the greatest single consumer of time
   * in the NNTP client: (e.g. 13% of total elapsed time during startup)      */
  /*   chop_str(cp,'\n');                                           */
  /*   if (sscanf(cp,"%s %d %d %s",newsg,&last,&first,mod) == 4) {  */
  /* we'll hard-code it for efficiency:              mark.martinec@nsc.ijs.si */
  ok = 1;
  for (j=0; (unsigned)*cp > (unsigned)' '; cp++)
    if (j < sizeof(newsg)-1) newsg[j++] = *cp;
  newsg[newsg_l=j] = '\0';  if (j<1) ok = 0;
  for (; *cp && *cp!='\n'; cp++) if (*cp!=' ' && *cp!='\t') break;
  last = first = 0;
  if (! (*cp>='0' && *cp<='9') ) ok = 0;
  else {
    last = 0; while (*cp=='0') cp++;
    while (*cp>='0' && *cp<='9') last = 10*last + (*cp++ - (int)'0');
    }
  for (; *cp && *cp!='\n'; cp++) if (*cp!=' ' && *cp!='\t') break;
  if (! (*cp>='0' && *cp<='9') ) ok = 0;
  else {
    first = 0; while (*cp=='0') cp++;
    while (*cp>='0' && *cp<='9') first = 10*first + (*cp++ - (int)'0');
    }
  for (; *cp && *cp!='\n'; cp++) if (*cp!=' ' && *cp!='\t') break;
  for (j=0; (unsigned)*cp > (unsigned)' '; cp++)
    if (j < sizeof(mod)-1) mod[j++] = *cp;
  mod[j] = '\0';  if (j<1) ok = 0;

  if (ok) {
    if ( *mod == 'x' || *mod == '=' ) return (0);
    ga[0]->grp_count += 1;
   /*
    * RFC977:  3.2.1. GROUP
    *   Note that the name of the newsgroup is not case-dependent.
    *   It must otherwise match a newsgroup obtained from the
    *   LIST command or an error will result.
    */
    strncpy(newsgrp.grp_name, newsg, sizeof(newsgrp.grp_name));
    ++newsgrp.grp_num;
    newsgrp.grp_topnum = last;
    newsgrp.grp_count = max(((last - first) + 1),0);
    if (!last) newsgrp.grp_count = 0;
    newsgrp.grp_topic[0] = '\0';
    newsgrp.grp_notice[0] = '\0';
    newsgrp.grp_flags = NEWS_M_NNTPSRV;
    if (tolower(*mod) == 'm') newsgrp.grp_flags |= NEWS_M_MAILMODERATE;
    if (tolower(*mod) != 'n') newsgrp.grp_flags |= NEWS_M_WRITE_ACCESS;
    else newsgrp.grp_flags |= NEWS_M_NOWRITE_SET;
    newsgrp.grp_flags |= NEWS_M_ACCESS_CHECKED;
    if (ga_size > (ga_malloc - 5)) {
      ga_malloc += 250;
      ga = news_realloc(ga,ga_malloc * (sizeof *ga));
      }
    /* figure out where to add the group */
    g = ++ga_size;	/* default is at end of [expanded] list */
    if (initng_sorted) {
      while (--g	/* while greater than 0 and name out of order... */
	  && (*newsg < *ga[g]->grp_name || strcmp(newsg,ga[g]->grp_name) < 0))
	continue;
      ++g;		/* insert new group here */
      if (g < ga_size)	/* shift rest of array up one slot */
	memmove(&ga[g+1], &ga[g], (ga_size - g) * (sizeof *ga));
    }
    /* add the group */
    ga[g] = new_group(&newsgrp);
    if (newsg_l > GRPNAMELEN_F) {
      /* replace the truncated group name with the full name */

      /*grp_name is now allocated with grpname_malloc(), don't call free() !
       * if (ga[g]->grp_name) news_free(ga[g]->grp_name);
       * ga[g]->grp_name = news_strdup(newsg,newsg_l);
       */
      ga[g]->grp_name = grpname_malloc(newsg_l+1);
      strnncpy(ga[g]->grp_name,newsg_l+1, newsg,newsg_l, 0);
      }
    ga[g]->grp_unread = ga[g]->grp_count;
    ga[g]->grp_firstnum = first;
    ga[g]->grp_funread = first;        /* guess - corrected in read_reg_file*/
    ga[g]->grp_topreadnum = first - 1; /* for previously seen groups */
    ga[g]->grp_display_indx = ++grp_display_size;
    retval = g;
    if (initng_sorted) {
      int gi = 0;
      for (g = 1; g <= ga_size; ++g)
	if (ga[g]->grp_display_indx)
	  ga[g]->grp_display_indx = ++gi;
      }
    }
  return(retval);
}

static int initng_compare(v_left, v_right)
  const void *v_left, *v_right;
{
  const GRP_PTR *left = v_left, *right = v_right;
  return strcmp((*left)->grp_name, (*right)->grp_name);
}

static void initng_sort()
{
  if (!initng_sorted) {
    register int g, i = 0;
    /* sort the unordered group array */
    qsort(ga+1, ga_size, sizeof *ga, initng_compare);
    for (g = 1; g <= ga_size; ++g)
      if (ga[g]->grp_display_indx)
	ga[g]->grp_display_indx = ++i;
    initng_sorted = 1;
    }
}

static int inittopics(cp)
  char *cp;
{
  char *tp;
  int g;

  chop_str(cp,'\n');
  for (tp=cp; *tp && !isspace(*tp); tp++) {}
  if (*tp) *tp++ = '\0';
  lower_case(cp);
  if ((g = ga_exact_name(cp)) != 0) {
    if (ga[g]->grp_topic)
      { news_free(ga[g]->grp_topic); ga[g]->grp_topic = NULL; }
    }
  if (!*tp || !g) return 0;
  while (isspace(*tp)) ++tp;
  ga[g]->grp_topic = news_strdup(tp,-1);
  return 0;
}
  
#if NNTP_USE_XGNOTICE
static int initnotices(cp)
  char *cp;
{
  char *tp;
  int g;

  chop_str(cp,'\n');
  if ((tp = chop_str_plus(cp,' ')) == 0) return 0;
  lower_case(cp);
  if ((g = ga_exact_name(cp)) != 0) {
    if (ga[g]->grp_notice) news_free(ga[g]->grp_notice);
    ga[g]->grp_notice = news_strdup(tp,-1);
    }
  return 0;
}
#endif

int initgroups(g)
  int g;
{
  int c,f,l;
  char *rcall;

  strcpy(err_oline,"GROUP "); err_oline_cat(ga[g]->grp_name);
  if (nntp_one_call(nntp_node,nntp_proto,err_oline,&rcall) == 211) {
    sscanf(rcall,"211 %d %d %d %*s",&c,&f,&l);
    ga[g]->grp_count = c;
    ga[g]->grp_firstnum = f;
    ga[g]->grp_topnum = l;
    }
  return 0;
}
#endif

void open_groups(cd)
  int cd;
{
#if !NNTP_CLIENT_ONLY
  int status;
  FILE *fpd;
  char *dp;
  struct FAB dirfab;
  char dir_name[256];
#endif

  if (nntp_client) {
#if !NNTP_CLIENT
    exit(NEWS$_NONNTP);
#else
    initng_count = 0;
#if !SILENT_CLIENT_STARTUP
    if (first_retr_call) {
      if (nntp_proto == DECNET)
        printf("Connecting to server @ %s (using %s)\n",nntp_node,"DECnet");
      else
        printf("Connecting to server @ %s (using TCP [%s])\n",
               nntp_node,proto_name(nntp_proto));
      }

#endif
#if FAST_LOAD
    if (fast_loading && !all_loaded) start_group_load();
    else
#endif
    {
    cur_dir_type = DIR_ALL;
    ga_malloc = 50;
    ga = (GRP_PTR *) news_malloc(ga_malloc * (sizeof *ga));
    ga[0] = news_malloc(sizeof(GRP)); memset(ga[0],0,sizeof(GRP));
    ga[0]->grp_life = GRP_TIME;
    ga[0]->grp_itmlife = EXP_TIME;
    time((time_t *)&(ga[0]->grp_credate));
    ga[0]->grp_entdate = ga[0]->grp_credate;
    ga[0]->grp_flags = NEWS_M_NNTPSRV;
    ga[0]->grp_srvproto = nntp_proto;
    ga[0]->grp_srvnode = news_strdup(nntp_node,-1);

/*???*/
    ga[0]->grp_reg = NEWGROUP_REG | (1 & (profile_flags & PROFILE_NEWREGISTER));

    ga_size = 0;
    grp_display_size = 0;
    }

#if !SILENT_CLIENT_STARTUP
    if (first_retr_call) printf("Retrieving newsgroups:");
#endif
    if ((!nntp_get_info(nntp_node,nntp_proto,"LIST",215,0,initng)
	 && first_retr_call) || ga[0]->grp_count == 0) {
      printf("\n");
      printf("NEWS: Failed to retrieve newsgroups from remote node\n");
      printf("      Connection terminated\n");
      exit(SS$_ABORT | 0x10000000);
      }
    grpname_malloc(-1);
    initng_sort();
    if (first_retr_call) printf(":");	/* end of LIST load */

#if NNTP_USE_XGTITLE
    if (!nntp_get_info(nntp_node,nntp_proto,"XGTITLE *",282,0,inittopics))
#endif
      nntp_get_info(nntp_node,nntp_proto,"LIST NEWSGROUPS",215,0,inittopics);
#if NNTP_USE_XGNOTICE
    nntp_get_info(nntp_node,nntp_proto,"XGNOTICE *",282,0,initnotices);
#endif
    if (first_retr_call) printf(":");	/* end of XGTITLE/XGNOTICE load */
  
/*#### TOO SLOW !!  
    {
      register int g, i = 0;
      for (g = 1; g <= ga_size; ++g)
	if (ga[g]->grp_firstnum <= 1) {
	  if (first_retr_call && !i) printf("\nRetrieving newsgroup counts:");
	  if (first_retr_call && !(++i % 10)) printf(".");
	  initgroups(g);
	  }
    }
  ####*/
  
    if (first_retr_call) printf("\n");
    first_retr_call = 0;
#endif  /* NNTP_CLIENT */
    }

#if !NNTP_CLIENT_ONLY
#if NNTP_CLIENT
  else {
#endif
#if FAST_LOAD
    if (fast_loading && !all_loaded) start_group_load();
    else
#endif
    {
    grprab.rab$l_kbf = (char *) c$rfi(0);
    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;
    sys_get(&grprab);
    cur_dir_type = DIR_ALL;
    ga_malloc = (newsgrp.grp_topnum + 5);
    ga = (GRP_PTR *) news_malloc(ga_malloc * (sizeof(GRP)));
    ga[0] = new_group(&newsgrp);
    ga_size = 0;
    grp_display_size = 0;
    }

    dirfab = cc$rms_fab;
    dirfab.fab$b_fac = FAB$M_GET;
    dirfab.fab$l_fna = dir_name;
    dirfab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

    grprab.rab$b_krf = 0;
    grprab.rab$b_rac = RAB$C_SEQ;
    sys_rewind(&grprab);
    while (sys_get_noeof(&grprab)) {
      if (!newsgrp.grp_num) continue;

#if FULL_DIRECTORY_ACCESS_CHECK
      sprintf(dir_name,Dir_template,util_dir(newsgrp.grp_name));
      dp = strrchr(dir_name,'.'); *dp = ']';
      dp = strchr(dir_name,'^'); *dp = '.';
      dirfab.fab$b_fns = strlen(dirfab.fab$l_fna);
      if (sys$open(&dirfab) & 1) sys_close(&dirfab);
      else continue;
#endif

      newsgrp.grp_flags &= ~(NEWS_M_WRITE_ACCESS | NEWS_M_MOD_ACCESS | NEWS_M_MOD_USER | NEWS_M_ACCESS_CHECKED);
      if (no_priv() && newsgrp.grp_flags & NEWS_M_RESTRICT_SET) {
        if (newsgrp.grp_flags & NEWS_M_MAILMODERATE) {
          char mod[132];
          sprintf(mod,"%.60s@%.60s",usr_username,Node_address);
          if (!strcmp(mod,moderator_address(newsgrp.grp_name))) {
            newsgrp.grp_flags |= (NEWS_M_ACCESS_CHECKED | NEWS_M_MOD_USER | NEWS_M_WRITE_ACCESS);
            }
          }
        if (!(newsgrp.grp_flags & NEWS_M_ACCESS_CHECKED)) {

#if DIRECTORY_ACCESS_CHECK && (!FULL_DIRECTORY_ACCESS_CHECK)
          sprintf(dir_name,Dir_template,util_dir(newsgrp.grp_name));
          dp = strrchr(dir_name,'.'); *dp = ']';
          dp = strchr(dir_name,'^'); *dp = '.';
          dirfab.fab$b_fns = strlen(dirfab.fab$l_fna);
          if (sys$open(&dirfab) & 1) sys_close(&dirfab);
          else continue;
#endif

          newsgrp.grp_flags |= NEWS_M_ACCESS_CHECKED;
          sprintf(itm_fname,Access_template,util_dir(newsgrp.grp_name));
          sysprv();
          if (!(fpd = fopen(itm_fname,"r"))) {
            nosysprv();

#if (!DIRECTORY_ACCESS_CHECK) && (!FULL_DIRECTORY_ACCESS_CHECK)
            sprintf(dir_name,Dir_template,util_dir(newsgrp.grp_name));
            dp = strrchr(dir_name,'.'); *dp = ']';
            dp = strchr(dir_name,'^'); *dp = '.';
            dirfab.fab$b_fns = strlen(dirfab.fab$l_fna);
            if (sys$open(&dirfab) & 1) sys_close(&dirfab);
            else continue;
#endif
            }
          else {
            int non_member = 1;
            char *cp;

            if (!(newsgrp.grp_flags & NEWS_M_NOWRITE_SET))  newsgrp.grp_flags |= NEWS_M_WRITE_ACCESS;

            { char acline[256];
              char *ap, *op;

              while (fgets(acline,sizeof(acline),fpd)) {
                if (*acline == '#') continue;
                if ((cp = strchr(acline,'\n')) != 0) *cp = ' ';
                lower_case(acline);
                op = chop_str(acline,':');
                cp = chop_str(acline,' ');
                ap = chop_str(acline,'@');
                if (!strcmp(acline,usr_username) || wild_match(usr_username,acline) || idmatch(acline)) {
                  if (cp) *cp = ' ';
                  if (ap) *ap = '@';
                  if (strchr(acline,'@')) {
                    char cknode[132];
                    sprintf(cknode,"@%.130s ",Node_address);
                    if (!substrcmp(acline,cknode)) continue;
                    }
                  if (substrcmp(cp," write ")) newsgrp.grp_flags |= NEWS_M_WRITE_ACCESS;
                  if (substrcmp(cp," nowrite ")) newsgrp.grp_flags &= ~NEWS_M_WRITE_ACCESS;
                  if (   substrcmp(cp," moderate ")
                      || substrcmp(cp," moderator ")
                      || substrcmp(cp," supervise "))
	  	  newsgrp.grp_flags |= (NEWS_M_MOD_USER | NEWS_M_WRITE_ACCESS);
                  non_member = 0;
                  }
                }
            }
            fclose(fpd);
            nosysprv();
            if (non_member) continue;
            }
          }
        }

      if (ga_size > (ga_malloc - 5)) {
        ga_malloc += 100;
        ga = news_realloc(ga,ga_malloc * (sizeof *ga));
        }
      ga[++ga_size] = new_group(&newsgrp);
      ga[ga_size]->grp_unread = ga[ga_size]->grp_count;
      ga[ga_size]->grp_reg = NEWGROUP_REG | (1 & (profile_flags & PROFILE_NEWREGISTER));
      ga[ga_size]->grp_display_indx = ++grp_display_size;
      }
    if (status != RMS$_EOF) c$cks(status);
#if NNTP_CLIENT
    }
#endif
#endif
  read_reg_file();
}

/*
 *  closefiles
 *
 *  Close RMS files, shutdown the screen, log usage,
 *  write the register file, and send any printer output
 *  to a nominated queue.
 */
void closefiles()
{
  closefiles_opt(1,1);
}

/*
 *  closefiles_opt
 *
 *  Close RMS files, shutdown the screen, log usage,
 *  and optionally: write the register file, and
 *  send any printer output to a nominated queue.
 */
void
closefiles_opt(write_reg,print_files)
  int write_reg;    /* enable writing of new version of NEWSRC */
  int print_files;  /* enable enqueueing of print jobs */
{
  closing_files++;  /* this prevents loop during abnormal shutdown */
#if !NNTP_CLIENT_ONLY
  if (!nntp_client) {
    int status;
    sysprv();
    sys_close(&itmfab);
    sys_close(&grpfab);
    nosysprv();
    close_hist_file();
    }
#endif
  close_mail_file();
  close_nntp_file();
  if (!write_reg)
    log_to_usage_file(1,0);
  else {
    write_reg_file();
 /* log_to_usage_file(1,0);   ... already done by write_reg_file() */
    }
  noscreen();
  if (print_files) print_exit();
  if (closing_files>0) closing_files--;
}

/*
 *  get_input
 *
 *  much the same as lib$get_input - but uses SMG display
 */

int get_input(get_dsc,prompt_dsc,out_len)
  struct dsc$descriptor_s *get_dsc;
  struct dsc$descriptor_const_s *prompt_dsc;
  unsigned short *out_len;
{
  return(get_input_general(get_dsc,prompt_dsc,out_len,0,0,2,1));
}

/*
 *  get_input_dflt
 *
 *  get input, with default input string supplied
 */

int get_input_dflt(get_dsc,prompt_dsc,out_len,init_dsc,trm)
  struct dsc$descriptor_s *get_dsc;
  struct dsc$descriptor_const_s *prompt_dsc,*init_dsc;
  unsigned short *out_len,*trm;
{
  return(get_input_general(get_dsc,prompt_dsc,out_len,init_dsc,trm,2,1));
}


/*
 *  get_input_general
 *
 *  low-level general purpose SMG get input
 */

int get_input_general(get_dsc,prompt_dsc,out_len,init_dsc,trm,row,line_editing)
/* all by-ref & by_desc parameters except the get_dsc are optional (addr 0) */
  struct dsc$descriptor_s *get_dsc;    /* where to put the entered string */
  struct dsc$descriptor_const_s *prompt_dsc; /* prompt string */
  struct dsc$descriptor_const_s *init_dsc;   /* initial (default) string */
  unsigned short *out_len;        /* length of the returned string in get_dsc */
  unsigned short *trm;            /* terminator character as returned by SMG  */
  int row;          /* row in the trailer_vd where the data entry takes place */
  int line_editing;           /* true enables line editing, false disables it */
{
  int status,stat;
  int use_tmp_prompt = 0;             /* true if prompt_str != tmp_prompt_str */
  int use_tmp_init = 0;               /* true if init_str   != tmp_init_str   */
  int prompt_str_l = !prompt_dsc ? 0 : prompt_dsc->dsc$w_length;
  int init_str_l   = !init_dsc   ? 0 : init_dsc->dsc$w_length;
  unsigned short get_str_l; /* length of the string as returned by SMG$READ...*/
  unsigned short terminator;          /* terminator key (trm may be NUL) */
  unsigned old_char1,old_char2;       /* saved terminal characteristics */

  if (row<1) row = 1;
  if (smg_active) {
    _c$cks(smg$add_key_def(&keytab,c$dsc("CTRLW"),0,
                           c$ac(SMG$M_KEY_TERMINATE+SMG$M_KEY_NOECHO),0,0));
    if (line_editing) {
      smg$delete_key_def(&keytab,c$dsc("UP"),0);
      smg$delete_key_def(&keytab,c$dsc("DOWN"),0);
      smg$delete_key_def(&keytab,c$dsc("UP"),c$dsc("GOLD"));
      smg$delete_key_def(&keytab,c$dsc("DOWN"),c$dsc("GOLD"));
      _c$cks(smg$set_term_characteristics(&pid, 0,c$ac(TT2$M_EDITING),
               c$ac(TT$M_WRAP),0, &old_char1,&old_char2));
      }
    else {
      _c$cks(smg$add_key_def(&keytab,c$dsc("UP"),0,c$ac(SMG$M_KEY_TERMINATE+SMG$M_KEY_NOECHO),c$dsc("UP"),0));
      _c$cks(smg$add_key_def(&keytab,c$dsc("DOWN"),0,c$ac(SMG$M_KEY_TERMINATE+SMG$M_KEY_NOECHO),c$dsc("DOWN"),0));
      _c$cks(smg$add_key_def(&keytab,c$dsc("UP"),c$dsc("GOLD"),c$ac(SMG$M_KEY_TERMINATE+SMG$M_KEY_NOECHO),c$dsc("TOP"),0));
      _c$cks(smg$add_key_def(&keytab,c$dsc("DOWN"),c$dsc("GOLD"),c$ac(SMG$M_KEY_TERMINATE+SMG$M_KEY_NOECHO),c$dsc("BOTTOM"),0));
      _c$cks(smg$set_term_characteristics(&pid, 0,0,
               c$ac(TT$M_WRAP),c$ac(TT2$M_EDITING), &old_char1,&old_char2));
      }
    _c$cks(smg$erase_line(&trailer_vd,&row,c$ac(1)));
    _c$cks(smg$set_cursor_abs(&trailer_vd,&row,c$ac(1)));
    _c$cks(smg$end_pasteboard_update(&pid));
    }
/*
  SMG$READ_COMPOSED_LINE requires that the
    prompt string size  +  entered (or default) string size
  do not exceed the number of visible columns in the virtual display
  (actually it must be <= devcol-1 to avoid read autotermination).

  Long init/prompt strings appear in ANU-NEWS in two forms:
  either the initial string is too long (e.g. 'Newsgroups: <list>' )
  or the prompt string is too long (e.g. 'Post to <list> [y]: ').
*/
#define tmp_prompt_str_size 128 /* max size of the (possibly collapsed) prompt*/
#define tmp_init_str_size   256 /* max size of the (poss. collapsed) init str */
#define tmp_get_str_size    256 /* max size of the user's response str */
#define prompt_tail_l 16     /* leave that much of a prompt after an ellipsis */
#define entry_str_min 12     /* (arbitrary) min. field size for user's entry  */

  if (!smg_active) {                   /* not limited to virtual screen width */
    stat = smg$read_composed_line(&kid,&keytab,get_dsc,prompt_dsc,&get_str_l,
             0,0,init_dsc,0,0,0,&terminator);
    if (! (stat&1) ) { get_str_l = 0; terminator = SMG$K_TRM_UNKNOWN; }
    }
  else {
/*  Prompt + initial/minimal_entry string is too long.
    Make a copy of init_str and prompt_str, truncate them if necessary,
    append/insert an ellipsis ("...") as appropriate.
*/
    int tmp_prompt_str_max;   /* collapse long prompt string to this size  */
    int tmp_init_str_max;     /* collapse long initial string to this size */
    int tmp_prompt_str_l;     /* final true size of the prompt string  */
    int tmp_init_str_l;       /* final true size of the initial string */
    char tmp_prompt_str[tmp_prompt_str_size];
    char tmp_init_str[tmp_init_str_size];
    char tmp_get_str[tmp_get_str_size];
    $DESCRIPTOR_CONST(tmp_prompt_dsc,tmp_prompt_str);
    $DESCRIPTOR_CONST(tmp_init_dsc,tmp_init_str);
    $DESCRIPTOR(tmp_get_dsc,tmp_get_str);
    $DESCRIPTOR(aux_get_dsc,tmp_get_str);

 /* calculate maximal prompt size that we can afford at the given screen size */
    tmp_prompt_str_max = tmp_prompt_str_size-1;       /* available array size */
    if (devcol && tmp_prompt_str_max > devcol*4/5)
      tmp_prompt_str_max = devcol*4/5;      /* but no more than 4/5 of a line */
    if (devcol && tmp_prompt_str_max > devcol-entry_str_min)
      tmp_prompt_str_max = devcol-entry_str_min; /* leave space for data entry*/
    if (tmp_prompt_str_max < 5+prompt_tail_l)
      tmp_prompt_str_max = 5+prompt_tail_l; /* but we need at least this much */
    if (devcol) news_assert(tmp_prompt_str_max<devcol);   /* is it feasible ? */

 /* handle prompt string first - make a (possibly truncated) copy of it */
    if (prompt_str_l <= tmp_prompt_str_max) {
      tmp_prompt_str_l = prompt_str_l; tmp_prompt_str[0] = '\0';
   /* no need to copy, we will not need tmp_prompt_str */
   /* if (prompt_dsc) strcpy(tmp_prompt_str, prompt_dsc->dsc$a_pointer); */
      }
    else {     /* insert "..." somewhere towards the end of the prompt string */
      use_tmp_prompt = 1;
      tmp_prompt_str_l = tmp_prompt_str_max - 5 - prompt_tail_l;
      strncpy(tmp_prompt_str, prompt_dsc->dsc$a_pointer, tmp_prompt_str_l);
      tmp_prompt_str[tmp_prompt_str_l] = '\0';
      strcat(tmp_prompt_str," ... ");  tmp_prompt_str_l += 5;
      strcat(tmp_prompt_str,
        (prompt_dsc->dsc$a_pointer)+prompt_str_l-prompt_tail_l);
      tmp_prompt_str_l = tmp_prompt_str_max;
      }

 /* calculate maximal initial string size that we can afford */
    tmp_init_str_max = tmp_init_str_size-1;           /* available array size */
    if (devcol && tmp_init_str_max > (devcol-1)-tmp_prompt_str_l)
      tmp_init_str_max = (devcol-1) - tmp_prompt_str_l; /* lineWidth - prompt */
    news_assert(tmp_init_str_max>=4); /*(strlen(" ...")==4); is it feasible ? */

 /* handle initial (default) string */
    if (init_str_l <= tmp_init_str_max) {
      tmp_init_str_l = init_str_l; tmp_init_str[0] = '\0';
   /* no need to copy, we will not need tmp_init_str */
   /* if (init_dsc) strcpy(tmp_init_str,init_dsc->dsc$a_pointer); */
      }
    else {
      use_tmp_init = 1;
      tmp_init_str_l = tmp_init_str_max-4;   /* reserve four chars for " ..." */
      if (init_dsc)
        strncpy(tmp_init_str,init_dsc->dsc$a_pointer,tmp_init_str_l);
      tmp_init_str[tmp_init_str_l] = '\0';
      strcat(tmp_init_str," ..."); tmp_init_str_l += 4;
      }
    tmp_init_dsc.dsc$w_length = tmp_init_str_l;
    tmp_prompt_dsc.dsc$w_length = tmp_prompt_str_l;
    tmp_get_dsc.dsc$w_length = min(get_dsc->dsc$w_length,tmp_get_str_size-1);

    brdcst_col = 1;
    if (use_tmp_prompt) brdcst_col += tmp_prompt_dsc.dsc$w_length;
    else brdcst_col += (!prompt_dsc ? 0 : prompt_dsc->dsc$w_length);

    stat = smg$read_composed_line(&kid,&keytab,
             (use_tmp_init ? &tmp_get_dsc : get_dsc),
             (use_tmp_prompt ? &tmp_prompt_dsc : prompt_dsc),
             &get_str_l,&trailer_vd,0,
             (use_tmp_init ? &tmp_init_dsc : init_dsc),
             0,0,0,&terminator);
    if (! (stat&1) ) { get_str_l = 0; terminator = SMG$K_TRM_UNKNOWN; }
 /* reading persists until data entry is properly terminated */
    while ((stat&1) &&
           (terminator==SMG$K_TRM_CTRLW || terminator==SMG$K_TRM_BUFFER_FULL)) {
      _c$cks(smg$begin_pasteboard_update(&pid));
      _c$cks(smg$erase_line(&trailer_vd,&row,c$ac(1)));
      if (terminator == SMG$K_TRM_CTRLW) {
        _c$cks(smg$repaint_screen(&pid));
        _c$cks(smg$set_keypad_mode(&kid,c$ac(SMG$M_KEYPAD_APPLICATION)));
        }
      else if (terminator == SMG$K_TRM_BUFFER_FULL) {
     /* chop off the last character to prevent autotermination */
        if (get_str_l>0) get_str_l--;
        _c$cks(smg$ring_bell(&trailer_vd,0));
        }
      _c$cks(smg$set_cursor_abs(&trailer_vd,&row,c$ac(1)));
      _c$cks(smg$replace_input_line(&kid,0,0));/*discard last line from recall*/
      _c$cks(smg$end_pasteboard_update(&pid));
      aux_get_dsc.dsc$a_pointer = 
        (use_tmp_init ? tmp_get_dsc.dsc$a_pointer : get_dsc->dsc$a_pointer);
      aux_get_dsc.dsc$w_length = min(get_str_l,tmp_init_str_max);
      stat = smg$read_composed_line(&kid,&keytab,
               (use_tmp_init ? &tmp_get_dsc : get_dsc),
               (use_tmp_prompt ? &tmp_prompt_dsc : prompt_dsc),
               &get_str_l,&trailer_vd,0,
               &aux_get_dsc,          /* use entered string as initial string */
               0,0,0,&terminator);
      if (! (stat&1) ) { get_str_l = 0; terminator = SMG$K_TRM_UNKNOWN; }
      }
/*  Here comes the trick:
      if user did *not* modify the default (possibly truncated) string,
      then we return the *full* (not truncated) initial string.
    It is necessary to read into a temporary get_str just in case
    that get_dsc and init_dsc point to the same memory locations
    (like when reading 'Subject:' line).
*/
    if (!use_tmp_init)
      (get_dsc->dsc$a_pointer)[get_str_l] = '\0';
    else {
      tmp_get_str[get_str_l] = '\0';
      if (strcmp(tmp_get_str,tmp_init_str)!=0)
        strcpy(get_dsc->dsc$a_pointer, tmp_get_str);
      else {
        news_assert(init_str_l <= get_dsc->dsc$w_length);
        if (init_str_l > 0)
          strcpy(get_dsc->dsc$a_pointer, init_dsc->dsc$a_pointer);
        get_str_l = init_str_l;
        }
      }
    }
  if (trm) *trm = terminator;                  /* fill-in optional parameters */
  if (out_len) *out_len = get_str_l;
  if (((stat == SS$_ABORT) || (stat == SS$_CANCEL)) && news_lock_alarm) {
    closefiles();
    exit(1);
    }
  if (stat == SMG$_EOF) stat = RMS$_EOF;
  if (stat != RMS$_EOF) _c$cks(stat);
  if (smg_active) {
    _c$cks(smg$begin_pasteboard_update(&pid));
    _c$cks(smg$set_term_characteristics(&pid,
             &old_char1,&old_char2, c$rfi(~old_char1),c$rfi(~old_char2), 0,0));
    }
  /* the successful interaction resets the signalled_error_count since the user 
     is still in control and loop prevention mechanism is unnecessary */
  if ((stat&1) && (signalled_error_count>0)) signalled_error_count = 1;

  return(stat);
}

#if FAST_LOAD
void start_group_load()
{
  int status;

  if (nntp_client) {
    ga_malloc = 50;
    ga = (GRP_PTR *) news_malloc(ga_malloc * (sizeof *ga));

    cur_dir_type = DIR_ALL;

    newsgrp.grp_num = 0;
    newsgrp.grp_topnum = 0;
    newsgrp.grp_count = 0;
    newsgrp.grp_life = GRP_TIME;
    newsgrp.grp_itmlife = EXP_TIME;
    time((time_t *)&newsgrp.grp_credate);
    newsgrp.grp_entdate = newsgrp.grp_credate;
    newsgrp.grp_flags = 0;
    strnncpy(newsgrp.grp_srvnode,sizeof(newsgrp.grp_srvnode), nntp_node,-1, 0);
    newsgrp.grp_srvproto = nntp_proto;
    newsgrp.grp_flags = NEWS_M_NNTPSRV;
    newsgrp.grp_srvcache = 0;
    newsgrp.grp_topic[0] = '\0';
    newsgrp.grp_notice[0] = '\0';
    ga[0] = new_group(&newsgrp);

/*???*/
    ga[0]->grp_reg = NEWGROUP_REG | (1 & (profile_flags & PROFILE_NEWREGISTER));

    ga_size = 0;
    grp_display_size = 0;
    }
  else {
    grprab.rab$l_kbf = (char *) c$rfi(0);
    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;
    sys_get(&grprab);
    ga_malloc = (fast_loading ? 20 : (newsgrp.grp_topnum + 5));
    ga = (GRP_PTR *) news_malloc(ga_malloc * (sizeof *ga));
    cur_dir_type = DIR_ALL;
    ga[0] = new_group(&newsgrp);
    ga_size = 0;
    grp_display_size = 0;
    }
}

/*
 * 
 *
 * 
 */
int read_group( group_name )
  char *group_name;
{
  if (nntp_client) {
#if !NNTP_CLIENT
    int status;
    _ck_sys(NEWS$_NONNTP,0,0);
#else
    char *buffer;
    int first, last, count;

    strcpy(err_oline,"GROUP "); err_oline_cat(group_name);
    if (   (nntp_one_call(nntp_node,nntp_proto,err_oline,&buffer) == 211)
	&& (sscanf(buffer,"211 %d %d %d",&count, &first, &last) == 3)) {
      sprintf(err_oline,"%.200s %d %d y",group_name,last,first);
      return(initng(err_oline));
      }
    strcpy(err_oline,"Group ");
    err_oline_cat(group_name); err_oline_cat(" -> "); err_oline_cat(buffer);
    err_line(err_oline);
#endif
    return(0);
    }
#if !NNTP_CLIENT_ONLY
  else {
    int status;
    int ga_indx;
    char *cp, *ap, *op, *dp,
         mod[132], acline[256], cknode[132], dir_name[256];
    char grp_name_tmp[GRPNAMELEN_F];
    FILE *fpd;
    struct FAB dirfab;

    dirfab = cc$rms_fab;
    dirfab.fab$b_fac = FAB$M_GET;
    dirfab.fab$l_fna = dir_name;
    dirfab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

    strncpy(grp_name_tmp,group_name,GRPNAMELEN_F);
    grprab.rab$l_kbf = grp_name_tmp;
    grprab.rab$b_ksz = GRPNAMELEN_F;
    grprab.rab$b_krf = 0;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_KEY;
    sys_get_nornf(&grprab);
    if (status == RMS$_RNF) return(0);
    if (!newsgrp.grp_num) return(0);

#if FULL_DIRECTORY_ACCESS_CHECK
    sprintf(dir_name,Dir_template,util_dir(newsgrp.grp_name));
    dp = strrchr(dir_name,'.'); *dp = ']';
    dp = strchr(dir_name,'^'); *dp = '.';
    dirfab.fab$b_fns = strlen(dirfab.fab$l_fna);
    if (sys$open(&dirfab) & 1) sys_close(&dirfab);
    else return(0);
#endif

    newsgrp.grp_flags &= ~(NEWS_M_WRITE_ACCESS | NEWS_M_MOD_ACCESS | NEWS_M_MOD_USER | NEWS_M_ACCESS_CHECKED);
    if (no_priv() && newsgrp.grp_flags & NEWS_M_RESTRICT_SET) {
      if (newsgrp.grp_flags & NEWS_M_MAILMODERATE) {
        sprintf(mod,"%.60s@%.60s",usr_username,Node_address);
        if (!strcmp(mod,moderator_address(newsgrp.grp_name))) {
          newsgrp.grp_flags |= (NEWS_M_ACCESS_CHECKED | NEWS_M_MOD_USER | NEWS_M_WRITE_ACCESS);
          }
        }
      if (!(newsgrp.grp_flags & NEWS_M_ACCESS_CHECKED)) {

#if DIRECTORY_ACCESS_CHECK && (!FULL_DIRECTORY_ACCESS_CHECK)
        sprintf(dir_name,Dir_template,util_dir(newsgrp.grp_name));
        dp = strrchr(dir_name,'.'); *dp = ']';
        dp = strchr(dir_name,'^'); *dp = '.';
        dirfab.fab$b_fns = strlen(dirfab.fab$l_fna);
        if (sys$open(&dirfab) & 1) sys_close(&dirfab);
        else return(0);
#endif

        newsgrp.grp_flags |= NEWS_M_ACCESS_CHECKED;
        sprintf(itm_fname,Access_template,util_dir(newsgrp.grp_name));
        sysprv();
        if (!(fpd = fopen(itm_fname,"r"))) {
          nosysprv();

#if (!DIRECTORY_ACCESS_CHECK) && (!FULL_DIRECTORY_ACCESS_CHECK)
          sprintf(dir_name,Dir_template,util_dir(newsgrp.grp_name));
          dp = strrchr(dir_name,'.'); *dp = ']';
          dp = strchr(dir_name,'^'); *dp = '.';
          dirfab.fab$b_fns = strlen(dirfab.fab$l_fna);
          if (sys$open(&dirfab) & 1) sys_close(&dirfab);
          else return(0);
#endif
          }
        else {
          int non_member = 1;

          if (!(newsgrp.grp_flags & NEWS_M_NOWRITE_SET))  newsgrp.grp_flags |= NEWS_M_WRITE_ACCESS;
          while (fgets(acline,sizeof(acline),fpd)) {
            if (*acline == '#') continue;
            if ((cp = strchr(acline,'\n')) != 0) *cp = ' ';
            lower_case(acline);
            cp = chop_str(acline,' ');
            ap = chop_str(acline,'@');
            op = chop_str(acline,':');
            if (!strcmp(acline,usr_username) || wild_match(usr_username,acline) || idmatch(acline)) {
              if (cp) *cp = ' ';
              if (ap) *ap = '@';
              if (strchr(acline,'@')) {
                sprintf(cknode,"@%.130s ",Node_address);
                if (!substrcmp(cp,cknode)) continue;
                }
              if (substrcmp(cp," write ")) newsgrp.grp_flags |= NEWS_M_WRITE_ACCESS;
              if (substrcmp(cp," nowrite ")) newsgrp.grp_flags &= ~NEWS_M_WRITE_ACCESS;
              if (   substrcmp(cp," moderate ")
                  || substrcmp(cp," moderator ")
                  || substrcmp(cp," supervise "))
	        newsgrp.grp_flags |= (NEWS_M_MOD_USER | NEWS_M_WRITE_ACCESS);
              non_member = 0;
              }
            }
          fclose(fpd);
          nosysprv();
          if (non_member) return(0);
          }
        }
      }
    if (ga_size > (ga_malloc - 5)) {
      ga_malloc += 50;
      ga = news_realloc(ga,ga_malloc * (sizeof *ga));
      }
    ga_indx = ++ga_size;
    {
      for (;  (ga_indx>1) && (0 > strcmp(util_grp_name_str(newsgrp.grp_name),
                                         ga[ga_indx-1]->grp_name));
           --ga_indx)  ga[ga_indx] = ga[ga_indx-1];
    }
    ga[ga_indx] = new_group(&newsgrp);
    ga[ga_indx]->grp_unread = ga[ga_indx]->grp_count;
    ga[ga_indx]->grp_reg = NEWGROUP_REG | (1 & (profile_flags & PROFILE_NEWREGISTER));
    ga[ga_indx]->grp_display_indx = ++grp_display_size;
    return( ga_indx );
    }
#endif /* !NNTP_CLIENT_ONLY */
  news_assert_nonfatal(0); return(0);  /* should never happen */
}
#endif
