[comp.os.vms] uw nntp news reader part a

dorl@vms.macc.wisc.edu (Michael Dorl - MACC) (07/29/88)

******** copyright.doc ********

    Copyright (C) 1988, University of Wisconsin Board of Regents,
    all rights reserved.

    Anyone may reproduce this work, in whole or in part, provided
    that: (1) any copy of the entire work must show University of 
    Wisconsin as the source, and must include this notice; and (2)
    any other use of this work must acknowledge the fact that the
    material is coprighted by the University of Wisconsin Board of
    Regents and is used by permission.
    
******** install.doc ********
News - A Native VMS News Reading Program

1.0 Introduction

    News is a user agent program for reading News.  The news database
    is not kept on the VMS machine, instead the nntp protocol is used
    to access the data on a server machine.  The VMS machine must be
    connected to an IP network and must run the Wollongong WIN/VX
    network software.

2.0 Distribution Description

    The distribution consists of 19 files.  Each file is preceded by
    a line of the form...

    ******** file_name ********

    The following files are included...

    FileName              ND    Description

    COPYRIGHT.DOC		Copyright notice
    INSTALL.DOC      		This file
    CONFIG.DAT       	   *	Configuration data
    CONVERT.COM      	   *	Com file to rename Eunice save files
    GETUSERNAME.FOR  		News source (subroutine)
    INSTALL.COM      	   *	Com file to install news.exe
    LINKNEWS.COM    		Com file to link News.Exe
    MAKE.COM         		Com file to compile News source
    NEWS.CLD         	   *	News command syntax definition
    NEWS.DEF        		News source (include)
    NEWS.DOC        		A user description of News
    NEWS.FOR       		News source (main program and subroutines)
    NEWS_CLD_INSTALL.COM   *    Com file to add News command syntax to system
    SMG.DEF          		News source (include)
    SMG_ROUTINES.FOR		News source (include)
    TNEWS.CLD        		Com file to define TNews command for testing
    UP_DN_PRIV.FOR  		News source (subroutine)
    USER_OPEN.COMMON		News source (include)
    USER_OPEN.FOR   		News source (subroutine )

    Use an editor to break up the distribution into its parts.  I
    keep the file in directory .xxnews in my home directory.

3.0 Building News

    The source code needs to be compiled, use the make.com file to do
    this.  Make accpets a list of Fortran options.  If you want a
    listing of the source do...

      @make list

    otherwise do

      @make

    Now link News using the linknews.com.  If you intend to install
    News sytem wide, you might want to include the notraceback
    parameter....

      @linknews notraceback

4.0 Configuring Site Dependent News Data

    All site dependent data is kept in newsdir:config.dat.  This 
    includes...

      siteid		the name of the machine, used when posting
			articles.  Include everything needed after
			the username. eg. @a.b.c.edu

      distribution	a list of valid distributions.

      organization	text describing the organization controlling
			your machine.  Used when posting articles.

      server		the numeric ip number of the server.

    Edit config.dat to use your definitions.

5.0 Running News 

    You can run News from your account or you can install it in a
    system directory.

5.1 Running News without Installing It

    Define symbol newsdir pointing to the directory containing
    news.exe and config.dat.  Edit config.dat so that contains
    data for your site.  Edit TNews.Cld so that it points at the 
    file containing news.ex.  Define the TNews parameter with...

      $set command tnews

    Now run News with the TNews command.

5.2 Running News after Installing It

    In order to install news on a system wide basis you need to
    do the following...

      compile news.

      link news with the notraceback option.

      edit config.dat to conform to data at your site.

      create a system wide logical name, newsdir, pointing
      at a file containing files news.exe and those marked
      in the ND column in section 2.0.

      install news.exe using newsdir:install.com.  This is
      not strictly required but does allow News to obtain 
      the user personal name from the Sys$Sytem:VMSMail.Dat
      file for use when posting articles.

      add the News command to the system command tables
      using newsdir:news_cld_install.com.

      update your boot procedure to define newsdir and to
      install news.

6.0 User Documentation

    File news.doc contains a user description of the program.
    This file was written to describe News to the editor 
    developing a final user document.  Its quite terse,
    contains instructions to the editor, and is probably full
    of gramatical and spelling errors.  The final user
    document may be made available at a later date.

7.0 Copyright Notice

    Copyright (C) 1988, University of Wisconsin Board of Regents,
    all rights reserved.

    Anyone may reproduce this work, in whole or in part, provided
    that: (1) any copy of the entire work must show University of 
    Wisconsin as the source, and must include this notice; and (2)
    any other use of this work must acknowledge the fact that the
    material is coprighted by the University of Wisconsin Board of
    Regents and is used by permission.
    
******** config.dat ********
siteid: @vms.macc.wisc.edu
distribution: local, cs, uw, wi, usa, na, net, world, mod, comp, news
distribution: sci, rec, misc, soc, talk, macc
organization: University of Wisconsin Academic Computing Center
server: 128.104.30.17
******** convert.com ********
$ ! Convert.Com
$ !
$ ! Renames files in sys$login:news.dir form Eunice rrn form to
$ ! VMS news form.  
$
$ ! Get next file
$
$NextFile:
$ File = F$Search ("*.*;*")
$ If File .eqs. "" Then Goto Exit
$ L = F$Length(File)
$ X = F$Locate ("]",File)+1
$ Dir = F$Extract(0,X,File)
$ File = F$Extract (X,L-X,File)
$
$ ! Change all $5 to _
$
$ NewFile = File
$
$NextDot:
$ L = F$Length (NewFile)
$ X = F$Locate ("$5",NewFile) 
$ If X .eq. L Then Goto ExitDot
$ NewFile = F$Extract(0,X,NewFile) + "_" + F$Extract(X+2,L-X-2,NewFile)
$ Goto NextDot
$ExitDot:
$
$ ! Change rest of $ to nothing
$
$NextDollar:
$ L = F$Length (NewFile)
$ X = F$Locate ("$",NewFile) 
$ If X .eq. L Then Goto ExitDollar
$ NewFile = F$Extract(0,X,NewFile) + F$Extract(X+1,L-X-1,NewFile)
$ Goto NextDollar
$ExitDollar:
$
$ ! Now display result
$
$ If File .eqs. NewFile Then Goto NoChange
$ Rename/Log 'File' 'NewFile'
$
$NoChange:
$ Goto NextFile
$
$Exit:
$ Exit
******** getusername.for ********
       Integer Function GetUserName (UserId)

C Description:
C
C   Function to obtain the user's name
C   Returns success or failure.

	Include 'News.Def'

C Parameter Definitions

       Character *32	UserId

C External Routines

       External Sys$GetJPI
       Integer  Sys$GetJPI

C Local Definitions

       Include '($JPIDEF)'

       Character 	*(UserIdSz) 	LUserId
       Integer		*4		LUserIdLg

       Integer		*2 		WJPIItmLst(12)
       Integer 		*4		JPIItmLst(6)
       Equivalence 			(JPIItmLst,WJPIItmLst)

C Begin GetUserName

       WJPIItmLst(1) = UserIdSz
       WJPIItmLst(2) = JPI$_UserName
       JPIItmLst(2)  = %Loc(LUserId)
       JPIItmLst(3)  = %Loc(LUserIdLg)
       JPIItmLst(4)  = 0

       GetUserName = Sys$GetJPI (,,,JPIItmLst,,,)
       If (GetUserName) Then
         UserId = LUserId(1:LUserIdLg)
       EndIf

       Return

       End ! GetUserName
******** install.com ********
$Run Sys$system:Install
NewsDir:News/Delete
NewsDir:News/Open/Shared/Header/Priv=Bypass
$
$Run Sys$System:Install
NewsDir:News/Full
$
******** linknews.com ********
$If P1 .nes. "" Then P1 = "/" + P1
$Link'P1 news.obj,smg_routines,user_open,Up_Dn_Priv,-
twg$tcp:[netdist.lib]libnet.olb/lib,-
twg$tcp:[netdist.lib]libnetacc.olb/lib,-
twg$tcp:[netdist.lib]libnet.olb/lib,-
twg$tcp:[netdist.lib]libnetacc.olb/lib,-
twg$tcp:[netdist.lib]libnet.olb/lib,-
twg$tcp:[netdist.lib]libnetacc.olb/lib,-
twg$tcp:[netdist.lib]libnet.olb/lib,-
twg$tcp:[netdist.lib]libnetacc.olb/lib,-
twg$tcp:[netdist.lib]libnet.olb/lib,-
twg$tcp:[netdist.lib]libnetacc.olb/lib,-
twg$tcp:[netdist.lib]libnet.olb/lib,-
twg$tcp:[netdist.lib]libnetacc.olb/lib,-
twg$tcp:[netdist.lib]libnet.olb/lib
$
******** make.com ********
$If P1 .nes. "" Then P1 = "/" + P1
$For'P1 news
$For'P1 getusername
$For'P1 smg_routines
$For'P1 up_dn_priv
$For'P1 user_open
$
******** news.cld ********
Define Verb News
 Image     "NewsDir:News"
 Qualifier Header      Value(List)
 Qualifier Mark	       Value(Type=$Rest_Of_Line)
******** news.def ********
	Implicit None

C VMS Constants

	Parameter		UserIdSz = 31	! Length of a username

C Define incore group control structure

	Structure /GroupDef/

	  Character *32		Name		! Name
	  Logical		Subscribed	! User has subscribed
	  Logical		Newsrc_File	! Found in Newsrc file
          Logical		Active_File	! Found in active file
	  Integer   *4		Active_Start	! Active file start
	  Integer   *4		Active_End	! Active file end
	  Logical  		Active_Post	! Active file post flag
	  Integer   *2		Range_First	! Newsrc range list start
	  Integer   *2		Range_Last	! Newsrc range list end

	End Structure ! GroupDef

C Define group article range structure

	Structure /RangeDef/

	  Integer   *2          Next
	  Integer   *2	        Start
	  Integer   *2		End

	End Structure ! RangeDef

C Define Socket Structure

	Structure /Socket_IN_Def/

	  Integer   *2		SIN_Family
	  Integer   *2		SIN_Port
	  Integer   *4          SIN_Address
	  Byte 			SIN_Fill(8)

	End Structure ! Socket_IN_Def

C Define news parameters

	Parameter		LU_Newsrc      = 10
	Parameter		LU_Save        = 11
	Parameter		LU_EditIn      = 12
	Parameter		LU_EditOut     = 13
	Parameter		LU_Init        = 14
	Parameter		LU_VMSMail     = 15
	Parameter		LU_Signature   = 16

	Parameter		File_Name_Size = 128

	Parameter		Mx_Range = 10000
	Parameter		Mx_Group =  1000

C Define News common block
	
	Integer	    *4		Group_Count
	Record	    /GroupDef/	Group(Mx_Group)
	Integer	    *4		Range_Count
	Record	    /RangeDef/	Range(Mx_Range)
	Integer	    *4		Range_Free_List
	Integer     *4		Distribution_Count
	Character   *16		Distribution(50)
	Character *31		UserName
	Character *64		UserDirectory
	Character *128		UserPersonalName
	Character *64		UserMailDirectory
	Character *64		SiteId
	Character *64		Organization
	Byte			Server_IP_Number(4)
	Logical			Newsrc_Is_Open  ! .true. if user had a
						!  xx.newsrc file
	Character *20		Newsrc_CDT_VMS  ! xx.newsrc creation date time
						!  'dd-mmm-yyyy hh:mm:ss'
	Character *13 		Newsrc_CDT_News ! xx.newsrc creation date time
						!  'ddmmyy hhmmss'
	Character *16		Header(16)	! /Header = fields
	Integer   *4		Header_Count	!  Number of /header fields
	Logical			Header_Present  ! .true. if /header present
	Character		Mark_Character  ! Followup mark character
						! .true. if logical name
						!  MAIL$EDIT defined
	Logical			Mail_Cmd_Mail$Edit 
	Logical 		Rotated		! .true. if article last seen
						!  in rotated mode
	Logical 		Debug		! .true. if debug mode turned
						!  on by J command
	Logical			FirstFlag	! Used to skip multiple
						!  copies of subject line
						!   in Cmd_ArticleList

	Common 	    /News/
     $    Group_Count, Group,
     $	  Range_Count, Range, Range_Free_List,
     $    Distribution_Count, Distribution,
     $    UserName, UserDirectory, UserPersonalName, UserMailDirectory,
     $    SiteId, Organization,
     $	  Server_IP_Number,
     $    Newsrc_CDT_VMS, Newsrc_CDT_News, Newsrc_Is_Open,
     $	  Header_Present, Header_Count, Header,
     $    Mark_Character, Mail_Cmd_Mail$Edit, Rotated, Debug, FirstFlag

C WIN TCP/IP Services

	Integer  Connect
	External Connect
	Integer  HtoNS
	External HtoNS
	Integer  Recv
	External Recv
	Integer  Send
	External Send
	Integer  Socket
	External Socket

C WIN TCP/IP Constants

	Parameter AF_INet     = 2
	Parameter Sock_Stream = 1
	Parameter Sock_DGram  = 2

C TCP/IP Common Definitions

	Integer     *4		Channel
	Character *512		Recv_Buf
	Integer   *4		Recv_BufE
	Integer	  *4		Recv_BufS
	Common      /Server/ 	Channel, Recv_Buf, Recv_BufS, Recv_BufE

******** news.doc ********
News

1.0 Introduction

News is a native VMS news reading client that uses the network news
transport protocol to access news stored on a remote server.  It
was written by Michael Dorl at the Madison Academic Computing
Center, University of Wisconsin as a personal project to gain
familiarity with The Wollongong WIN/VX program interface to
the TCP/IP network.

Much of the look and feel of the user interface was shamelessly 
borrowed from the many fine news reading programs in the Unix
world.

2.0 User Documentation

News works best on screen oriented terminal.  It uses the DEC
SMG routines and supports all of the standard VMS terminal types.

The best way to learn about news is to try it.  But before you
do that, a few words on converting from your current Eunice rrn
news environment to your new news environment.

2.1 Converting from Eunice rrn to news

News keeps your news database in file sys$login:xx.newsrc.
This file has the same format as a rrn .newsrc file
except that its in RMS format rather than in Eunice stream format.
To make a xx.newsrc:

  $ Set default sys$login
  $ Copy .newsrc xx.newsrc
  $ Unixtovms xx.newsrc

News keeps your saved articles in directory sys$login:news.dir
instead of the Eunice rrn sys$login:$n$ews.dir directory. 
Create this directory and copy your saved news articles there
with the following commands:

  $ Set default sys$login
  $ Create/Dir [.news]
  $ Copy [.$n$ews]*.*;* [.news]*.*;*
  $ Set default [.news]
  $ Unixtovms *.*;*

The files in your [.news] directory still have the old Eunice
names.  These include $ characters used as shift markers and
also $5 pairs used as second and subsequent . characters.  For
example if you have been saving your comp.os.vms articles in the
default file, you will have file $c$omp.os$5vms.  Since the news
equivalent of this file is comp.os_vms, you may want to
rename files in your .news directory to the news equivalents.
No set of wild card cards will do this for you but there is a
command procedure which you can run to convert these names.

  $ Set default sys$login
  $ Set default [.news]
  $ @newsdir:convert

Once you satisfy yourself that news works and want to switch from
Eunice and rrn, don't forget to go back and delete the files in
your sys$login:$n$ews.dir and that directory.

2.2 First Time News Users

If you are a first time news user, you need take no special action.
The best thing to do is to dive right in.  News will notice that you have
no xx.newsrc file and assumme you want to look at all groups.  Run news
and start reading.  If a group looks like you are not interested in it,
unsubscribe by typing a u command.  Try not to be offended by anything you
see, the news system serves a diverse set of users.  Groups that might
offend the faint of heart include ???, ???, and ???.  You might want
to unsubscribe to these without reading anything in them.

2.3 News Operation

To run news, type:

  $ News

News starts up and performs a series of initialization operations.
Since some of these might take a while, news tells you what's going
on:

  Reading your XX.Newsrc file...

  Connecting to remote news server...
  200 dogie.macc.wisc.edu NNTP server version 1.5 GAMMA (6 feb 88) ready
  at Tue Apr 26 21:47:46 1988 (posting ok).

  Retrieving active file...

  Last News execution at 25 Apr 1988 21:12:13
  
News now goes through the news groups you have subscribed to prompting
you for an action for each one.   For example:

  Group comp.os.vms           available 242 - 396 unread: 1

  Group comp.os.vms action (<CR> b c d g h p n q u):

Legal actions at the group prompt include:

  <CR> 		Start reading
  b		Go back to the previous group
  c 		catchup, mark all articles as read
  c #		catchup through and including article #
  d		directory of unread articles
  d/g		directory of groups
  d/g pattern   directory of groups matching pattern.  Pattern may
		include * match any number of any character or $
		match any single character
  p		post an article
  p/x           post a rotated article
  g group	go to group 'group'
  h		help
  n		next group
  q 		quit
  u		unsubscribe from this group
  #		display article # 
  ^             first unread article
  $             last unread article

If you answer with a <CR>, news will cycle through the unread articles
in this group.  After displaying each article, news will prompt you for
an action.  

  Group comp.os.vms         available: 242 - 396  unread: 0
  Article 396

  From: jackjones@vms3.macc.wisc.edu
  Subject: Has anyone ever figured out how logical names work?
  ...
  Action (<CR> b c d f p g h k m n q r s u x):

Legal actions at the article prompt include:

  <CR> 		Next article
  b		Go back to the previous article
  c 		catchup, mark all articles as read
  c #		catchup through and including article #
  d		directory of unread articles
  d/g		directory of groups
  d/g pattern   directory of groups matching pattern.  Pattern may
		include * match any number of any character or $
		match any single character
  f		post a follow up article
  p		post an article
  p/x           post a rotated article
  g group	go to group 'group'
  h		help
  k		kill, mark as read, articles with this subject
  m		mark this article unread
  n		next group
  q 		quit
  r		refresh this article
  s		save this article
  u		unsubscribe from this group
  x		refresh this article in rotate 13 mode.
  z             next article with the same subject
  #		display article # 
  ^             first unread article
  $             last unread article

When news has processed all groups, it displays a end of groups
prompt:

  End groups, action (q p g):

Legal actions at the end of groups prompt include:

  <CR> 		exit from news
  b		Go back to the previous group
  d/g		directory of groups
  d/g pattern   directory of groups matching pattern.  Pattern may
		include * match any number of any character or $
		match any single character
  p		post an article
  p/x           post a rotated article
  g group	go to group 'group'
  h		help
  q 		quit

When you answer <CR> or q, news writes an updated xx.newsrc to your
home directory and exits.

  Closing News server connection...
  205 dogie.macc.wisc.edu closing connection.  Goodbye.

  Updating XX.Newsrc

3.0 Customizing News

News accepts the following qualifiers:

  /Header=(list)

  /Mark

Header selects which header lines you wish to see.  Since most articles
contain 10 - 15 header lines most of which you have no interest in,
you probably want to select only the few lines you need.  The following
works well for most folks:

  /Header=(Subject, From, Sender)

Mark allows you to select the character to be used to 'mark' the original
text in followup articles.  Its mainly used to defeat certain smart
news software attempting to limit the amount of followup text posted
to the net.

A good way to customize news is to include a line such as:

  news = "news/header=(from,subject,sender)/mark=""{"""

in your login.com file.

News uses the editor specified by MAIL$EDIT when composing posted
or followup articles.  It also appends the signature.mai file
from your VMS mail directory to articles you post.

4.0 Helpfull Hints

One of the most productive ways to use news is to print a directory
of unread articles in the group with the d command.  Look at these
and decide which ones you want to read.  Read these by specifying their
numbers or by using the z command to follow all articles with the
same subject.  When you have seen all you want, answer c to skip 
the remainder in the group.  

An alternative is to do a c # to catchup to all articles before the
one you wish to read, read that one with a <CR>, do another d to get
a refreshed list, and repeat until done.

5.0 Netiquette


******** news.for ********
	Include 'News.Def/list'
	Include 'SMG.Def/list'

C External Routines

	Integer			CLI$Get_Value
	Integer			CLI$Present
	Integer			Close_Newsrc
	Logical			Cmd_ArticleCatchUp
	Logical			Cmd_ArticleFirst
	Logical			Cmd_ArticleLast
	Integer			Cmd_ArticleNumber
	Integer			Cmd_ArticleSameSubj
	Integer			GetUserName
	Integer			GetUserDirectory
	Integer			GetInteger
	Integer			Group_Find
	Integer			Open_Newsrc
	Integer			Range_Find
	Integer			Range_Allocate
	Logical			SMG_More_Print
	Integer			SMG_Prompt
	Integer			Srv_Cmd
	Integer			Srv_Connect
	Integer			Srv_NetClose
	Integer			Srv_RdTxt
	Integer			Srv_Recv
	Integer			Srv_Send
	Integer			TransLog
	Integer			TrimLg
	Integer			UnRead

C Local Definitions

	Logical			A_Continue
	Logical			Action
	Logical			Any
	Character *512 		Buf
	Logical			ByReadChk
	Logical			BySubscribedChk
	Integer   *4		C
	Character *1		Ch
	Character *2		CRLF
	Logical      		Done
	Integer   *4		E
	Integer   *4		G
	Logical			G_Continue
	Integer   *4		Next_G
	Integer	  *4		I
	Character *512		Image
	Integer   *4		Lg
	Integer   *4		Lg2
	Integer   *4		Lg3
	Integer   *4		N
	Character *8		Num
	Character *8            Num2
	Character *8            Num3
	Logical			Ok
	Integer   *4		R
	Logical			RefreshArticle
	Logical			RefreshGroup
	Integer   *4		RS, RE, RU
	Integer   *4		S
	Integer   *4		Status
        Integer	  *4		U
	Integer   *4		Unavail_Start
	Integer   *4		Unavail_End
	Integer   *4		X

C Begin News

	Debug = .false.			! Debug is off

	Range_Free_List = 0		! Initialize Range item free list

	Group_Count = 0			! Initialize next group

	Range_Count = 0			! Initialize next range

	Header_Count = 0		! Number of /Header fields

	Call SetUpPriv			! Initialize privileges

C Determine if logical name MAIL$EDIT is defined

	If (TransLog('MAIL$EDIT',Buf,0)) Then
	  Mail_Cmd_Mail$Edit = .true.
	Else
	  Mail_Cmd_Mail$Edit = .false.
	EndIf

C Get our user name

	Status = GetUserName (UserName)
	If (.not. Status) Then
	  Call SMG_All_Print ('Error obtaining your username', '|')
	  Stop
	EndIf

C Get our .news directory

	Status = GetUserDirectory (UserDirectory)
	If (.not. Status) Then
	  Call SMG_All_Print
     $	    ('Error obtaining your login directory', '|')
	  Stop
	EndIf

	Call Get_Mail_Control		! Get Mail directory and
					!  personal name

	Call Ctrl_C			! Initialize control C handler

	Call SMG_Initialize		! Initialize screen routines

	Call SMG_All_Print		! Welcome user to news
     $	  ('News', '|')
	Call SMG_All_Print (' ', '|')

	Call Read_Init 			! Read News configuration file

	CRLF(1:1) = Char (13)
	CRLF(2:2) = Char (10)

C Get /Header parameter telling us what header lines to print

	Status = 1
	Do While (Status)
	  Header_Count = Header_Count + 1
	  Status = CLI$Get_Value ('HEADER', Header(Header_Count))
	EndDo
	Header_Count = Header_Count - 1
	Header_Present = Cli$Present ('HEADER')

C Get followup command mark character

	Status = CLI$Get_Value ('MARK', Mark_Character)
	If (.not. Status) Then
	  Mark_Character = '>'
	EndIf

C Read users XX.NEWSRC file

	Call SMG_All_Print ('Reading your XX.Newsrc file...', '|')
	Call SMG_All_Print (' ', '|')

	Status = Open_Newsrc()
	If (.not. Status) Then
	  Write (Buf, '(A,I)')
     $      ' Can''t open xx.newsrc, status = ', Status
	  Call SMG_All_Print (Buf, '|')
	EndIf

C Connect to server

	Call SMG_All_Print
     $	  ('Connecting to remote news server...', '|')
	Status = Srv_Connect()
	If (.not. Status) Then
	  Call SMG_All_Print 
     $      (
     $       ' Can not connect to remote news server!',
     $       '|'
     $      )
	  Stop
	EndIf

C Get initial signon line

	Status = Srv_Recv (Buf, Lg)
	If (.not. Status) Then
	  Write (Buf, '(A,I)')
     $      ' Error receiving data from news server, status = ',
     $	    Status
	  Call SMG_All_Print (Buf, '|')
	  Stop
	EndIf

	If (Lg .gt. 0) Then
	  Call SMG_All_Print (Buf (1:Lg), '|')
	EndIf

	Call SMG_All_Print (' ', '|')

C Send a list command

	Call SMG_All_Print ('Retrieving active file...', '|')

	If (.not. Srv_Cmd('list',Buf,Lg)) Then
	  Stop 'Server failed'
	EndIf

	If (Buf(1:3) .ne. '215') Then
	  Call SMG_All_Print
     $      (
     $       ' Unexpected LIST command result, ' // Buf(1:Lg),
     $       '|'
     $      )
	  Stop
	EndIf

C Retrieve list output and update group

	Status = Srv_Recv (Buf, Lg)
	Do While (Status .and. (Buf(1:Lg) .ne. '.'))
	  I = Index (Buf(1:Lg), ' ')
	  If (I .eq. 0) Then
	    Stop 'Bad active file entry'
	  EndIf
	  X = 1
	  Do While 
     $      (
     $       (X .le. Group_Count) 
     $      .and. 
     $       (Group(X).Name .ne. Buf(1:I))
     $      )
	    X = X + 1
	  EndDo

	  If (X .gt. Group_Count) Then
	    Group_Count = X
	    Group(X).Name = Buf(1:I)
	    Group(X).Active_File = .true.
	    Group(X).Newsrc_File = .false.
	    Group(X).Subscribed = .false.
	    Group(X).Range_First = 0
	    Group(X).Range_Last = 0
	  End If

C If user had no XX.Newsrc file, assumme he is subscribed to this group

	  If (.not. Newsrc_Is_Open) Then
	    Group(X).Subscribed = .true.
	  EndIf
	
C Now get last first and p flags

	  Group(X).Active_End   = GetInteger (Buf, I, Lg)
          Group(X).Active_Start = GetInteger (Buf, I, Lg)

	  Call GetField (C, Buf, I, Lg)
	  If ((C .eq. 'y') .or. (C .eq. 'Y')) Then
	    Group(X).Active_Post  = .true.
	  Else
	    Group(X).Active_File = .true.
	  EndIf

	  Status = Srv_Recv (Buf, Lg)
	End Do

C Now get list of groups created since XX.NEWSRC created

	If (Newsrc_Is_Open) Then
	  Call SMG_All_Print (' ', '|')
	  Call SMG_All_Print
     $      (
     $       'Last News execution at ' // Newsrc_CDT_VMS // '.',
     $       '|'
     $      )

	  Call SMG_All_Print (' ', '|')
	  Call SMG_All_Print ('Retrieving new groups...', '|')

	  Buf = 'newgroups ' // Newsrc_CDT_News
	  If (.not. Srv_Cmd(Buf(1:23),Buf,Lg)) Then
	    Stop 'Server failed'
	  EndIf

 	  If (Buf(1:3) .ne. '231') Then
	    Call SMG_All_Print
     $        (
     $         ' Unexpected LIST command result, ' // Buf(1:Lg),
     $         '|'
     $        )
	    Stop
	  EndIf

C Retrieve newgroups output and update group

	  Any = .false.
	  Status = Srv_Recv (Buf, Lg)
	  Do While (Status .and. (Buf(1:Lg) .ne. '.'))
	    If (Buf(1:Lg) .ne. '.') Then
	      G = Group_Find (Buf(1:Lg))
	      If (G .ne. 0) Then
	        Any = .true.
	        Group(G).Subscribed = .true.
	        Call SMG_All_Print ('New group ' // Buf(1:Lg), '|')
	      EndIf
	    EndIf
	    Status = Srv_Recv (Buf, Lg)
	  EndDo

C If any new groups found, give user a chance to see them

	  If (Any) Then
	    Call SMG_Prompt (Buf, 'Type <return> to continue',Lg)
	  EndIf

	EndIf

C Scan through groups displaying new messages

	G = 1
	G_Continue = .true.
	BySubscribedChk = .false.
	More_Input = ' '
	Done = .false.

1	Do While (G_Continue .and. (G .le. Group_Count))

	  Next_G = G + 1 

	  If (BySubscribedChk .or. (Group(G).Subscribed)) Then

	    Call CacheHdr_Init (G)		! Initialize header cache

	    S = Group(G).Active_Start		! Starting article
	    E = Group(G).Active_End		! Ending article
	    R = Group(G).Range_First

	    If (S .eq. 0) Then 
	      S = 1
	    End If

C Adjust S depending on articles we have read to avoid some overhead

	    Do While (R .ne. 0)

C Trim range S:E if S is in this range 

	      If 
     $		(
     $		 (S .ge. Range(R).Start)
     $		.and. 
     $		 (S .le. Range(R).End)
     $		) 
     $        Then
		S = Range(R).End + 1
	      EndIf

C Do next range

	      R = Range(R).Next

	    End Do

	    U = UnRead (G)

C If there is something to read, open the group

	    If (BySubscribedChk .or. (S .le. E)) Then

	      BySubscribedChk = .false.

C Attempt to select group

	      If 
     $          (.not. Srv_Cmd ('group ' // Group(G).Name, Buf, Lg))
     $	      Then
	        Stop 'Server failed'
	      End If

	      If (Buf(1:3) .eq. '211') Then

C Good status indicates group selected

	        A_Continue = .true.

C Find out what user wants with this group

	        ByReadChk = .false.
	        Action = .false.
		RefreshGroup = .true.

	        Do While (.not. Action)

		  If (RefreshGroup) Then
		    RefreshGroup = .false.
	            Call ItoS (Group(G).Active_Start, Num, Lg)
	            Call ItoS (Group(G).Active_End, Num2, Lg2)
	            Call ItoS (U, Num3, Lg3)
     	            More_Hdg_One = 'Group ' // Group(G).Name //
     $		    ' available: ' // Num(1:Lg) //
     $	   	    ' - ' // Num2(1:Lg2) //
     $              '  unread: ' // Num3(1:Lg3)
	            More_Hdg_Two = '@'
	            Call More_Heading
		  EndIf

		  If (More_Input .ne. ' ') Then
		    Buf = More_Input
		    Lg = TrimLg(More_Input)
		    More_Input = ' '
		  Else
	            Status = SMG_Prompt
     $                (
     $                 Buf,
     $		       'Group ' //
     $ 		       Group(G).Name(1:TrimLg(Group(G).Name)) // 
     $		       ' action (<CR> b c d g h n p q u): ',
     $                 Lg
     $                )
		  EndIf

	          Action = .true.

		  If      (Buf(1:1) .eq. ' ') Then  ! Process group

	          Else If (Buf(1:1) .eq. 'b') Then  ! Backup
		    Next_G = G - 1
		    BySubscribedChk = .true.
	            A_Continue = .false.
	          Else If (Buf(1:1) .eq. 'c') Then  ! Catchup
		    Action = Cmd_ArticleCatchUp (G, S, Buf(1:Lg), U)
		    RefreshGroup = .true.	! Need new heading
		    Next_G = G + 1
		  Else If (Buf(1:3) .eq. 'd/g') Then ! Dir/Group
		    Call Cmd_GroupList (Buf(1:Lg))
		    Action = .false.
	          Else If (Buf(1:1) .eq. 'd') Then  ! Dir
		    Call Cmd_ArticleList (G, Buf(1:Lg))
		    Action = .false.
	          Else If (Buf(1:1) .eq. 'f') Then  ! Followup
		    Call Cmd_ArticleNone
	            Action = .false.
		  Else If (Buf(1:1) .eq. 'p') Then  ! Post
		    Call Cmd_ArticlePost (G, Buf(1:Lg))
		    Action = .false.
	          Else If (Buf(1:1) .eq. 'g') Then  ! Group g
		    I = Group_Find (Buf(3:34))
	            If (I .eq. 0) Then
	              Action = .false.
	              Call SMG_All_Print 
     $		        (
     $		         ' No such group as ' // 
     $                     Buf (3:2+TrimLg(Buf(3:34))),
     $		         '|'
     $		        )
	            Else
		      BySubscribedChk = .true.
		      Next_G = I
	              A_Continue = .false.
		      Group(G).Subscribed = .true.
	            End If
	          Else If (Buf(1:1) .eq. 'h') Then  ! Help
		    Call Cmd_Help
		    Action = .false.
	          Else If (Buf(1:1) .eq. 'k') Then  ! Kill
		    Call Cmd_ArticleNone
	            Action = .false.
	          Else If (Buf(1:1) .eq. 'm') Then  ! Mark unread
		    Call Cmd_ArticleNone
	            Action = .false.
	          Else If (Buf(1:1) .eq. 'n') Then  ! Next group
		    A_Continue = .false.
	          Else If (Buf(1:1) .eq. 'q') Then  ! Quit
	            G_Continue = .false.
	            A_Continue = .false.
	          Else If (Buf(1:1) .eq. 'r') Then  ! Refresh
		    Call Cmd_ArticleNone
	            Action = .false.
	          Else If (Buf(1:1) .eq. 's') Then  ! Save
		    Call Cmd_ArticleNone
	            Action = .false.
	          Else If (Buf(1:1) .eq. 'u') Then  ! Unsubscribe
		    Group(G).Subscribed = .false.
	            A_Continue = .false.
	          Else If (Buf(1:1) .eq. 'x') Then  ! Refresh rot mode
		    Call Cmd_ArticleNone
	            Action = .false.
		  Else If (Buf(1:1) .eq. 'z') Then  ! Next article 
		    Call Cmd_ArticleNone	    !  same subject
	            Action = .false.
		  Else If (Buf(1:1) .eq. '^') Then  ! First unread article
		    Action = Cmd_ArticleFirst (G,S)
		  Else If (Buf(1:1) .eq. '$') Then  ! Last unread article
		    Action = Cmd_ArticleLast (G,S)
		  Else If
     $		    (
     $		     (Buf(1:1) .ge. '0')
     $		    .and.
     $		     (Buf(1:1) .le. '9')
     $              )
     $            Then
		    If (Cmd_ArticleNumber (G, Buf(1:Lg), S)) Then
	    	      ByReadChk = .true.
		    Else
		      Action = .false. 
		    EndIf
		  Else If (Buf(1:1) .eq. 'j') Then
		    Action = .false.
		    Debug = .not. Debug
		    If (Debug) Then
		      Call SMG_All_Print ('Debug is on', '|')
	            Else
		      Call SMG_All_Print ('Debug is off', '|')
		    EndIf
	          Else
		    Call SMG_All_Print ('Huh?', '|')
	            Action = .false.
	          End If

	        End Do

C Now page through the articles in range S - E

	        Unavail_Start = 0		! No unavailable articles
		Unavail_End = 0			!  so far

	        Do While ((E .gt. 0 ) .and. (S .le. E) .and. A_Continue)

		  If
     $		    (
     $		     ByReadChk
     $		    .or.
     $               (Range_Find(G, S, .false.) .eq. 0)
     $              )
     $            Then

		    ByReadChk = .false.
	            Write (Buf, '(A, I7)') 'stat ', S

		    If (.not. Srv_Cmd (Buf(1:15), Buf, Lg)) Then
		      Stop 'Server failed'
		    End If

		    If (Buf(1:3) .ne. '223') Then
		      If (Unavail_Start .eq. 0) Then
		        Unavail_Start = S
	              End If
		      Unavail_End = S
		      Status = Range_Find (G, S, .true., U)
		    EndIf

		    If
     $		      (
     $		       ((Unavail_Start .ne. 0) .and. (S .eq. E))
     $		      .or.
     $		       (Buf(1:3) .eq. '223') 
     $		      )
     $		    Then

		      More_Input = ' '

		      If (Unavail_Start .ne. 0) Then
		        S = Unavail_End
c			If (Buf(1:3) .eq. '220') Then
c			  Call Srv_RdTxt
c     $                      (.false., .false., %Val(0))	! Skip article
c		        EndIf
		        Call Unavail_Print (Unavail_Start, Unavail_End)
		      Else
		        Status = Range_Find (G, S, .true., U)
		        Call ItoS (Group(G).Active_Start, Num, Lg)
	      	        Call ItoS (Group(G).Active_End, Num2, Lg2)
	      	        Call ItoS (U, Num3, Lg3)
     	      	        More_Hdg_One = 'Group ' // Group(G).Name //
     $			  ' available: ' // Num(1:Lg) //
     $			  ' - ' // Num2(1:Lg2) //
     $                    '  unread: ' // Num3(1:Lg3)
	                Call ItoS (S, Num, Lg)
     		        More_Hdg_Two = 'article ' // Num
			Rotated = .false.
			Call Cmd_ArticleDisplay (G, S, Rotated)
		      EndIf

C What does user want to do with this article?
	
	      	      Action = .false.
		      RefreshArticle = .false.

	      	      Do While (.not. Action)

		        Call ItoS (Group(G).Active_Start, Num, Lg)
	      	        Call ItoS (Group(G).Active_End, Num2, Lg2)
	      	        Call ItoS (U, Num3, Lg3)
     	      	        More_Hdg_One = 'Group ' // Group(G).Name //
     $			  ' available: ' // Num(1:Lg) //
     $			  ' - ' // Num2(1:Lg2) //
     $                    '  unread: ' // Num3(1:Lg3)
	                Call ItoS (S, Num, Lg)
     		        More_Hdg_Two = 'article ' // Num

		        If (RefreshArticle) Then
			  RefreshArticle = .false.
			  Call More_Heading
		        EndIf

			Call ItoS (S, Num, Lg)

			If (More_Input .ne. ' ') Then

			  Buf = More_Input
			  Lg = TrimLg (More_Input)
			  More_Input = ' '

			Else

			  Call SMG_Print (' ','|')
	        	  Status = SMG_Prompt
     $ 			    (
     $			     Buf,
     $			     'End article ' // Num(1:Lg) //
     $			       ' action (<CR> ' //
     $ 			       'b c d f g h k m n p q r s u x z): ',
     $			     Lg
     $			    )

			EndIf

	        	Action = .true.
			If      (Buf(1:1) .eq. ' ') Then  ! Next article
			  S = S + 1
	       		Else If (Buf(1:1) .eq. 'b') Then  ! Backup
		  	  S = S - 1
			  ByReadChk = .true.
	        	Else If (Buf(1:1) .eq. 'c') Then  ! Catchup
			  Action = Cmd_ArticleCatchUp(G, S, Buf(1:Lg), U)
			  RefreshArticle = .true.
		  	Else If (Buf(1:3) .eq. 'd/g') Then ! Dir/Group
		    	  Call Cmd_GroupList (Buf(1:Lg))
		    	  Action = .false.
	        	Else If (Buf(1:1) .eq. 'd') Then  ! Dir
		  	  Call Cmd_ArticleList (G, Buf(1:Lg))
		  	  Action = .false.
	        	Else If (Buf(1:1) .eq. 'f') Then  ! Followup
			  Call Cmd_ArticleFollowUp (G, S, Buf(1:Lg))
			  Action = .false.
		  	Else If (Buf(1:1) .eq. 'p') Then  ! Post
		 	  Call Cmd_ArticlePost (G, Buf(1:Lg))
			  Action = .false.
	        	Else If (Buf(1:1) .eq. 'g') Then  ! Group g
		  	  I = Group_Find (Buf(3:34))
	                  If (I .eq. 0) Then
	                    Action = .false.
	                    Call SMG_All_Print
     $			      (
     $			       'No such group as ' // 
     $			         Buf(2:2+TrimLg(Buf(3:34))),
     $			       '|'
     $			      )
	          	  Else
		            BySubscribedChk = .true.
		    	    Next_G = I
	            	    A_Continue = .false.
			    Group(G).Subscribed = .true.
	          	  End If
	        	Else If (Buf(1:1) .eq. 'h') Then  ! Help
			  Call Cmd_Help
			  Action = .false.
	        	Else If (Buf(1:1) .eq. 'k') Then  ! Kill
			  Call Cmd_ArticleKill (G, S, U)
			  RefreshArticle = .true.
			  Action = .false.
	        	Else If (Buf(1:1) .eq. 'm') Then  ! Mark unread
			  Call Cmd_ArticleMark (G, S, U)
			  RefreshArticle = .true.
			  Action = .false.
	        	Else If (Buf(1:1) .eq. 'n') Then  ! Next article
			  S = S + 1
	        	Else If (Buf(1:1) .eq. 'q') Then  ! Quit
	          	  A_Continue = .false.
	        	Else If (Buf(1:1) .eq. 'r') Then  ! Refresh
			  ByReadChk = .true.
	        	Else If (Buf(1:1) .eq. 's') Then  ! Save
			  Call Cmd_ArticleSave (G,S,Buf)
			  Action = .false.
	        	Else If (Buf(1:1) .eq. 'u') Then  ! Unsubscribe
		  	  Group(G).Subscribed = .false.
	          	  A_Continue = .false.
	                Else If (Buf(1:1) .eq. 'x') Then  ! Refresh rot mode
			  Rotated = .true.
		          Call Cmd_ArticleDisplay (G, S, Rotated)
			  Action = .false.
		  	Else If (Buf(1:1) .eq. 'z') Then  ! Next article 
							  !  same subject
		  	  Action = Cmd_ArticleSameSubj (G, S) 
		        Else If (Buf(1:1) .eq. '^') Then  ! First unread article
		          Action = Cmd_ArticleFirst (G, S)
		        Else If (Buf(1:1) .eq. '$') Then  ! Last unread article
		          Action = Cmd_ArticleLast (G,S)
			Else If 			  ! Article number
     $			  (
     $			   (Buf(1:1) .ge. '0') 
     $			  .and. 
     $			   (Buf(1:1) .le. '9')
     $			  ) 
     $			Then
		          If (Cmd_ArticleNumber (G, Buf(1:Lg), S)) Then
	    		    ByReadChk = .true.
			  Else
		            Action = .false. 
		          EndIf
		  	Else If (Buf(1:1) .eq. 'j') Then
		    	  Action = .false.
		    	  Debug = .not. Debug
		    	  If (Debug) Then
		      	    Call SMG_All_Print ('Debug is on', '|')
	            	  Else
		      	    Call SMG_All_Print ('Debug is off', '|')
		    	  EndIf
	                Else
		  	  Call SMG_All_Print ('Huh?', '|')
	          	  Action = .false.
	                End If

	              End Do ! (.not. Action)

		    Else

		      S = S + 1

		    End If

		  Else

		    S = S + 1

	          End If

	        End Do ! (S .le. E)

		If (Unavail_Start .ne. 0) Then
		  Call Unavail_Print (Unavail_Start, Unavail_End)
		EndIf

	      Else If (Buf(1:3) .eq. '411') Then
	        Call SMG_All_Print 
     $		  (
     $		   'No such group as ' // Group(G).Name,
     $		   '|'
     $            )
	        Call SMG_All_Print (Buf(1:Lg), '|')
	      Else
	        Call SMG_All_Print (Buf(1:Lg), '|')
	        Stop 'Unexpected GROUP command response '
	      End If
	    End If
	  End If

	  G = Next_G

	End Do ! (G_Continue .and. (G .le. Group_Count)) 

C Done with news groups, find out what user want's to do now

	More_Input = ' '
	Action = .false.
	Do While (.not. Action)

	  If (More_Input .ne. ' ') Then
	    Buf = More_Input
	    Lg = TrimLg (More_Input)
	    More_Input = ' '
	  Else
	    Status = SMG_Prompt
     $        (
     $         Buf,
     $	       'End groups, action (<CR> h p q g): ',
     $         Lg
     $        )
	  EndIf

	  Action = .true.
	  If      (Buf(1:1) .eq. ' ') Then  ! Process group
	    Done = .true.
	  Else If (Buf(1:1) .eq. 'b') Then  ! Backup
	    G = G - 1
	  Else If (Buf(1:1) .eq. 'c') Then  ! Catchup
            Call Cmd_GroupNone
	    Action = .false.
	  Else If (Buf(1:3) .eq. 'd/g') Then ! Dir/Group
	    Call Cmd_GroupList (Buf(1:Lg))
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'd') Then  ! Dir
            Call Cmd_GroupNone
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'f') Then  ! Followup
	    Call Cmd_ArticleNone
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'p') Then  ! Post
	    Call Cmd_ArticlePost (G, Buf(1:Lg))
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'g') Then  ! Group g
	    I = Group_Find (Buf(3:34))
	    If (I .eq. 0) Then
	      Action = .false.
	      Call SMG_All_Print 
     $		 (
     $		  ' No such group as ' // 
     $            Buf (3:2+TrimLg(Buf(3:34))),
     $		  '|'
     $		 )
	    Else
	      BySubscribedChk = .true.
	      G = I
	      A_Continue = .false.
	      Group(G).Subscribed = .true.
	    End If
	  Else If (Buf(1:1) .eq. 'h') Then  ! Help
	    Call Cmd_Help
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'k') Then  ! Kill
	    Call Cmd_ArticleNone
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'm') Then  ! Mark unread
	    Call Cmd_ArticleNone
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'n') Then  ! Next group
	    Done = .true.
	  Else If (Buf(1:1) .eq. 'q') Then  ! Quit
	    Done = .true.
	  Else If (Buf(1:1) .eq. 'r') Then  ! Refresh
	    Call Cmd_ArticleNone
	    Action = .false.
	  Else If (Buf(1:1) .eq. 's') Then  ! Save
	    Call Cmd_ArticleNone
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'u') Then  ! Unsubscribe
            Call Cmd_GroupNone
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'x') Then  ! Refresh rot mode
	    Call Cmd_ArticleNone
	    Action = .false.
	  Else If (Buf(1:1) .eq. 'z') Then  ! Next article 
	    Call Cmd_ArticleNone	    !  same subject
	    Action = .false.
	  Else If (Buf(1:1) .eq. '^') Then  ! First unread article
	    Call Cmd_ArticleNone	    !  same subject
	    Action = .false.
	  Else If (Buf(1:1) .eq. '$') Then  ! Last unread article
%% end part a
Michael Dorl (608) 262-0466
dorl@vms.macc.wisc.edu
dorl@wiscmacc.bitnet