/*
**++
**  FACILITY:
**      NNTP_SERVER
**
**  ABSTRACT:
**      Implementation of NNTP using generic I/O calls. To construct an
**      NNTP server this module must be linked with the appropriate driver
**      module.
**
**      Supported drivers are: DECnet (both single and multi-threaded),
**      CMU-Tek TCP/IP,  WIN TCP/IP, TTY, TCPware, MULTINET, UCX.
**
**      The server can be configured as either a single- or multi-threaded
**      server.
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1988,1989,1990,1991
**
**  MODIFICATION HISTORY:
**      V5.3    17-Jun-1988     GIH
**          Version V5.3 Release
**      V5.4    10-Aug-1988     Mats Sundvall, Uppsala
**          Changed SYS$SCRATCH to NEWS_MANAGER
**      V5.5     7-Oct-1988     GIH
**          Included support for multi-threading of the server. The acutal
**          multi-threaded driver is the responsibility of the driver level.
**          The changes involve placing the main() procedure in the driver
**          support module, and defining the server as a set of callable
**          procedures.
**      V5.7     2-Dec-1988     GIH
**        - Alter NEWNEWS procedure to improve response times
**        - Include support for XHDR command
**        - output_file - change close() call to fclose() call!
**        - Altered the NEWNEWS command to interpret distribution fields
**          as both a newsgroup name filter and a Distribution: header
**          line check.
**      V5.8    26-Feb-1989     GIH
**        - Improve syntax checking
**        - Add support for GMT timezone
**        - Add access file of permissions
**        - Re-format code, and include a number of additional access checks
**          to enforce restricted newsgroup access for NNTP clients
**        - Alter NNTP output files to NEWS_MANAGER:NNTP.BATCH
**        - Add aliases support
**      V5.9C   13-Mar-1990     Leonard J. Peirce
**        - Change definition of NNTP_STRLEN to match Unix implementation
**        - Use NNTP_STRLEN instead of some hard-coded values
**      V6.0-1  19-Nov-1990     William H. Glass
**        - Numerous bug fixes From: glass@vixvax.mgi.com (William H. Glass)
**          1) I've made a big performance improvement to the process of
**          receiving articles via the IHAVE command.  Instead of copying the
**          article to a temporary file as it comes in and then reading that
**          file to append to the batch file, I just save the article in memory
**          and then directly append it to the batch file.  The symbol
**          ART_STRLEN controls the maximum article size that will be held in
**          memory (articles over that size still use a temporary file). I've
**          curently got it set to 8K bytes which handles most articles.  I've
**          gotten a 35% reduction in the amount of time that it takes me to
**          receive a news feed, and my disk drives love the reduced traffic. 
**          The key routine for this new stuff is called append_line.  It
**          handles each line of an article as it comes in and either puts it
**          into the memory array or to disk as required.  I also use this code
**          for the POST command, but since the posting routine couldn't easily
**          handle an article held in memory, I always write the memory array to
**          a temporary file at the start of the posting routine.  Maybe
**          someday it might be worth improving this, but there are so few
**          postings in relationship to the number of IHAVE's.
**
**          2) POSTed articles with CONTROL headers are now spooled to a batch
**          file for later processing.  Spooling is handled in the new routine
**          spool_add_item.
**
**          3) I changed some static arrays that were used in only one routine
**          to be automatic (i.e., on the stack) in those routines.
**
**          4) I fixed the bug I quizzed you about in get_post_defaults in
**          processing the "default" entry in the NEWS_POST.DEFAULTS file.
**
**          5) Fixed a bug in auth_list so that it would correctly check the
**          node name.
**
**          6) The server now uses the logicals NEWS_NODE, NEWS_ADDRESS,
**          NEWS_PATHNAME, etc. in the same manner that the NEWS program itself
**          does.  This should give consistency in how Path headers are
**          generated and checked.
**
**          7) The server now includes its name in the Path header (e.g.,
**          server!client!user) on articles that it posts.  It does not modify
**          the Path of articles that are mailed to a moderator or spooled for
**          later processing.
**
**          8) Fixed a bug where the do_new_item routine could clobber an error
**          message (in no_new_item) in an article with cross references.
**
**          9) The server can output long header lines by splitting them up
**          just like the NEWS program does.
**
**          10) Some logicals (e.g., NEWS_ADDRESS and NEWS_TIMEZONE) are only
**          translated once instead of on each article received.
**
**          11) The program will correctly detect errors in opening a temporary
**          file and return an error code at the end of the IHAVE or POST
**          stating the problem.
**
**          12) The program will correctly handle VERY long lines (in excess of
**          the setting of NNTP_STRLEN).  I've tested it on 6000 character
**          lines without any problem.
**
**          13) The code to open the batch file got moved to spool_add_item and
**          was considerably simplified.
**
**          14) The check_dlist routine had a bug that left a leading space on
**          the first distribution name.  This caused the NEWNEWS and NEWGROUPS
**          commands to fail when a distribution list was given.
**
**          15) The problems of lines beginning with periods were fixed in
**          ahbs, the IHAVE, and POST commands.  This stuff correctly detects
**          periods that are at the start of lines and is not fooled by periods
**          in the middle of long lines that require multiple I/O operations.
**
**          16) The was an uninitialized variable in newnews that caused
**          unpredictable results.
**
**          17) I was comparing things against the Unix version of NNTP
**          (version 1.5.10). Recently, the Unix folks decided that the NEWNEWS
**          command should get an error if you don't have read privs.  I
**          changed NNTP_SERVER to also have that restriction.
**
**          18) I changed to log_to_file routine to make two attempts to open
**          the logging file (in case another NNTP_SERVER has it open).  Now if
**          CMUTCP just had multi-threading, I wouldn't need this.
**
**          19) And of course, some minor cleanups in various places.
**
**	V6.03	11-Mar-1991	GIH
**	  - Add support for LIST ACTIVE | NEWSGROUPS | DISTRIBUTIONS command
**	    extension
**	V6.0-4	15-Apr-1991	glass@vivax.mgi.com
**	     1) Added code to calculate data transfer rates (similar to what Earl Ake
**		added to NNTP_XMIT).
**	     2) Changed tests on return value of read_net to assume that non-zero means success.
**	     3) Removed the calls on stat to determine the size of the spool file.
**	     4) Removed several redundant calls on strlen to improve the speed of the
**		ihave and post transfers.
**	     5) Added an increment of the "failed" counter if an article is refused
**		because disk space is exhausted.
**	     6) This one is optional - I "stole" the idea from the Unix version. When
**		a client initially connects to the server and only xfer operations are
**		permitted for that client, then the server will return a 400 error code
**		immediately if disk space is tight.  This is done on the presumption that
**		if you only have xfer privs, then the only useful thing you can do is an
**		ihave which is going to fail because of limited disk space, so why not just give
**		the error immediately.
**	     7) I also included several patches that have already been posted to the
**		newsgroup during the last month (too much trouble to separate the new ones
**		from the old ones - I hope that you don't mind).
**	 There are also some unresolved issues with the NNTP_SERVER:
**	   1) A while back Bob Sloane pointed out a problem with translating some
**	      logical names in EXEC mode.  These names used to be defined in EXEC mode,
**	      but aren't any longer.  I wasn't sure which way you really want to be doing
**	      things, so I haven't done anything about it.
**	   2) A while back I ran into a problem with one of my feed sites.  I ran out
**	      of disk space one day, and NNTP_SERVER started sending a 502 response which
**	      is an "unexpected response" for the Unix version of XMIT.  The administrator
**	      of one of my feeds let me know that I was logging a lot of errors on his end.
**	      Apparently, the Unix version of NNTP will send a 436 failure code if there
**	      isn't enough disk space.  I looked at the RFC's, and a 502 reponse looks
**	      perfectly OK to me.  I sent a query to Stan Barber (the maintainer of the
**	      Unix version) several weeks ago, but I never got a response.  Because I wanted to
**	      keep my feed site happy, I implemented the 400 code stuff described in #6
**	      above.
**	V6.0-5	16-Apr-1991	Bill Fenner (wcf@ecla.psu.edu)
**	     -  Added ALWAYS_SPOOL define constant to allow NNTP to simply SPOOL incoming POST
**	        messages. (Default setting in distribution is set to OFF - GH)
**	V6.0-6	25-May-1991	volz@process.com
**	  - Added support for TCPware transport
**	V6.1	17-Sep-91	BF
**	  - Place newline after each unknown header of posted item - bugfix
**	V6.1b7	 8-Aug-1993	Charles Bailey  bailey@genetics.upenn.edu
**	  - add mem_fail() routine as part of updated memory management
**	V6.1b8	16-Sep-1993     Mark Martinec   mark.martinec@ijs.si
**        - added error checking and condition handling
**	V6.1b8	14-Dec-1993     Mark Pizzolato	mark@infocomm.com
**	  - adopted the strategy that the spool files will be saved in a 
**	    file with a work file name (.INCOMING_BATCH), until the batch is
**	    closed, when it will be renamed to it's true name (.BATCH).
**	    This avoids problems with any conflicts that may arise if code
**	    which trys to add incoming batches to the news environment trys to
**	    handle a file which is still open by the NNTP server.
**        - Made the functionality of the previously compile time code 
**	    ALWAYS_SPOOL and LEAVE_SPOOL_OPEN be determined at run time by the
**	    contents of the logical names NEWS_NNTP_SERVER_ALWAYS_SPOOL and 
**	    NEWS_NNTP_SERVER_LEAVE_SPOOL_OPEN.
**	  - The previous default of 0 for LEAVE_SPOOL_OPEN is particularly 
**	    ineffecient, in that the I/O to actually perform the appropriate 
**	    opens/closes/renames will far exceed the actual I/O for the 
**	    typical news article data.  Given this fact, and the fact that an
**	    external handle has been provided to allow local dynamic control
**	    of this feature, the default has been changed to now keep the file
**	    open.  Making this choice ends up possibly leaving an incoming
**          batch file open for an indefinite period, so an additional
**	    mechanisim has been added to close the batch file anyway if another
**	    article does not arrive within 2 minutes.
**	V6.1b8	20-Dec-1993     Mark Martinec   mark.martinec@ijs.si
**	  - based on a performance optimization patch 930723_nntp_server
**	    by Saul Tannenbaum <saul@hnrc.tufts.edu> I unified the
**	    fopen options into groups, for which macros are now defined:
**	    of the form OPEN_{READ|WRITE|APPEND}_OPT{0|1|2}.
**	    Besides grouping options (under somewhat arbitrary names)
**	    I mostly kept the option values from the patch.
**	    Due to the recent changes in this module, the original patch
**	    is no longer aplicable to this code in its entirety - it might
**	    be worth to use the pre-allocation size calculations from
**	    the original patch.
**	V6.1b9	24-May-1994  Charles Bailey  bailey@genetics.upenn.edu
**	  - added further file optimizations based on patch noted in
**	    previous comment (930723_nntp_server.c!saul@hnrc.tufts.edu).
**	V6.1b9	14-Jun-1994     Saul Tannenbaum  saul@hnrc.tufts.edu
**	  - RMS optimizations to item, group and history file
**      V6.1b9    4-AUG-1994  Wayne Westmoreland  wayne.westmoreland@srs.gov
**        - Changed auth_list to: 1) Not allow posting for just anyone to a
**                                   restricted newsgroup even if it is marked
**                                   as writable.
**                                2) Return with an error if the user does not
**                                   have write privilege to even *one*
**                                   newsgroup being posted to.
**	V6.1b9	24-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
**          (lots of typecasting and minor changes, include newsextern.h
**	    to avoid lots of duplication in prototype declarations, modified
**	    some function declarations to match prototypes in newsextern.h)
**	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  03-Oct-1994     Wayne Westmoreland  (wayne.westmoreland@srs.gov)
**        - Fixed bug in XHDR where continuation lines were not being returned.
**          This caused clients problems when attempting to mark crossposted
**          articles as read or when trying to do reference line threading.
**     V6.1b10 13-Oct-1994  Bob Sloane sloane@kuhub.cc.ukans.edu
**	  - allow multiple device names in NNTP_DISK, separated by
**	    commas. Each device in the list is checked against
**	    NNTP_DISK_DANGER and diskdanger() fails if any disk doesn't
**	    have enough space.
**      V6.1b10 16-Nov-1994     Bob Sloane sloane@kuhub.cc.ukans.edu
**        - Modified the behavior of the POST command to allow remote
**          moderation of newsgroup. Now all that is required is that the
**          article have an Approved: header. This is consistant with the
**          way INN works, and has been requested by several users.
**      V6.1b10 4-Jan-1995     Bob Sloane sloane@kuhub.cc.ukans.edu
**        - added a primitive version of NOV support. Only enabled if XOVER
**          is defined. It is OFF by default.
**	V6.1b10	3-Mar-1995	Mark Martinec   mark.martinec@ijs.si
**	  - replaced strncpy with util_idcpy when forming RMS key 1, as it
**	    turned out that some parts of ANU understood the 60-character
**	    item ID key 1 field in NEWS.ITEMS file as a (59 + null) character
**	    string, while other parts truncated item ID to full 60 characters.
**	    This resulted in nntp clients not being able to access items
**	    with long IDs.  As the 59+null form is more common in the code,
**	    we'll join the flock, even though one character is wasted.
**	V6.1b10 6-Mar-1995	Alan Greig Alan@dct.ac.uk
**	  - Some post bug fixes. Set ihave_size before entering
**	    spool_add_item. Allow close_file() to delete scratchfile.
**	    Correctly free unk_headers[i].
**	V6.1b10 7-Mar-1995	Alan Greig Alan@dct.ac.uk
**	  - Minor correction to previous patch (move fstat into
**	    spool_add_item)
**	V6.1b10	17-Aug-1995	Mark Martinec   mark.martinec@ijs.si
**	  - changed first argument of get_head() to be passed by reference
**	    (char *line --> char **line) and changed the code accordingly.
**	    This fixes an elusive bug when server side XOVER was enabled
**	    and an article was read with header line longer than 1024
**	    characters.  get_head() would reallocate the buffer to get
**	    a larger one, but the calling code in xover() kept using
**	    the old (now free-ed) buffer.  This led to the reuse of the
**	    old (free) memory in the driver routine write_thread() - but not
**	    until several articles later.
**	V6.1b10 6-Aug-1995	Mark Pizzolato mark@infocomm.com
**	  - changed logic that writes out the newsbatch files to not write more
**	    than 32750 bytes per call to fputs.  Some versions of the VAXCRTL
**	    can't handle fputs with large strings correctly.  This logic was
**	    adopted from similar code in NEWSDIST.C.
**	V6.1b10 10-Dec-1996	Mark Pizzolato mark@infocomm.com
**	  - changed the OPEN_{WRITE|APPEND}_OPT{0|1|2} declarations to
**	    explicitly include "shr=get".  This is done since the VAXC rtl
**	    always did this implicitly, but the DEC C RTL seems to default
**	    to "shr=nil" as might actually be expected from reading the
**	    RMS manual.  In any case, the real motivation for this is that
**	    some existing batch procedures depended on the "shr=get" 
**	    behaviour.
**	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/strcat with safer strnncpy/strnncat
**	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.
**	  - move global variables to NEWSVARIABLES.H to assure that common
**	    variables, used by the support routines in modules common
**	    to NEWS and NNTP server, see the same variable declarations.
**	    This also avoids the problem with VAXC which stores 'const int'
**	    in writable psections if they are not defined in the main program,
**	    causing conflicts when linking.
**	  - argparse(): fixed buffer overflow, avoid copying to temp string
**	  - group(): avoid buffer overflow - return '411 no such news group'
**	    if newsgroup name is longer than GRPNAMELEN_F
**	  - sprintf format %.<n>s (where n>512) does not work in VAXC - it
**	    does not write the field. Avoid that.
**	  - fix the year 2000 problem
**	V6.2.0 19-Jun-1997	Alan Greig A.Greig@tay.ac.uk
**	  - XOVER save to disk code. This code allows ANU News
**	    to save overview info to disk by having the server
**	    rather than the NEWSADD code update the information.
**	    The more frequently read a group the more up-to-date
**	    the information. Stale information should never be
**	    returned.
**	    Both XOVER and XOVER_SAVE_TO_DISK must be defined
**	    and then the option can be controlled dynamically
**	    by defining (or undefining) the logical NEWS_DEVICE_OVERVIEW
**	    Normally this would be set as
**	    $ DEFINE/SYS/EXEC NEWS_DEVICE_OVERVIEW NEWS_DEVICE
**	    but could point to a different tree if (say) only
**	    a subset of groups were to be handled this way.
**	    Add the following to your nightly (or weekly) skim
**          $ delete news_device_overview:[*...]*.nntp.* /log/before="-15-"
**	    This just prevents groups which are only read once
**	    in a blue moon from taking up extra disk space
**--
**/

/* #define XOVER 1			/* Basic XOVER support */
#define XOVER_SAVE_TO_DISK 1	/* Save overview info to disk */



#define nntp_version "6.2.0"          /* NEWS and NNTP version identification */

#ifdef vaxc
#module NNTP_SERVER  "V6.2"
#endif

#define _NNTP_SERVER_C
#define module_name "NNTP_SERVER"

                            /* system definitions */
#include "nntpinclude.h"
#include "newsmail.h"

#if NAKED_INCLUDES
#include math
#include stsdef
#include otsdef
#include strdef
#include shrdef
#else
#include <math.h>
#include <stsdef.h>
#include <otsdef.h>
#include <strdef.h>
#include <shrdef.h>
#endif

extern int fsync __ARGS((int));

extern int cache_check __ARGS((char *, int));

static void parser __ARGS((int));
char *util_dir __ARGS((const char *input));
int spool_add_item __ARGS((char *, char *, char *, int, int));
void spool_flush_batch __ARGS((void));

/*
 * Define macros READ_MBF and WRITE_MBF as empty to disable multibuffering,
 * or specify appropriate MBF options
 */
/*
#define READ_MBF
#define WRITE_MBF
*/
#define READ_MBF	,"mbf=2","rop=rah"
#define WRITE_MBF	,"mbf=2","rop=wbh"

#define OPEN_READ_OPT0		"r","mbc=16"
#define OPEN_READ_OPT1		"r","mbc=16"		READ_MBF
#define OPEN_WRITE_OPT1		"w","mbc=16"		WRITE_MBF, "shr=get"
#define OPEN_WRITE_OPT2		"w","mbc=32","deq=256"	WRITE_MBF, "shr=get"
#define OPEN_APPEND_OPT0	"a","mbc=16"                     , "shr=get"
#define OPEN_APPEND_OPT2	"a","mbc=32","deq=256"	WRITE_MBF, "shr=get"

#define CLOSE_LINK      0   /* Status flags passed between server and driver */
#define NO_INPUT        0
#define CMD_INPUT       1
#define FILE_INPUT      2

#define FEED            0
#define POST            1

#define TIME_STRLEN     26
#define NNTP_STRLEN     2048
#define HOST_STRLEN     256
#define FILE_STRLEN     256
#define BUFF_STRLEN     1024
#define	ART_STRLEN	16394

#define M100  0
#define M199  1
#define M200  2
#define M201  3
#define M202  4
#define M205  5
#define M211  6
#define M215  7
#define M220  8
#define M221  9
#define M222  10
#define M223  11
#define M230  12
#define M231  13
#define M235  14
#define M240  15
#define M335  16
#define M340  17
#define M400  18
#define M411  19
#define M412  20
#define M420  21
#define M421  22
#define M422  23
#define M423  24
#define M430  25
#define M435  26
#define M436  27
#define M437  28
#define M440  29
#define M441  30
#define M500  31
#define M501  32
#define M502  33
#define M503  34
#define M280  35
#define M380  36
#define M480  37
#define M281  38
#define M502D 39
#define M282  40
#define M481  41
#define M503F 42
#define EOM   43

static int
    leave_spool_open = 1,
    always_spool = 0; /* set to 1 to spool ALL POST items - other spool only Control items */

static char ibuf[NNTP_STRLEN];	/* network channel input buffer */
static char sbuf[NNTP_STRLEN];	/* network channel output buffer */
static char scratchfile[FILE_STRLEN];
static char artbuf[ART_STRLEN];	/* short article buffer */
char *artptr;

#ifdef XOVER

/* XOVER support for retaining overview array */
char **ovr;
char ovr_groupname[1024];
int index_first, index_last, item, previous;
int ovr_start, ovr_highest, ovr_ostart, ovr_ohighest, ovr_len;
int ovr_modified, ovr_append;
#endif   

                            /* server response table */
static const
char *msg[] =        {"100 help text follows\r\n",
                      "199 debug output\r\n",
                      "200 ANU NEWS/NNTP server %.100s (V%.100s). Ready at %.100s (%.100s).\r\n",
                      "201 ANU NEWS/NNTP server %.100s (V%.100s). Ready at %.100s (%.100s).\r\n",
                      "202 slave status noted\r\n",
                      "205 closing connection - goodbye!\r\n",
                      "211 %d %d %d %.512s\r\n",
                      "215 list of newsgroups follows\r\n",
                      "220 %d %.512s article retrieved - head and body follow\r\n",
                      "221 %d %.512s article retrieved - head follows\r\n",
                      "222 %d %.512s article retrieved - body follows\r\n",
                      "223 %d %.512s article retrieved - request text separately\r\n",
                      "230 list of new articles by message-id follows\r\n",
                      "231 list of new newsgroups follows\r\n",
                      "235 article transferred ok\r\n",
                      "240 article posted ok\r\n",
                      "335 send article to be transferred.  End with <CR-LF>.<CR-LF>\r\n",
                      "340 send article to be posted. End with <CR-LF>.<CR-LF>\r\n",
                      "400 service discontinued - read timeout\r\n",
                      "411 no such news group\r\n",
                      "412 no newsgroup has been selected\r\n",
                      "420 no current article has been selected\r\n",
                      "421 no next article in this group\r\n",
                      "422 no previous article in this group\r\n",
                      "423 no such article number in this group\r\n",
                      "430 no such article found\r\n",
                      "435 article not wanted - do not send it\r\n",
                      "436 transfer failed - try again later\r\n",
                      "437 article rejected - do not try again.\r\n",
                      "440 posting not allowed\r\n",
                      "441 posting failed\r\n",
                      "500 command not recognized.\r\n",
                      "501 command syntax error\r\n",
                      "502 access restriction or permission denied.\r\n",
                      "503 program fault - command not performed\r\n",
                      "280 list of updates to item list follows\r\n",
                      "380 send id's to be compared. End with <CR-LF>.<CR-LF>\r\n",
                      "480 no such news group\r\n",
                      "281 NNTP connection information: %.512s@%.512s.\r\n",
		      "502 permission denied - low server diskspace\r\n",
		      "282 Newsgroup titles follow.\r\n",
		      "481 Requested Fields not defined on server.\r\n",
		      "503 fatal program fault - closing connection\r\n",
                      ".\r\n"};

static const
char *help_cmds[] = {
    "ARTICLE <message-id> Send article referenced by id.\r\n",
    "ARTICLE <nnn>        Send article, number <nnn> from current newsgroup.\r\n",
    "ARTICLE              Send current article.\r\n",
    "BODY <message-id>    Send article body, referenced by id.\r\n",
    "BODY <nnn>           Send article body, number <nnn> from current newsgroup.\r\n",
    "BODY                 Send current article body.\r\n",
    "GROUP <ggg>          Set current group to <ggg>, return status.\r\n",
    "HEAD <message-id>    Send article headers, referenced by id.\r\n",
    "HEAD <nnn>           Send article headers, number <nnn> from current newsgroup.\r\n",
    "HEAD                 Send current article headers.\r\n",
    "HELP                 Send this text.\r\n",
    "IHAVE <message-id>   Notify server of new item and send to server.\r\n",
    "LAST                 Set current item pointer to previous item.\r\n",
    "LIST                 List all newsgroups held on server.\r\n",
    "NEWGROUPS date time [GMT] [<distributions>].\r\n",
    "                     List of newsgroups created since <date time>.\r\n",
    "NEWNEWS newsgroups date time [GMT] [<distributions>].\r\n",
    "                     List of new news items created since <date time>.\r\n",
    "NEXT                 Set current item pointer to next item.\r\n",
    "POST                 Post a news item.\r\n",
    "QUIT                 Close connection.\r\n",
    "SLAVE                Set slave server status.\r\n",
    "STAT <message-id>    Send article status, referenced by id.\r\n",
    "STAT <nnn>           Send article status, number <nnn> from current newsgroup.\r\n",
    "STAT                 Send current article status.\r\n",
    "Version 2 Commands supported:\r\n",
    "DATE                 Return GMT in ISO3307 format\r\n",
    "  The following extensions are supported.\r\n",
    "XCONN                Send connection status.\r\n",
#ifdef XOVER
    "XOVER                Retrieve XOVER information for a range of articles.\r\n",
#endif
    "XHDR header          Retrieve a single header line from a range of articles.\r\n",
    "XUPDGROUP  (Program interface) Compare server and client article holdings.\r\n",
    "XGTITLE [group]      Respond with current newsgroup's title.\r\n",
    "XGNOTICE [group]     Respond with current newsgroup's notice.\r\n",
    ".\r\n",
    ""};

#ifdef __DECC
#pragma member_alignment save
#pragma member_alignment
#endif
static
struct context {
    unsigned int curr_group;    /* server context - current newsgroup number */
    unsigned int curr_item;     /* server context current news item number */
    unsigned int high_item;     /* highest item number in group */
    char cache_id[IDLEN + 4];   /* server context - current item message id */
    int cfilt_date;
    unsigned int cgrp;
    int canread;                /* remote user has read privs */
    int canxfr;                 /* remote user has transfer privs */
    int canpost;                /* remote user has post privs */
    int grpc;                   /* size of grpv arg */
    char **grpv;                /* group permissions vector */
    char rhst[BUFF_STRLEN];
    char rusr[BUFF_STRLEN];
    char curr_grpnam[BUFF_STRLEN];
    int accepted, offered, rejected, failed, posted, arts_read, grps_read;
    } *cxt = 0;
#ifdef __DECC
#pragma member_alignment restore
#endif

int mail_add_expiry = 0,
    batch_size,
    ihave_size = 0;

static unsigned int
    total_bytes_received = 0,
    total_elapsed_time = 0;

static FILE *fpb = 0;

static FILE *fplog = 0;

void sysprv()
{
  return;
}

void nosysprv()
{
  return;
}

/*
 * elapsed_time
 *
 * get the elapsed time in hundredths (0.01) of seconds
 *
 * usage:
 *
 *	elasped_time(1); will perform a 'start-the-clock'
 *
 *	elapsed_time(0); returns the number of centi-seconds since the last
 *		'start-the-clock' command
 */

static int elapsed_time(update)
  int update;
{
  int result;
  static struct timeb rbefore, rafter;

  ftime(&rafter);
  result = 100 * (rafter.time - rbefore.time) +
	((int) rafter.millitm - (int) rbefore.millitm) / 10;
  if (update) rbefore = rafter;
  return(result);
}

int diskdanger(void)
{
  int status;
  char *gotenv,*cp;
  int danger, diskspace;
  unsigned int itmlist[4];
  unsigned short iosb[4], len = 0;

  /* Set up item list for finding out how much disk space we have left */
  itmlist[0] = (DVI$_FREEBLOCKS << 16) + sizeof (int);
  itmlist[1] = (int) &diskspace;
  itmlist[2] = (int) &len;
  itmlist[3] = 0;

  /*
   * If ANY error occurs, assume that disk space is okay
   *
   */

  /* First translate the logical containing the space restriction */
  if (!(gotenv = news_getenv("NNTP_DISK_DANGER",1))) return (0);
  if (sscanf (gotenv, "%d", &danger) != 1) return (0);

  /* Now find out how much space is left on the disk(s) */
  for ( gotenv=news_getenv("NNTP_DISK",1); gotenv && *gotenv; gotenv=cp ) {
      if ( (cp=strchr(gotenv,',')) != 0 )
	  *cp++ = '\0';
      len = diskspace = 0;
      if (!_c$cks(sys$getdviw(0,0,c$dsc(gotenv),&itmlist,&iosb,0,0,0)))
	  return (0);
      if (!_c$cks(iosb[0])) return (0);
      if ( diskspace <= danger ) return (1);
  }
  /* Made it through all disks, Return good status */
  return (0);
}

/*
 *  open_file
 *
 *  open output file with unique scratch name.
 */

static FILE *open_file(stm)
  int stm;
{
    char filename[FILE_STRLEN];

    sprintf(filename,scratchfile,getpid(),stm);
    return(fopen(filename,OPEN_WRITE_OPT1,"alq=128","deq=128"));
}

/*
 *  moderator_address
 *
 *  Use mailpaths file to resolve the mailpath address
 */

char *moderator_address(newsgroup)
  char *newsgroup;
{
  static char mail_rtn[HOST_STRLEN];

  char inpline[HOST_STRLEN], name[HOST_STRLEN], address[HOST_STRLEN], *p;
  int name_l; /* strlen(name) */
  FILE *fpr;

  *mail_rtn = '\0';
  if (!(fpr = fopen(MAILPATHS_FILE,OPEN_READ_OPT0))) return(mail_rtn);
  while (fgets(inpline,sizeof(inpline),fpr)) {
    if (*inpline == '#') continue;
    lower_case(inpline);
    if (sscanf(inpline,"%s %s",name,address) == 2) {
      name_l = strlen(name); news_assert(name_l < sizeof(name));
      if (!strcmp(name,"internet")) continue;
      if (name_l>4 && !strcmp(&name[name_l-4],".all"))
        { name_l -= 4; name[name_l] = '\0'; }
      if (name_l>2 && !strcmp(&name[name_l-2],".*"))
        { name_l -= 2; name[name_l] = '\0'; }
      
      if (strcmp(name,"backbone")) {
        if (!wild_match(newsgroup,name)) {
          if (strcmp(&name[name_l-2],".*")) {
            strnncat(name,sizeof(name),name_l, ".*",2, &name_l);
            if (!wild_match(newsgroup,name)) continue;
            }
	  else continue;
          }
        }
      p = strnncpy(inpline,sizeof(inpline), newsgroup,-1, 0);
      while ( (p = strchr(p,'.')) ) *p = '-';
      sprintf(mail_rtn,address,inpline);
      break;
      }
    }
  if (fclose(fpr)) _ck_close(fpr);
  return(mail_rtn);
}

/*
 *  add_item
 */

#define path_header           headers[PATH]
#define newsgroups_header     headers[NEWSGROUPS]
#define subject_header        headers[SUBJECT]
#define message_id_header     headers[MESSAGE_ID]
#define from_header           headers[FROM]
#define date_header           headers[DATE]
#define reply_to_header       headers[REPLY_TO]
#define sender_header         headers[SENDER]
#define followup_to_header    headers[FOLLOWUP_TO]
#define expires_header        headers[EXPIRES]
#define references_header     headers[REFERENCES]
#define control_header        headers[CONTROL]
#define distribution_header   headers[DISTRIBUTION]
#define organisation_header   headers[ORGANISATION]
#define keywords_header       headers[KEYWORDS]
#define summary_header        headers[SUMMARY]
#define approved_header       headers[APPROVED]
#define supersedes_header     headers[SUPERSEDES]
#define lines_header          headers[LINES]
#define xref_header           headers[XREF]
#define relay_version_header  headers[RELAY_VERSION]

#ifndef MAX_UNK  
#define MAX_UNK  50
#endif

static const
char *header_keyw[NHEADERS] = {
          "path",
          "newsgroups",
          "subject",
          "message-id",
          "from",
          "date",
          "reply-to",
          "sender",
          "followup-to",
          "expires",
          "references",
          "control",
          "distribution",
          "organization",
          "keywords",
          "summary",
          "approved",
          "supersedes",
          "lines",
          "xref",
          "relay-version",
          "x-pgp-sig"},

     *fheaders[NHEADERS] = {
          "Path: ",
          "Newsgroups: ",
          "Subject: ",
          "Message-ID: ",
          "From: ",
          "Date: ",
          "Reply-To: ",
          "Sender: ",
          "Followup-To: ",
          "Expires: ",
          "References: ",
          "Control: ",
          "Distribution: ",
          "Organization: ",
          "Keywords: ",
          "Summary: ",
          "Approved: ",
          "Supersedes: ",
          "Lines: ",
          "Xref: ",
          "Relay-Version: ",
          "X-PGP-Sig: "};

static
char *headers[NHEADERS],
     *unk_headers[MAX_UNK];

static
int unk_index = 0;

unsigned int cre_grp[512];
char no_new_item[512];

static char batchname[FILE_STRLEN] = "";
static char batchname_tmp[FILE_STRLEN] = "";

/*
 *  add_to_headers
 */

static int add_to_headers(s)
  char *s;
{
  char head_word[132],
       *p,
       *h = head_word;
  int i;

  for (p=s;  *p != ':' && isprint(*p);  p++) {}
  if (*p != ':') return(0);
  if (!*(p+1)) return(1);

/*
  if (!isspace(*(p+1))) return(0);	this call omitted to allow brain-damaged headers to pass
					through without NEWS barfing
*/

  *p = '\0';
  strnncpy(h,sizeof(head_word), s,-1, 0);
  *p++ = ':';
  if (!*h) return(0);

  while (isspace(*p)) p++;
  if (!*p) return(1);

  lower_case(h);
  for (i=0; i<NHEADERS; i++) {
    if (!strcmp(h,header_keyw[i])) {
      if (headers[i]) { news_free(headers[i]); headers[i] = NULL; }

	/* force message_id lines to use the "<idstring>" format  - GH 17/4/91 */
      if (i == MESSAGE_ID) {
        char *lb, *rb;

        if ((lb = strchr(p,'<')) && (rb = strrchr(lb,'>'))) {
          *++rb = '\0';
          p = lb;
          }
        else break;
        }
      headers[i] = news_strdup(p,-1);
      break;
      }
    }
  if ((i == NHEADERS) && (unk_index < MAX_UNK))
    unk_headers[unk_index++] = news_strdup(s,-1);
  return(1);
}

/*
 *  call_mail
 */

static char *from_address(address)
  char *address;
{
  static char my_address[IO_SIZE];
  char *cp;

  if (!address) return(0);
  if ( (cp = strchr(address,'<')) ) {
    strnncpy(my_address,sizeof(my_address), ++cp,-1, 0);
    if ( (cp = strchr(my_address,'>')) ) *cp = '\0';
    }
  else {
    for (cp=address; isspace(*cp); cp++) {}
    strnncpy(my_address,sizeof(my_address), cp,-1, 0);
    for (cp=my_address; *cp && !isspace(*cp);  cp++) {}
    *cp = '\0';
    if ( (cp = strchr(my_address,'(')) ) *cp = '\0';
    }
  return(my_address);
}

int call_mail(file,subject,address,from)
  const char *file, *subject, *address, *from;
{
  int context = 0,
      sts,
      i = 0;

  struct cmlst {
      short buffer_length;
      short item_code;
      const char *buffer_address;
      int *return_length_address;
    } null_list[] = {{0,0,0,0}},
      att_list[4];

  i = 0;
  att_list[i].buffer_length = 0;
  att_list[i].item_code = 0;
  att_list[i].buffer_address = (char *) 0;
  att_list[i].return_length_address = 0;
  sysprv();
  if (!((sts = mail$send_begin(&context,null_list,att_list)) & 1)) {
    nosysprv();
    return(sts);
    }
  i = 0;
  att_list[i].buffer_length = strlen(address);
  att_list[i].item_code = MAIL$_SEND_TO_LINE;
  att_list[i].buffer_address = address;
  att_list[i].return_length_address = 0;
  ++i;

  if (from && *from) {
    att_list[i].buffer_length = strlen(from);
    att_list[i].item_code = MAIL$_SEND_FROM_LINE;
    att_list[i].buffer_address = from;
    att_list[i].return_length_address = 0;
    ++i;
    }
  if (subject && *subject) {
    att_list[i].buffer_length = strlen(subject);
    att_list[i].item_code = MAIL$_SEND_SUBJECT;
    att_list[i].buffer_address = subject;
    att_list[i].return_length_address = 0;
    ++i;
    }
  att_list[i].buffer_length = 0;
  att_list[i].item_code = 0;
  att_list[i].buffer_address = (char *) 0;
  att_list[i].return_length_address = 0;

  if (!((sts = mail$send_add_attribute(&context,att_list,null_list)) & 1)) {
    mail$send_end(&context,null_list,null_list);
    nosysprv();
    return(sts);
    }
  i = 0;
  att_list[i].buffer_length = strlen(file);
  att_list[i].item_code = MAIL$_SEND_FILENAME;
  att_list[i].buffer_address = file;
  att_list[i].return_length_address = 0;
  ++i;
  att_list[i].buffer_length = 0;
  att_list[i].item_code = 0;
  att_list[i].buffer_address = (char *) 0;
  att_list[i].return_length_address = 0;

  if (!((sts = mail$send_add_bodypart(&context,att_list,null_list)) & 1)) {
    mail$send_end(&context,null_list,null_list);
    nosysprv();
    return(sts);
    }
  
  i = 0;
  att_list[i].buffer_length = strlen(address);
  att_list[i].item_code = MAIL$_SEND_USERNAME;
  att_list[i].buffer_address = address;
  att_list[i].return_length_address = 0;
  ++i;
  att_list[i].buffer_length = 0;
  att_list[i].item_code = 0;
  att_list[i].buffer_address = (char *) 0;
  att_list[i].return_length_address = 0;
  if (!((sts = mail$send_add_address(&context,att_list,null_list)) & 1)) {
    mail$send_end(&context,null_list,null_list);
    nosysprv();
    return(sts);
    }

  i = 0;
  att_list[i].buffer_length = 0;
  att_list[i].item_code = 0;
  att_list[i].buffer_address = 0;
  att_list[i].return_length_address = 0;
  if (!((sts = mail$send_message(&context,att_list,null_list)) & 1)) {
    mail$send_end(&context,null_list,null_list);
    nosysprv();
    return(sts);
    }
  mail$send_end(&context,null_list,null_list);
  nosysprv();
  return(1);
}

/*
 *  post_cc
 */

void post_cc(newsgroups,subject,from,filename,ccfile)
  char *newsgroups,
       *subject,
       *from,
       *filename,
       *ccfile;
{
  FILE *fpr;
  char xfrbuf[IO_SIZE],
       newsg[IO_SIZE],
       addr[IO_SIZE],
       *cp1,
       *cp2;

  if ( (fpr = fopen(ccfile,OPEN_READ_OPT0)) ) {
    while (fgets(xfrbuf,sizeof(xfrbuf),fpr)) {
      if ( (cp1 = strchr(xfrbuf,'#')) ) *cp1 = '\0';
      if ( (cp1 = strchr(xfrbuf,'\n')) ) *cp1 = '\0';
      if (sscanf(xfrbuf,"%s %s",newsg,addr) != 2) continue;
      for (cp1=newsgroups; cp1; cp1=cp2) {
        if ( (cp2 = strchr(cp1,',')) ) *cp2 = '\0';
        if (wild_match(cp1,newsg))
          call_mail(filename,subject_header,add_transform(addr),from_address(from));
        if (cp2) *cp2++ = ',';
        }
      }
    if (fclose(fpr)) _ck_close(fpr);
    }
}

void add_keys(arg,val,val_m)
    char *arg, *val;
    int val_m;  /* sizeof(val) */
{
  char *cp1, *cp2, cmpval[132];

  strnncpy(cmpval,sizeof(cmpval), "*,",2, 0);
  for (cp1=arg; cp1; cp1=cp2) {
    if ((cp2 = strchr(cp1,',')) != 0) *cp2++ = '\0';
    if (!*cp1) continue;
    strnncpy(&cmpval[2],sizeof(cmpval)-2, cp1,-1, 0);
    strnncat(cmpval,sizeof(cmpval),-1, ",*",2, 0);
    if (!wild_match(val,cmpval)) {
      strnncat(val,val_m,-1, cp1,-1, 0);
      strnncat(val,val_m,-1, ",",1,  0);
      }
    }
}

void get_post_defaults_byref(groups,cur_dist,cur_follow)
    char **groups, **cur_dist, **cur_follow;
{
    char in_line[NNTP_STRLEN], hkey[256], arg[256];
    char *buf, *locgrp, *locdst, *locfol,
         *cp, *cp1, *cp2;
    int locgrp_m, locdst_m, locfol_m;  /* sizeof */
    int locgrp_l, locdst_l, locfol_l;  /* strlen */
    int skp = 0, dd = 1, df = 1;
    FILE *fpr;

    locgrp_m = (*groups ? strlen(*groups) : 0) + 512;
    locgrp = (char *) news_malloc(locgrp_m); locgrp_l = 0;
    locdst_m = (*cur_dist ? strlen(*cur_dist) : 0) + 512;
    locdst = (char *) news_malloc(locdst_m); locdst_l = 0;
    locfol_m = (*cur_follow ? strlen(*cur_follow) : 0) + 512;
    locfol = (char *) news_malloc(locfol_m); locfol_l = 0;
    strnncpy(locgrp,locgrp_m, ",",1,           &locgrp_l);
    strnncpy(locdst,locdst_m, locgrp,locgrp_l, &locdst_l);
    strnncpy(locfol,locfol_m, locgrp,locgrp_l, &locfol_l);
    if (*cur_dist) {
      strnncat(locdst,locdst_m,locdst_l, *cur_dist,-1, &locdst_l);
      if (**cur_dist) {
        strnncat(locdst,locdst_m,locdst_l, locgrp,locgrp_l, &locdst_l);
        dd = 0;
        }
      }
    if (*cur_follow) {
      strnncat(locfol,locfol_m,locfol_l, *cur_follow,-1, &locfol_l);
      if (**cur_follow) {
        strnncat(locfol,locfol_m,locfol_l, locgrp,locgrp_l, &locfol_l);
        df = 0;
        }
      }
    strnncat(locgrp,locgrp_m,locgrp_l, *groups,-1, &locgrp_l);
    strnncat(locgrp,locgrp_m,locgrp_l, ",",1,      &locgrp_l);

    if (!(fpr = fopen("NEWS_MANAGER:NEWS_POST.DEFAULTS",OPEN_READ_OPT0))) {
      news_free(locgrp); locgrp = NULL; locgrp_m = locgrp_l = 0;
      news_free(locdst); locdst = NULL; locdst_m = locdst_l = 0;
      news_free(locfol); locfol = NULL; locfol_m = locfol_l = 0;
      return;
      }
    strnncpy(in_line,sizeof(in_line), "*,",2, 0);
    buf = &in_line[2];
    while (skp || fgets(buf,sizeof(in_line)-4,fpr)) {
        skp = 0;
        if (*buf == '#') continue;
        if ( (cp = strchr(buf,'\n')) ) *cp = '\0';
        if (!*buf || isspace(*buf)) continue;
        lower_case(buf);
        if (!strcmp(buf,"default") ||
            (strnncat(buf,sizeof(in_line)-2,-1, ",*",2, 0),
             wild_match(locgrp,in_line))) {

                /* find out which newsgroups caused the successful match
                   and remove them from locgrp */

            char newloc[512],
                 defdist[256],
                 deffol[256];

            *defdist = *deffol = '\0';
            if (!strcmp(buf,"default"))
              strnncpy(buf,sizeof(in_line)-2, "*",1, 0);
            else buf[strlen(buf) - 2] = '\0';
            strcpy(newloc,",");
            locgrp[--locgrp_l] = '\0';
            for (cp = &locgrp[1]; cp; cp=cp1) {
                if ( (cp1 = strchr(cp,',')) ) *cp1++ = '\0';
                if (!wild_match(cp,buf)) {
                    strnncat(newloc,sizeof(newloc),-1, cp,-1, 0);
                    strnncat(newloc,sizeof(newloc),-1, ",",1, 0);
                    }
                else {
                    if (*deffol) strnncat(deffol,sizeof(deffol),-1, ",",1, 0);
                    strnncat(deffol,sizeof(deffol),-1, cp,-1, 0);
                    if ( (cp2 = strchr(cp,'.')) ) *cp2 = '\0';
                    if (*defdist) strnncat(defdist,sizeof(defdist),-1, ",",1, 0);
                    strnncat(defdist,sizeof(defdist),-1, cp,-1, 0);
                    }
                }
            if (strcmp(newloc,",")) strnncpy(locgrp,locgrp_m, newloc,-1, 0);
            else { *locgrp = '\0'; locgrp_l = 0; }

                /* now read in the default header values */

            skp = 1;
            while (fgets(buf,sizeof(in_line)-2,fpr)) {
                if (*buf == '#') continue;
                if ( (cp = strchr(buf,'\n')) ) *cp = '\0';
                if (!isspace(*buf)) break;
                lower_case(buf);
                if (sscanf(buf," %s %s",hkey,arg) == 2) {
                    if (!strcmp(hkey,"distribution:")) {
                        add_keys(arg,locdst,locdst_m);
                        *defdist = '\0';
                        dd = 0;
                        }
                    else if (!strcmp(hkey,"followup-to:")) {
                        add_keys(arg,locfol,locfol_m);
                        *deffol = '\0';
                        df = 0;
                        }
                    }
                }
            if (*defdist) add_keys(defdist,locdst,locdst_m);
            if (*deffol)  add_keys(deffol,locfol,locfol_m);
            if (!*locgrp) break;
            }
        }
    if (fclose(fpr)) _ck_close(fpr);
    news_free(locgrp); locgrp = NULL; locgrp_m = locgrp_l = 0;
  if (!dd && (!*cur_dist || (*cur_dist && !**cur_dist)) ) {
    if (*cur_dist) news_free(*cur_dist);
    *cur_dist = news_strdup(&locdst[1],max(0,locdst_l-1));
    if ((*cur_dist)[skp = (strlen(*cur_dist)-1)] == ',') (*cur_dist)[skp] = '\0';
    }
  news_free(locdst); locdst = NULL; locdst_m = locdst_l = 0;

  if (!df && (!*cur_follow || (*cur_follow && !**cur_follow)) ) {
    if (*cur_follow) news_free(*cur_follow);
    *cur_follow = news_strdup(&locfol[1],max(0,locfol_l-1));
    if ((*cur_follow)[skp = (strlen(*cur_follow)-1)] == ',') (*cur_follow)[skp] = '\0';
    }
  news_free(locfol); locfol = NULL; locfol_m = locfol_l = 0;
  return;
}

int getgroupbyname(gp)
  const char *gp;
{
  int status;
  char grp_name[GRPNAMELEN_F];

  strncpy(grp_name,gp,GRPNAMELEN_F);
  grprab.rab$l_kbf = grp_name;
  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;
  return(sys_get_nornf(&grprab));
}

/*
 *  auth_list_1
 *
 *  Check a newsgroup list, and edit out unauthorized entries
 */

int auth_list_1(gl,node,usr,cg)
  char *gl, *node, *usr;
  unsigned int *cg;
/*  renamed auth_list to auth_list_1 to avoid conflict in declaration
    with newsextern.h .              22-Aug-1994 mark.martinec@ijs.si  */
{
  char s[GRPNAMELEN],
       acline[256],
       cknode[132],
       *cp,
       *ap,
       *op,
       *cp1,
       *cp2,
       *retstr;
  int retstr_m;  /* sizeof(retstr) */
  int nowrite_acc = 1;
  FILE *fpd;

  retstr_m = strlen(gl)+1; retstr = news_strdup(gl, retstr_m-1);
  *retstr = '\0';
  sprintf(cknode,"@%.129s ",node);
  for (cp1=gl; cp1; cp1=cp2) {
    if ( (cp2 = strchr(cp1,',')) ) *cp2 = '\0';
    util_cvrt(s,cp1);
    if (getgroupbyname(s)) {
      sprintf(itm_fname,Access_template,util_dir(newsgrp.grp_name));
      if ( (fpd = fopen(itm_fname,OPEN_READ_OPT0)) ) {
        nowrite_acc = 1;
        while (fgets(acline,sizeof(acline),fpd)) {
          if (*acline == '#') continue;
          if ( (cp = strchr(acline,'\n')) ) *cp = ' ';
          lower_case(acline);
          if ( (op = strchr(acline,':')) ) *op = '\0';
          if ( (cp = strchr(acline,' ')) ) *cp = '\0';
          if ( (ap = strchr(acline,'@')) ) *ap = '\0';
          if (!strcmp(acline,usr) || wild_match(usr,acline)) {
            if (cp) *cp = ' ';
            if (ap) *ap = '@';
            if (strchr(acline,'@')) {
              if (!substrcmp(acline,cknode)) continue;
              }
            if (substrcmp(cp," write ")) nowrite_acc = 0;
            if (substrcmp(cp," nowrite ")) nowrite_acc = 1;
            }
          }
        if (fclose(fpd)) _ck_close(fpd);
        }
      else nowrite_acc = (newsgrp.grp_flags & NEWS_M_NOWRITE_SET);
      if (!nowrite_acc) {
        *cg++ = newsgrp.grp_num;
        if (*retstr) strnncat(retstr,retstr_m,-1, ",",1, 0);
        strnncat(retstr,retstr_m,-1, s,-1, 0);
        }
      else return (0);
      }
    if (cp2) *cp2++ = ',';
    }
/*  strcpy(gl,retstr); */
  news_free(retstr); retstr = NULL;
  *cg = 0;
  return(strlen(gl));
}

/*
 *  do_new_item
 *
 *  Create new item in newsgroup
 */
unsigned int do_new_item(g,id,subj,fromstr,fnam,new_flag,
                         skip_history,linecount,bypass_signalling_errors)
    unsigned int *g;
    const char *id, *subj, *fromstr, *fnam;
    int new_flag, skip_history, linecount, bypass_signalling_errors;
/* added parameters new_flag, skip_history and bypass_signalling_errors
   to make declaration compatible with newsextern.h .
                                       22-Aug-1994 mark.martinec@ijs.si */
{
  int status;
  ITM savitm;
  struct stat sbuffer;
  unsigned int cre_grps[100], cre_itm[100];
  time_t cur_time;
  int i, g_count = 0, failure = 0;
  FILE *ofile = 0, *ifile = 0;
  unsigned int *gptr;
  char xrefline[250], ibuff[IO_SIZE], id_key[IDLEN + 4];
  int xrefline_l; /* strlen(xrefline) */

  if (!fnam || !*fnam) {
    strcpy(no_new_item,"No input filename specified");
    return(1);
    }
  if (stat(fnam,&sbuffer)) {
    strcpy(no_new_item,"Cannot access input file");
    return(1);
    }
  if (!sbuffer.st_size) {
    strcpy(no_new_item,"Input file is empty");
    return(1);
    }
  if (!(ifile = fopen(fnam,OPEN_READ_OPT1))) {
    strcpy(no_new_item,"Cannot open input file (read access)");
    return(1);
    }
  util_idcpy(id_key,id);

  strnncpy(xrefline,sizeof(xrefline), "Xref: ",-1, &xrefline_l);
  strnncat(xrefline,sizeof(xrefline),xrefline_l, news_node,-1, &xrefline_l);
  time(&cur_time);
  newsitm.itm_recvdate = cur_time;
  newsitm.itm_flags = (new_flag ? NEWS_M_NEW : 0) | NEWS_M_UNREAD | NEWS_M_LINESVALID;
  newsitm.itm_lines = linecount;
  newsitm.itm_life = mail_add_expiry;
  newsitm.itm_cachedate = cur_time;
  util_subjcpy(newsitm.itm_title,subj);
  util_idcpy(newsitm.itm_id,id);
  util_fromcpy(newsitm.itm_from,fromstr);

  itmrab.rab$b_krf = 1;
  itmrab.rab$l_kbf = id_key;
  itmrab.rab$b_ksz = IDLEN + 4;
  itmrab.rab$l_rop = RAB$M_WAT;
  itmrab.rab$b_rac = RAB$C_KEY;
  itmrab.rab$w_rsz = sizeof newsitm;

  grprab.rab$b_ksz = 4;
  grprab.rab$b_krf = 1;
  grprab.rab$l_rop = RAB$M_WAT;
  grprab.rab$b_rac = RAB$C_KEY;

  for (; *g ; ++g) {
    grprab.rab$l_kbf = (char *) g;
    if (!sys_get_nornf(&grprab)) {
      failure = 1;
      strcpy(no_new_item,"Newsgroup not located");
      continue;
      }
    gptr = (unsigned int *) &id_key[IDLEN];
    *gptr = newsitm.itm_grp = newsgrp.grp_num;
    savitm = newsitm;
    if (sys_get_nornf(&itmrab)) {
      failure = 1;
      strcpy(no_new_item,"Cannot add to Newsitem index file");
      newsitm = savitm;
      continue;
      }
    newsitm = savitm;
    ++newsgrp.grp_count;
    newsgrp.grp_entdate = cur_time;
    newsitm.itm_num = ++newsgrp.grp_topnum;
    newsitm.itm_cid = newsitm.itm_num;
    itmrab.rab$l_rbf = (char *) &newsitm;
    itmrab.rab$w_rsz = sizeof newsitm;
    if (!sys_put(&itmrab)) {
      failure = 1;
      strcpy(no_new_item,"Cannot add to Newsitem index file");
      continue;
      }
    if (!sys_update(&grprab)) {
      itmrab.rab$l_kbf = (char *) &(newsitm.itm_num);
      itmrab.rab$b_ksz = 8;
      itmrab.rab$b_krf = 0;
      itmrab.rab$l_rop = RAB$M_WAT;
      itmrab.rab$b_rac = RAB$C_KEY;
      if (sys_find_nornf(&itmrab)) sys_delete(&itmrab);
      failure = 1;
      strcpy(no_new_item,"Cannot update Newsgroup index file");
      continue;
      }
    news_assert(g_count < 100);
    cre_itm[g_count] = newsitm.itm_num;
    cre_grps[g_count++] = newsgrp.grp_num;
    sprintf(ibuff," %.*s:%d",
            GRPNAMELEN_F,newsgrp.grp_name, newsitm.itm_num);
    strnncat(xrefline,sizeof(xrefline),xrefline_l, ibuff,-1, &xrefline_l);
    }

  *itm_fname = '\0';
  if (!g_count) {
    if (ifile) { if (fclose(ifile)) _ck_close(ifile); }
    return((!failure) ? (strcpy(no_new_item,"No Valid Newsgroups"),failure = 1) : failure);
    }
  strnncat(xrefline,sizeof(xrefline),xrefline_l, "\n",1, &xrefline_l);
  grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
  for (i = 0; i < g_count; ++i) {
    grprab.rab$l_kbf = (char *) &(cre_grps[i]);
    if (!sys_get_nornf(&grprab)) {
      strcpy(no_new_item,"Cannot access newsgroup record");
      failure = 1;
      continue;
      }
    if (!*itm_fname) {
      int header = 1;

      /* 7/21/93 saul@hnrc.tufts.edu - calculate approximate output size
       * based upon the size in bytes of the input file and pre-extend
       * the file */

      char alloc_size[32];
      sprintf (alloc_size, "alq=%d", (int)((sbuffer.st_size+511)/512));


      sprintf(itm_fname,Itm_template,util_dir(newsgrp.grp_name),cre_itm[i]);
      if (!(ofile = fopen(itm_fname,OPEN_WRITE_OPT1,alloc_size,
                                    "deq=16","fop=cbt,tef"))) {
        strcpy(no_new_item,"Cannot open output file (write access)");
        failure = 1;
        *itm_fname = '\0';
        continue;
        }
      while (fgets(ibuff,sizeof(ibuff),ifile)) {
        if (header && (*ibuff == '\n')) {
          if (g_count > 1) fputs(xrefline,ofile);
          fputs(ibuff,ofile);
          header = 0;
          }
        else if (header && (!strncmp(ibuff,"Xref:",5))) {
          if (g_count > 1) fputs(xrefline,ofile);
          header = 0;
          }
        else fputs(ibuff,ofile);
        }
      if (fclose(ifile)) _ck_close(ifile);
      ifile = 0;
      if (fclose(ofile)) _ck_close(ofile);
      if (newsgrp.grp_flags & NEWS_M_RESTRICT_SET) chmod(itm_fname,0700);
      else chmod(itm_fname,0744);
      failure = 0;
      }
    else {
      char fline[FILE_STRLEN];

      sprintf(fline,Itm_template,util_dir(newsgrp.grp_name),cre_itm[i]);
      file_copy(itm_fname,fline,0);
      if (newsgrp.grp_flags & NEWS_M_RESTRICT_SET) chmod(fline,0700);
      else chmod(fline,0744);
      failure = 0;
      }
    }
  if (ifile) { if (fclose(ifile)) _ck_close(ifile); }
  return(failure);
}

/*
 *  time_str - generate string based on current time
 */

static
char *time_str(void)
{
  static char timestr[14];

  time_t ctime;
  struct tm *stm;

  time(&ctime);
  stm = localtime(&ctime);
  sprintf(timestr,"%02d%02d%02d%02d%02d%02d",
          stm->tm_year % 100, (stm->tm_mon) + 1, stm->tm_mday,
          stm->tm_hour, stm->tm_min, stm->tm_sec);
  return(timestr);
}

/*
 *  fout_line
 *
 *  Output a header line
 */

static void
fout_line(s,f)
  FILE *f;
  char *s;
{
  char *c, *cp, save_c;

  while (strlen(s) > 132) {
    save_c = s[78];
    s[78] = '\0';
    cp = &s[30];
    if (   (c = strrchr(cp,' '))
        || (c = strrchr(cp,'!'))
        || (c = strrchr(cp,','))
        || (c = strrchr(cp,';')))
      ++c;
    else c = &s[78];
    s[78] = save_c;
    save_c = *c;
    *c = '\0';
    fputs(s,f);
    fputs("\n ",f);
    *c = save_c;
    s = c;
    }
  fputs(s,f);
}

int post_add_item(filename,buf,msg,stm)
  char *filename, *buf, *msg;
  int stm;
{
  char xfrbuf[IO_SIZE],
       scrfile[FILE_SIZE],
       *curr_line = NULL;
  int i, scan_header, line_count,
      curr_line_m = 0, /* sizeof(curr_line) */
      curr_line_l = 0, /* strlen(curr_line) */
      xfrbuf_l;        /* strlen(xfrbuf) */
  FILE *fpr,
       *fpw;
  char mod_addr[IO_SIZE],
       post_addr[IO_SIZE],
       lg[GRPNAMELEN],
       *cp1, *cp2, *xx, *p;
  int  approval_required = 0,
       posted = 0;
  time_t cur_time;
  struct tm *stme;
  struct stat stat_buffer;

  time(&cur_time);
  p = ctime(&cur_time);
  p += 4;
  stme = localtime(&cur_time);

  sprintf(scrfile,scratchfile,getpid(),stm);
  for (i = 0; i < NHEADERS; ++i) if (headers[i]) {
    news_free(headers[i]);
    headers[i] = 0;
    }
  for (i = 0; i < MAX_UNK; ++i) if (unk_headers[i]) {
    news_free(unk_headers[i]);
    unk_headers[i] = 0;		/* ARG - Mark as freed */
    }
  unk_index = 0;

  if (!*filename) {			/* dump the buffer to a file */
    if (!(fpw = fopen(scrfile,OPEN_WRITE_OPT1,"alq=16","deq=16"))) {
      sprintf(msg,"Scratch file %.255s: %.512s",scrfile,strerror(errno,vaxc$errno));
      return(0);
      }
    strcpy(filename, scrfile);		/* ARG - allow caller to delete */
    fputs(buf,fpw);
    if (fclose(fpw)) _ck_close(fpw);
    }

  *msg = '\0';
  if (!(fpr = fopen(filename,OPEN_READ_OPT1))) {
    sprintf(msg,"Post file %.255s: %.512s",scrfile,strerror(errno,vaxc$errno));
    return(0);
    }
  scan_header = 1; line_count = 0;
  while (fgets(xfrbuf,sizeof(xfrbuf),fpr)) {
    if (!scan_header) ++line_count;
    else if (*xfrbuf == '\n') scan_header = 0;
    while (!strchr(xfrbuf,'\n') && fgets(xfrbuf,sizeof(xfrbuf),fpr));
    }
  fseek(fpr,0,0);
  /* save the size for pre-extending the output file
   * saul@hnrc.tufts.edu 7/22/93 */
    fstat (fileno(fpr), &stat_buffer);

  while (fgets(xfrbuf,sizeof(xfrbuf),fpr)) {
    xfrbuf_l = strlen(xfrbuf);
    if (*xfrbuf == '\n') break;
    if (xfrbuf[xfrbuf_l-1]=='\n') xfrbuf[--xfrbuf_l]='\0';  /* remove newline */
    if ((*xfrbuf == ' ') || (*xfrbuf == '\t')) {
      if (curr_line) {
        curr_line_m = curr_line_l + (xfrbuf_l-1) + 1;
        curr_line = (char *) news_realloc(curr_line, curr_line_m);
        strnncat(curr_line,curr_line_m,curr_line_l,
                 xfrbuf+1,xfrbuf_l-1, &curr_line_l);
        }
      else {
        if (fclose(fpr)) _ck_close(fpr);
        strcpy(msg,"Invalid header format.");
        return(0);
        }
      }
    else {
      if (curr_line) {
        if (!add_to_headers(curr_line)) {
          if (fclose(fpr)) _ck_close(fpr);
          news_free(curr_line); curr_line = NULL; curr_line_m = curr_line_l = 0;
          strcpy(msg,"Invalid header format.");
          return(0);                           
          }
        news_free(curr_line); curr_line = NULL; curr_line_m = curr_line_l = 0;
        }
      curr_line = news_strdup(xfrbuf,xfrbuf_l);
      curr_line_l = xfrbuf_l; curr_line_m = curr_line_l+1;
      }
    }
  if (curr_line) {
    if (!add_to_headers(curr_line)) {
      if (fclose(fpr)) _ck_close(fpr);
      news_free(curr_line); curr_line = NULL; curr_line_m = curr_line_l = 0;
      strcpy(msg,"Invalid header format.");
      return(0);
      }
    news_free(curr_line); curr_line = NULL; curr_line_m = curr_line_l = 0;
    }

  if (!date_header) {
    sprintf(xfrbuf,"Date: %d %.3s %04d %02d:%02d:%02d %.200s",
          stme->tm_mday, p, stme->tm_year + 1900,
          stme->tm_hour, stme->tm_min, stme->tm_sec, news_timezone);
    add_to_headers(xfrbuf);
    }
  if (!message_id_header) {
    sprintf(xfrbuf,"Message-ID: %.200s",gen_id());
    add_to_headers(xfrbuf);
    }
  if (!path_header) {
    strcpy(xfrbuf,"Path: ");
 /* strnncat(xfrbuf,sizeof(xfrbuf),-1, news_pathname,-1, 0); */
 /* strnncat(xfrbuf,sizeof(xfrbuf),-1, "!",1, 0); */
    strnncat(xfrbuf,sizeof(xfrbuf),-1, "nntp",-1, 0);
    add_to_headers(xfrbuf);
    }
  if (!from_header) {
    strcpy(xfrbuf,"From: news@");
    strnncat(xfrbuf,sizeof(xfrbuf),-1, Node_address,-1, 0);
    add_to_headers(xfrbuf);
    }
  if (!subject_header) {
    strcpy(xfrbuf,"Subject: <none>");
    add_to_headers(xfrbuf);
    }
  if (!newsgroups_header)
    strcpy(msg,"Invalid headers - Newsgroups: header missing.");
  if (*msg) {
    if (fclose(fpr)) _ck_close(fpr);
    return(0);
    }

  xx = aliases(newsgroups_header,-1);
  news_free(newsgroups_header);
  newsgroups_header = news_strdup(xx,-1);
  get_post_defaults_byref(&newsgroups_header,&distribution_header,&followup_to_header);

  mail_add_expiry = parse_expiry_date(expires_header);
  {
    /* open the file and prextend it to the size of existing file 
     * saul@hnrc.tufts.edu 7/22/93 */
    char alloc_size[32];
    sprintf(alloc_size, "alq=%d", (int)((stat_buffer.st_size+511)/512));
    fpw = fopen(scrfile,OPEN_WRITE_OPT2,alloc_size,"fop=cbt,tef");
    _ck_open_w(fpw,scrfile);
    }
#ifdef __ALPHA
  fprintf(fpw,"Relay-Version: %s %s OpenVMS AXP; site %s\n",
              NEWS_VERSION,NEWS_DDATE,news_node);
#else
  fprintf(fpw,"Relay-Version: %s %s OpenVMS VAX; site %s\n",
              NEWS_VERSION,NEWS_DDATE,news_node);
#endif

  if (!auth_list_1(newsgroups_header,cxt[stm].rhst,cxt[stm].rusr,cre_grp)) {
    strcpy(msg,"Attempted posting to unauthorized newsgroups.");
    if (fclose(fpr)) _ck_close(fpr);
    if (fclose(fpw)) _ck_close(fpw);
    while (!delete(scrfile));
    return(0);
    }

    /* check if this posting is by the moderator of a newsgroup,
       and if so, whether there is further moderation required,
       or if the posting can happen now */

  sprintf(mod_addr,"%.200s@%.200s",cxt[stm].rusr,cxt[stm].rhst);
  *post_addr = '\0';
  xx = news_strdup(newsgroups_header,-1);
  for (cp1=xx; cp1 && *cp1; cp1=cp2) {
    if ( (cp2 = strchr(cp1,',')) ) *cp2++ = '\0';
    util_cvrt(lg,cp1);
    if (getgroupbyname(lg) && (newsgrp.grp_flags & NEWS_M_MAILMODERATE)) {
      if (approved_header == 0 || *approved_header == 0) {
        strnncpy(post_addr,sizeof(post_addr),
                 moderator_address(util_grp_name_str(newsgrp.grp_name)),-1, 0);
        break;
        }
      else approval_required = 1;
      }
    }
  news_free(xx); xx = NULL;

  for (i = 0; i < OHEADERS; ++i) {
    if (headers[i]) {
      if (i == PATH) {
        fout_line(fheaders[PATH],fpw);
        if (!control_header && !*post_addr && !always_spool) {
          fout_line(news_pathname,fpw);
          fputc('!',fpw);
          }
        fout_line(headers[PATH],fpw);
        fputc('\n',fpw);
        }
      else {
        fout_line(fheaders[i],fpw);
        fout_line(headers[i],fpw);
        fputc('\n',fpw);
        }
      }
    }

  if (strcmp(cxt[stm].rhst,"tcp") && strcmp(cxt[stm].rhst,"decnet"))
    fprintf(fpw,"Nntp-Posting-Host: %s\n",cxt[stm].rhst);
  if (strcmp(cxt[stm].rusr,"nntp"))
    fprintf(fpw,"Nntp-Posting-User: %s\n",cxt[stm].rusr);

  if (approval_required && (approved_header == 0 || *approved_header == 0))
    fprintf(fpw,"Approved: %s\n",mod_addr);

  /* fenner@jazz.psu.edu - place newline after each header */
  for (i = 0; i < unk_index; ++i) {
    if (!strncmp(unk_headers[i],"Nntp-Posting-User",17) || !strncmp(unk_headers[i],"Nntp-Posting-Host",17))
      fout_line("X-",fpw);
    fout_line(unk_headers[i],fpw);
    fputc('\n',fpw);
    }
  fprintf(fpw,"Lines: %d\n",line_count);

  if (headers[X_PGP_SIG]) {
    int len,len1,len2, word_cnt=0;
    char *s = headers[X_PGP_SIG];
    int s_l;

    fout_line(fheaders[X_PGP_SIG],fpw);
    for (s_l=strlen(s);  s_l>0;  s+=len, s_l-=len) {
                         /* wrap at every opportunity after the second word */
      len1 = strspn(s," \t");         /* span whitespace */
      len2 = strcspn(&s[len1]," \t"); /* span non-whitespace */
      len = len1+len2;
      if (len >= s_l) break;          /* the last part is written out below */
      fwrite(s,sizeof(char),len,fpw); _ck_put(fpw); word_cnt++;
      if (word_cnt > 1) { fputc('\n',fpw); _ck_put(fpw); }
      }
    if (s_l > 0) { fwrite(s,sizeof(char),s_l,fpw); _ck_put(fpw); }
    fputc('\n',fpw); _ck_put(fpw);
    }

  fputc('\n',fpw);
  while (fgets(xfrbuf,sizeof(xfrbuf),fpr)) fputs(xfrbuf,fpw);
  if (fclose(fpr)) _ck_close(fpr);
  if (fclose(fpw)) _ck_close(fpw);

  if (*post_addr) {
    if (call_mail(scrfile,subject_header,add_transform(post_addr),from_address(from_header)) & 1) {
      sprintf(msg,"Post: Item posted to moderator (%.512s).",post_addr);
      posted = 1;
      }
    else sprintf(msg,"Post: Failed to send to moderator (%.512s).",post_addr);
    }
  else {
    if (control_header || always_spool) {
      posted = spool_add_item(scrfile,0,msg,stm,POST);
      }
    else {
      if (!sys_local_accept(newsgroups_header,distribution_header))
        strcpy(msg,"Post: newsgroups/distribution rejected by local SYS filter.");
      else {
        if (do_new_item(cre_grp,message_id_header,subject_header,from_header,
            scrfile,1,0,line_count,0))
          sprintf(msg,"Post: %.512s.",no_new_item);
        else {
          posted = 1;
          strcpy(msg,"Post: Item posted to news.");
          sys_remote_send(path_header,newsgroups_header,distribution_header,
              scrfile,message_id_header,0);
	  flush_downstream(0);
          }
        }
      }
    }

  post_cc(newsgroups_header,subject_header,from_header,scrfile,POSTCC_FILE);

  while (!delete(scrfile));
  return(posted);
}

/*
 * spool_add_item
 *
 */

static int max_batch = 0;

int spool_add_item(filename,buf,msg,stm,mode)
  char *filename;	/* name of temp file containing article (1st char NUL if none) */
  char *buf;		/* ptr to article in memory (only used if no temp file) */
  char *msg;		/* where to store an error message */
  int stm;
  int mode;		/* POST - we're spooling a posting
			   FEED - we're spooling a newsfeed  - used only to
			      estimate spool file size */
{
  char *batch_logical = NULL;
  char xfrbuf[IO_SIZE];
  FILE *fpr = 0;
  struct stat stat_buffer;

  if (filename && *filename) {
    if (!(fpr = fopen(filename,OPEN_READ_OPT1))) {
      sprintf(msg,"Temp %.255s: %.512s",filename,strerror(errno,vaxc$errno));
      return(0);
      }
    }
  max_batch = 0;
  if (mode == FEED) { /* if we're spooling an incoming newsfeed, calculate
			 the batch size */
    if ((batch_logical = news_getenv("NEWS_NNTP_SERVER_BATCH_SIZE",0)) != NULL)
              sscanf(batch_logical,"%d",&max_batch);
    if (max_batch == 0) max_batch = NEWS_BATCH_SIZE;
    else if (max_batch < NEWS_BATCH_SIZE_LOWLIM)
      max_batch = NEWS_BATCH_SIZE_LOWLIM;
    else if (max_batch > NEWS_BATCH_SIZE_HIGHLIM)
      max_batch = NEWS_BATCH_SIZE_HIGHLIM;
    }
  else {     /* otherwise, we're spooling a single posting, so use a
		more conservative size */
      max_batch = NEWS_BATCH_SIZE_LOWLIM;
      fstat(fileno(fpr),&stat_buffer); 	/* ARG - ihave_size needed */
      ihave_size = stat_buffer.st_size;	/* Later for #rnews value */
    }

  if (leave_spool_open) {
    sys$cantim(spool_flush_batch, 0);
    if (fpb) spool_flush_batch();
    if (!fpb) {
      char alloc_size[32];
      sprintf (alloc_size, "alq=%d", (max_batch+511)/512);
      sprintf(batchname,NEWSBATCH,time_str(),getpid(),"");
      sprintf(batchname_tmp,NEWSBATCH,time_str(),getpid(),"INCOMING_");
      fpb = fopen(batchname_tmp,OPEN_WRITE_OPT2,alloc_size,"fop=cbt,tef");
      batch_size = 0;
      }
    }
  else {
    if (!*batchname) {
      sprintf(batchname,NEWSBATCH,time_str(),getpid(),"");
      sprintf(batchname_tmp,NEWSBATCH,time_str(),getpid(),"INCOMING_");
      }

    rename(batchname,batchname_tmp);
    if (!*batchname || (batch_size > max_batch) ||
       !(fpb = fopen(batchname_tmp,OPEN_APPEND_OPT2))) {
      char alloc_size[32];
      sprintf (alloc_size, "alq=%d", (max_batch+511)/512);
      sprintf(batchname,NEWSBATCH,time_str(),getpid(),"");
      sprintf(batchname_tmp,NEWSBATCH,time_str(),getpid(),"INCOMING_");
      fpb = fopen(batchname_tmp,OPEN_WRITE_OPT2,alloc_size,"fop=cbt,tef");
      batch_size = 0;
      }
    }

  if (!fpb) {
    sprintf(msg,"Batch %.255s: %.512s",batchname,strerror(errno,vaxc$errno));
    if (fpr) { if (fclose(fpr)) _ck_close(fpr); }
    return(0);
    }
  fprintf(fpb,"#! rnews %d\n", (ihave_size > 0 ? ihave_size : 1));
  if (fpr) {
    while (fgets(xfrbuf,sizeof(xfrbuf),fpr)) fputs(xfrbuf,fpb);
    if (fclose(fpr)) _ck_close(fpr);
    }
  else {
#define MAX_FPUTSIZE 32750
    int wlen, dlen;
    char savec = 0, *ep = 0, *dp = buf;

    dlen = (ihave_size>0 ? ihave_size : strlen(buf));
    for (;  dlen>0;  dlen-=wlen, dp+=wlen) {
      wlen = dlen;
      if (wlen > MAX_FPUTSIZE) {
	ep = dp + MAX_FPUTSIZE;
	while (*ep != '\n') --ep;
	savec = ep[1];
	ep[1] = '\0';
	wlen = 1 + (ep-dp);
	}
      fputs(dp,fpb);
      if (dlen != wlen)
	ep[1] = savec;
      }
    }
  batch_size += ihave_size;

  if (leave_spool_open) {
    int daytim[2] = {-120*10000000, -1};
    sys$setimr(0, daytim, spool_flush_batch, spool_flush_batch, 0);
    }
  else {
    spool_flush_batch();
    }
  strcpy(msg,"Item successfully spooled.");
  return(1);
}

void spool_flush_batch()
{
  int always_close;
  char *noflush;

  if ( (noflush = news_getenv("NEWS_NNTP_SERVER_BATCH_FLUSH_DISABLE",0)) ) {
    if (isdigit(*noflush))
      always_close = (1 & strtol(noflush, NULL, 0));
    else {
      noflush[1] = '\0';
      noflush[0] = tolower(noflush[0]);
      always_close = strspn(noflush, "ty");
      }
    }
  else always_close = !leave_spool_open;

  if (fpb) {
    if (always_close || batch_size >= max_batch) {
      if (fclose(fpb)) _ck_close(fpb);
      rename(batchname_tmp,batchname);
      fpb = NULL;
      }
    else {
      fflush(fpb);
      fsync(fileno(fpb));
      }
    }
}

void close_batch_file(void)
{
  if (fpb) {
      if (fclose(fpb)) _ck_close(fpb);
      rename(batchname_tmp,batchname);
      fpb = NULL;
      }
}

/*
 *  close_file
 *
 *  Close the scratch file and call add_item.
 */

static
int close_file(fp,buf,mode,msgbuf,stm)
  FILE *fp;
  char *buf;
  int mode;
  char *msgbuf;
  int stm;
{
  int retval;
  char filename[FILE_STRLEN];

  if (fp) {
    fgetname(fp,filename);
    if (fclose(fp)) _ck_close(fp);
    }
  else *filename = '\0';
  if (mode == POST) retval = post_add_item(filename,buf,msgbuf,stm);
  else retval = spool_add_item(filename,buf,msgbuf,stm,FEED);
  if (*filename) while (!delete(filename));
  return(retval);
}

/*
 *  open_server_files
 *
 *  Open the item and newsgroup files for sharing
 */
static int itm_file_open = 0,
           grp_file_open = 0,
           hist_file_open = 0,
           hist_off = 0;

#ifdef __DECC
#pragma member_alignment save
#pragma nomember_alignment   /* no member alignment - VMS structures */
#endif

static struct FAB histfab;
static struct RAB histrab;

static struct hist {
  char hist_id[IDLEN];
  unsigned int hist_date;
  } newshist;

#ifdef __DECC
#pragma member_alignment restore
#endif

int open_hist_file()
{
  int status;
  FILE *fpr;

  if ( (fpr = fopen(HIST_OFF,OPEN_READ_OPT0)) ) {
    if (fclose(fpr)) _ck_close(fpr);
    hist_off = 1;
    return(0);
    }

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

  histrab = cc$rms_rab;
  histrab.rab$l_fab = &histfab;
  histrab.rab$l_rbf = histrab.rab$l_ubf = (char *) &newshist;
  histrab.rab$w_rsz = histrab.rab$w_usz = sizeof newshist;

  if (!sys_open_nofnf(&histfab)) return(0);
  hist_file_open = 1;
  if (!sys_connect(&histrab)) {
    sys_close(&histfab); hist_file_open = 0;
    return(0);
    }
  return(hist_file_open);
}

static int open_server_files(void)
{
  int status;
  static struct XABKEY xabkey_3;

  itmfab = cc$rms_fab;
  itmfab.fab$b_fac = FAB$M_GET | FAB$M_PUT | FAB$M_DEL;
  itmfab.fab$l_fna = (char *) ITM_FILENAME;
  itmfab.fab$b_fns = strlen(itmfab.fab$l_fna);
  itmfab.fab$w_mrs = sizeof newsitm;
  itmfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

  itmrab = cc$rms_rab;
  itmrab.rab$l_fab = &itmfab;
  itmrab.rab$l_ubf = itmrab.rab$l_rbf = (char *) &newsitm;
  itmrab.rab$w_usz = itmrab.rab$w_rsz = sizeof newsitm;
  
  grpfab = cc$rms_fab;
  grpfab.fab$b_fac = FAB$M_GET | FAB$M_PUT | FAB$M_UPD;
  grpfab.fab$l_fna = (char *) GRP_FILENAME;
  grpfab.fab$b_fns = strlen(grpfab.fab$l_fna);
  grpfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;
  grpfab.fab$l_xab = (char *) &xabkey_3;
  xabkey_3 = cc$rms_xabkey; xabkey_3.xab$b_ref = 0;

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

 /* ST 6/8/94  RMS Performance Patch */
{
       int buff_count=0;
       char *buff_count_ptr;
       buff_count_ptr = news_getenv("NEWS_ITEMFILE_BUFFERCOUNT",0);
       if (buff_count_ptr != NULL)  sscanf(buff_count_ptr,"%d",&buff_count);
     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 */
     if (buff_count > 0) 
       itmrab.rab$b_mbf = (buff_count <= 127) ? buff_count : 127;
}
  if (!sys_open(&itmfab)) return(0);
  itm_file_open = 1;

 /* 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;
       }
     }

  if (!sys_connect(&itmrab)) {
    sys_close(&itmfab); itm_file_open = 0;
    return(0);
    }
/* 6/8/94 saul@hnrc.tufts.edu - RMS Performance patch */
{
      int buff_count=0;
      char *buff_count_ptr;
      buff_count_ptr = news_getenv("NEWS_GROUPFILE_BUFFERCOUNT",0);
      if (buff_count_ptr != NULL)  sscanf(buff_count_ptr,"%d",&buff_count);

    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 */
    if (buff_count > 0)
      grprab.rab$b_mbf = (buff_count <= 127) ? buff_count : 127;
}
  if (!sys_open(&grpfab)) return(0);
  grp_file_open = 1;

/* 6/8/94 - saul@hnrc.tufts.edu RMS Performance patch */
      {
      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 (!sys_connect(&grprab)) {
    sys_close(&grpfab); grp_file_open = 0;
    return(0);
    }
  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);
    }
  open_hist_file();
  return(1);
}

/*
 *  util_dir
 *
 *  Convert a newsgroup name to a VMS directory string.
 *
 *  Allow group name size to be at most GRPNAMELEN_F characters,
 *  thus accepting also the not necessarily '\0'-terminated newsgrp.grp_name
 *  as a parameter.
 */

static char dir_result[GRPNAMELEN_F + GRPNAMELEN_F + 1];

char *
util_dir(input)
    const char *input;
{
    char *p = dir_result;
    const char *in;
    int j;

    for (in=input, j=0;  j<GRPNAMELEN_F && *in;  in++, j++) {
      if (isalnum(*in) || (*in == '-') || (*in == '.'))
        *p++ = *in;
      else {
        *p++ = '_';
        if     (*in == '_') *p++ = '_';
        else if (*in < '0') *p++ = (*in - '!') + 'A';
        else if (*in < 'A') *p++ = (*in - ':') + '0';
        else if (*in < 'a') *p++ = (*in - '[') + 'P';
        else                *p++ = (*in - '{') + 'V';
        }
      }
    *p = '\0';
    return(dir_result);
}

/*
 *  util_cvrt
 *
 *  Convert a string into standard newsgroup name format
 */

void util_cvrt(result, input)
    char *result;
    const char *input;
{
    int input_l, j;
    char *p;

    while (*input == ' ') input++;
    for (input_l=strlen(input); input_l>0 && input[input_l-1]==' '; input_l--){}
    strnncpy(result,GRPNAMELEN, input,input_l, 0);
    for (j=input_l; j<GRPNAMELEN; j++) result[j] = '\0';
    for (p=result; *p; p++) {
      if (!isgraph(*p)) *p = '_';
      else if (*p>='A' && *p<='Z') *p = *p - 'A' + 'a';
      }
}

/*
 *  cvt_date_str
 *
 *  cvt_date_str converts the strings "yymmdd", "hhmmss" to Unix-format date value.
 *
 */

static
const char *mstr[] =
  {"JAN","JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"};

static int cvt_date_str(d,h)
    char *d,
         *h;
/* renamed cvt_date to cvt_date_str to avoid mixup with the function cvt_date
   in NEWSRTL.C which has a different functionality.     mark.martinec@ijs.si */
{
    char c_str[30];
    int t, monthind, year,
        two = 2,
        vax_date[2],
        offset[2] = {0X4BEB4000, 0X007C9567},
        adjtim[2],
        divisor = 10000000;
    $DESCRIPTOR(c_dsc,c_str);

    if (strlen(d) != 6 || strlen(h) != 6) return(-1);
    monthind = (d[2]-'0')*10 + (d[3]-'0');
    year = (d[0]-'0')*10 + (d[1]-'0');
    year = (year>=70) ? year+1900 : year+2000;
    sprintf(c_str,"%c%c-%s-%04d %c%c:%c%c:%c%c.0",
        d[4],d[5],
        mstr[monthind > 12 ? 0 : monthind],
        year,
        h[0],h[1], h[2],h[3], h[4],h[5]);

    c_dsc.dsc$w_length = strlen(c_str);
    if (!(sys$bintim(&c_dsc,vax_date) & 1)) t = -1;
    else {
        lib$subx(vax_date,offset,adjtim,&two);
        lib$ediv(&divisor,adjtim,&t,vax_date);
        }
    return(t);
}

/*
 *  check_read_access
 *
 *  If the newsgroup is set as a restricted access newsgroup, then the
 *  newsgroup access file is consulted to establish whether the remote
 *  user has read access to the newsgroup.
 */

static
int check_read_access(grpname,usr,host)
    char *grpname, *usr, *host;
{
    char acline[IO_SIZE], cknode[132],
         *cp, *ap, *op;
    int read_access = 1;
    FILE *fpd;

    if (!(newsgrp.grp_flags & NEWS_M_RESTRICT_SET)) return(1);
    sprintf(itm_fname,Access_template,util_dir(newsgrp.grp_name));
    if ( (fpd = fopen(itm_fname,OPEN_READ_OPT0)) ) {
        read_access = 0;
        while (fgets(acline,sizeof(acline),fpd)) {
            if (*acline == '#') continue;
            if ( (cp = strchr(acline,'\n')) ) *cp = ' ';
            lower_case(acline);
            if ( (op = strchr(acline,':')) ) *op = '\0';
            if ( (cp = strchr(acline,' ')) ) *cp = '\0';
            if ( (ap = strchr(acline,'@')) ) *ap = '\0';
            if (!strcmp(acline,usr) || wild_match(usr,acline)) {
                if (cp) *cp = ' ';
                if (ap) *ap = '@';
                if (strchr(acline,'@')) {
                    sprintf(cknode,"@%.129s ",host);
                    if (!substrcmp(acline,cknode)) continue;
                    }
                read_access = 1;
                break;
                }
            }
        if (fclose(fpd)) _ck_close(fpd);
        }
    return(read_access);
}

/*
 *  check_ngperm
 *
 *  Check the newsgroup name against the NNTP access group list, and prevent
 *  access if the group is not included in the permissions list.
 */

static
int check_ngperm(grpname,stm)
    char *grpname;
    int stm;
{
    int i;
    int match = 0;
    char mbuf[BUFF_STRLEN],
         *cp;
    int mbuf_l; /* strlen(mbuf) */

    cp = mbuf + 1;
    if (cxt[stm].grpc < 0) match = 1;
    else {
        for (i = 0; i < cxt[stm].grpc; ++i) {
            strnncpy(mbuf,sizeof(mbuf), cxt[stm].grpv[i],-1, &mbuf_l);
            if (*mbuf == '!') {
                if (wild_match(grpname,cp)) return(0);
                strnncat(mbuf,sizeof(mbuf),mbuf_l, ".*",2, &mbuf_l);
                if (wild_match(grpname,cp)) return(0);
                }
            else {
                if (wild_match(grpname,mbuf)) match = 1;
                else {
                    strnncat(mbuf,sizeof(mbuf),mbuf_l, ".*",2, &mbuf_l);
                    if (wild_match(grpname,mbuf)) match = 1;
                    }
                }
            }
        }
    if (match) match = check_read_access(grpname,cxt[stm].rusr,cxt[stm].rhst);
    return(match);
}

/*
 *  host_access
 *
 *  Determine the access of the client by consulting the NNTP access file.
 *  Permissions in this file are based on node names, and determine read,
 *  post and transfer access. The file also may contain a newsgroup filter
 *  set.
 */

static void host_access(canread,canpost,canxfr,gdlist,stm)
  int *canread, *canpost, *canxfr;
  char *gdlist;
  int stm;
{
  char host_name[HOST_STRLEN], host[HOST_STRLEN], line[BUFF_STRLEN],
       readperm[BUFF_STRLEN], postperm[BUFF_STRLEN], groups[BUFF_STRLEN],
       *cp;
  int count;
  FILE *acs_fp;

  strcpy(gdlist,"*");
  *canread = *canpost = *canxfr = 0;
  if (!(acs_fp = fopen(NNTP_ACCESS_FILE,OPEN_READ_OPT0))) {
    *canread = *canpost = *canxfr = 1;
    return;
    }
  strnncpy(host_name,sizeof(host_name), cxt[stm].rhst,-1, 0);

  while (fgets(line,sizeof(line),acs_fp)) {
    if ( (cp = strchr(line,'\n')) ) *cp = '\0';
    if ( (cp = strchr(line,'#')) ) *cp = '\0';
    if (!*line) continue;
    lower_case(line);
    count = sscanf(line,"%s %s %s %s",host,readperm,postperm,groups);
    if (count < 2) continue;
    if (count < 3) *postperm = '\0';
    if (count < 4) strcpy(groups,"*");
    if (!strcmp(host,host_name) || wild_match(host_name,host)) {
      *canread = (*readperm == 'r' || *readperm == 'v');
      *canxfr = (*readperm == 'r' || *readperm == 'x');
      *canpost = (*postperm == 'p');
      strcpy(gdlist,groups);
      break;
      }
    if (!strcmp(host,"default")) {
      *canread = (*readperm == 'r' || *readperm == 'v');
      *canxfr = (*readperm == 'r' || *readperm == 'x');
      *canpost = (*postperm == 'p');
      strcpy(gdlist,groups);
      }
    }
  if (fclose(acs_fp)) _ck_close(acs_fp);
}

static
struct il {
    char id[132];
    struct il *next;
    } *itm_list = 0,
      *ngp_list = 0;

static
struct gl {
    unsigned int gl_index;
    struct gl *gl_next;
    } *grpnum_list = 0;

static
int gl_all = 0;

static void add_list(i,tp)
  const char *i;
  struct il **tp;
{
  struct il *tmp;

  tmp = (struct il *) news_malloc(sizeof *tmp);
  util_idcpy(tmp->id,i);
  tmp->next = *tp;
  *tp = tmp;
}

static int loc_grp(g,tmp)
  const char *g;
  struct il *tmp;
{
  for (; tmp; tmp=tmp->next) {
    if (wild_match(g,tmp->id)) return(1);
    }
  return(0);
}

static int loc_ngrp(g,tmp)
    const char *g;
    const struct il *tmp;
{
    for (; tmp; tmp=tmp->next) {
        if (*(tmp->id) == '!') {
            if (wild_match(g,(tmp->id) + 1)) return(0);
            }
        else if (wild_match(g,tmp->id)) return(1);
        }
    return(0);
}

/*
 * loc_dist
 *
 * Search the supplied distribution set for a match with a distribution
 * word. If the word matches a set entry then return 1, if a match with
 * a negated set entry then return 0, else return -1
 */

static int loc_dist(g,tmp)
    char *g;
    struct il *tmp;
{
    int i;
    for (; tmp; tmp=tmp->next) {
        if (*(tmp->id) == '!') {
            if (wild_match(g,(tmp->id) + 1)) return(0);
            tmp->id[(i = (strlen(tmp->id) - 2))] = '\0';
            if (wild_match(g,(tmp->id) + 1)) return((tmp->id[i] = '.'),0);
            tmp->id[i] = '.';
            }
        else {
            if (wild_match(g,tmp->id)) return(1);
            tmp->id[(i = (strlen(tmp->id) - 2))] = '\0';
            if (wild_match(g,(tmp->id))) return((tmp->id[i] = '.'),1);
            tmp->id[i] = '.';
            }
        }
    return(-1);
}

static int loc_id(i)
    const char *i;
{
    struct il *tmp,
              *ptmp = 0;

    for (tmp=itm_list; tmp; tmp=tmp->next) {
        if (!strcmp(i,tmp->id)) {
            if (ptmp) ptmp->next = tmp->next;
            else itm_list = tmp->next;
            news_free(tmp); tmp = NULL;
            return(1);
            }
        ptmp = tmp;
        }
    return(0);
}

static void convert_filter(l,f,stm)
  struct il *l, *f;
  int stm;
{
  int status;
  struct gl *gtmp, *gt = 0;

  gl_all = 1;
  grprab.rab$b_krf = 0;
  grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
  grprab.rab$b_rac = RAB$C_SEQ;


  sys_rewind(&grprab);
  while (sys_get_noeof(&grprab)) {
    char *gn = util_grp_name_str(newsgrp.grp_name);
    if (!newsgrp.grp_num) continue;
    if (   !loc_ngrp(gn,l)
	|| (f && !loc_dist(gn,f))
	|| !check_ngperm(gn,stm)) {
      gl_all = 0;
      continue;
      }
    gtmp = (struct gl *) news_malloc(sizeof *gtmp);
    gtmp->gl_index = newsgrp.grp_num;
    gtmp->gl_next = 0;
    if (gt) gt->gl_next = gtmp;
    else grpnum_list = gtmp;
    gt = gtmp;
    }
}

/*
 * check_dist
 *
 * check if a newsitem matches a distribution list. As the distribution
 * list has already been applied to the newsgroup names (which is implicitly
 * the distribution) then this filter works as follows:
 *      no distribution: header - accept
 *      match distribution in header against a "!entry" in the dlist - reject
 *      match against a word - accept, but continue checking for !match
 *      no match - reject
 *  i.e. if there is a !match and a match then the !match wins and the check
 *  return reject (a !filter is a specific negation of a larger
 *  class of acceptance is the underlying assumption here).
 */

static int check_dist(ilp)
  struct il *ilp;
{
  int status;
  FILE *fpr;
  char buf[NNTP_STRLEN], c, *cp, *cp1;
  int accept = 1, i;

  if (newsgrp.grp_num != newsitm.itm_grp) {
    grprab.rab$l_kbf = (char *) &(newsitm.itm_grp);
    grprab.rab$b_ksz = 4;
    grprab.rab$b_krf = 1;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_KEY;
    if (!sys_get_nornf(&grprab)) return(0);
    }
  sprintf(itm_fname,Itm_template,util_dir(newsgrp.grp_name),newsitm.itm_num);
  if (!(fpr = fopen(itm_fname,OPEN_READ_OPT0))) return(0);
  while (fgets(buf,sizeof(buf),fpr)) {
    if ((c = *buf) == '\n') break;
    if ((c != 'd') && (c != 'D')) continue;
    if (!(cp = strchr(buf,':'))) continue;
    *cp++ = '\0';
    lower_case(buf);
    lower_case(cp);
    if (strcmp(buf,"distribution")) continue;
    accept = 0;
    while (isspace(*cp)) cp++;
    if ( (cp1 = strchr(cp,'\n')) ) *cp1 = '\0';
    for (; cp; cp=cp1) {
      if ( (cp1 = strchr(cp,',')) ) *cp1++ = '\0';
      if (!(i = loc_dist(cp,ilp))) {
        accept = 0;
        break;
        }
      if (i == 1) accept = 1;
      }
    break;
    }
  if (fclose(fpr)) _ck_close(fpr);
  return(accept);
}

/*
 *  openartbyid
 *
 *  Open article given the identifier key.
 */

static
FILE *openartbyid(id,msg_status,stm,no_open_file)
  const char *id;
  int *msg_status,
      stm,
      no_open_file;
{
  int status;
  char l_id[IDLEN + 4];

  *msg_status = M430;

  memset(&l_id[IDLEN],0,4);
  util_idcpy(l_id,id);

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

  itmrab.rab$l_kbf = l_id;
  itmrab.rab$b_ksz = IDLEN + 4;
  itmrab.rab$b_krf = 1;
  itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
  itmrab.rab$b_rac = RAB$C_KEY;
  if (   !sys_get_nornf(&itmrab)
      || strcmp(l_id,newsitm.itm_id)
      || !sys_get_nornf(&grprab))
    return(NULL);
  itmrab.rab$b_rac = RAB$C_SEQ;
  while (!check_ngperm(util_grp_name_str(newsgrp.grp_name),stm))
    if (   !sys_get_nornf(&itmrab)
        || strcmp(l_id,newsitm.itm_id)
        || !sys_get_nornf(&grprab)) {
      *msg_status = M502;
      return(NULL);
      }
  if (no_open_file)
    return((FILE *) 1);   /* address 1 ???  is this ok?  mark.martinec@ijs.si */

  sprintf(itm_fname,Itm_template,util_dir(newsgrp.grp_name),newsitm.itm_num);
  return(fopen(itm_fname,OPEN_READ_OPT1));
}

/*
 *  start_seq
 *
 *  Utility routine used by xhdr. Returns the first item number in the current
 *  newsgroup which is greater than or equal to the arg value.
 */

static int start_seq(inum,stm)
    int     inum;
    int     stm;
{
    int status;
    unsigned int    l_key[2];

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

    l_key[0] = inum;
    l_key[1] = cxt[stm].curr_group;
    itmrab.rab$l_kbf = (char *) l_key;
    itmrab.rab$b_ksz = 8;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    if ((!sys_get_nornf(&itmrab)) || (newsitm.itm_grp != cxt[stm].curr_group)
        || (!sys_get_nornf(&grprab))) return(0);
    itmrab.rab$b_rac = RAB$C_SEQ;
    return(newsitm.itm_num);
}

#ifdef XOVER
void ovr_free()
{
/* Alan - Clear cached overview if any */
  if (ovr)
    {
/*    printf("ovr_free: clearing cached overview\n"); */
    for (item=index_first;item<index_last;item++)
      {
      if (ovr[item-index_first])
        {
        news_free(ovr[item-index_first]);
        ovr[item-index_first] = 0;
        }
      }
    news_free(ovr);
    ovr=NULL;
    ovr_groupname[0] = 0;
    }
}
#endif

/*
 *  print_header
 *
 *  Utility routine used by xhdr to extract a single header line from an item
 *  line and write it to the network channel.
 */

static void print_header(fp, header, artname,stm)
  FILE *fp;
  const char *header, *artname;
  int stm;
{
  char line[NNTP_STRLEN];
  char *cp, *cp1;

  while (fgets(line, sizeof(line), fp) != NULL) {
    if (*line == '\n' || *line == '\0') {
      sprintf(sbuf,"%.512s (none)\r\n", artname);
      write_net(sbuf,stm); return;
      }
    if ( (cp = strchr(line, ':')) ) {
      *cp++ = '\0';
      cp++;
      lower_case(line);
      if (!strcmp(header, line)) {
        if ( (cp1 = strchr(cp, '\n')) ) *cp1 = '\0';
        sprintf(sbuf,"%.512s %.512s", artname, cp);
        write_net(sbuf,stm);
        while (fgets(line, sizeof(line), fp) != NULL) {
          if ((*line != ' ') && (*line != '\t')) break;
          if ( (cp1 = strchr(line,'\n')) ) *cp1 = '\0';
          write_net(line,stm);
          }
        write_net("\r\n",stm);
	return;
        }
      }
    }
}

/*
 *  get_distlist
 *
 *
 */

static int get_distlist(tp,dl)
  struct il **tp;
  char *dl;
{
  char *cp1,*cp2,
       dist_filter[BUFF_STRLEN];
  int dcount = 0;

  *tp = 0;
  if (*dl != '<') return(-1);
  if ( (cp1 = strchr(dl++,'>')) ) *cp1 = '\0';
  else return(-1);
  for (cp1=dl; cp1; cp1=cp2) {
    if ( (cp2 = strchr(cp1,',')) ) *cp2++ = '\0';
    if (!*cp1) continue;
    strnncpy(dist_filter,sizeof(dist_filter), cp1,-1, 0);
    strnncat(dist_filter,sizeof(dist_filter),-1, ".*",2, 0);
    add_list(dist_filter,tp);
    ++dcount;
    }
 return(dcount);
}

/*  NNTP COMMAND ROUTINES
 *
 *  ahbs
 *
 *  ARTICLE, BODY, HEAD, STAT  [] | [num] | <message-id>
 */

#define ARTICLE 0
#define HEAD    1
#define BODY    2
#define STAT    3

static void ahbs(argc,argv,stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  int cmd_type;
  char *cp,*cp1;
  FILE *fpr;

  if (!cxt[stm].canread) { write_net(msg[M502],stm); return; }
  if (argc > 2) { write_net(msg[M501],stm); return; }
  if (*argv[0] == 'a') cmd_type = ARTICLE;
  else if (*argv[0] == 'h') cmd_type = HEAD;
  else if (*argv[0] == 'b') cmd_type = BODY;
  else cmd_type = STAT;

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

  if ((argc == 2) && (*argv[1] == '<')) {
    int mdx;

    if (!(fpr = openartbyid(argv[1],&mdx,stm,0)))
      { write_net(msg[mdx],stm); return; }
    }
  else {
    unsigned int l_key[2];

    if (!cxt[stm].curr_group)
      { write_net(msg[M412],stm); return; }
    if (argc == 1) {
      if (!cxt[stm].curr_item)
        { write_net(msg[M420],stm); return; }
      l_key[0] = cxt[stm].curr_item;
      }
    else if (sscanf(argv[1],"%d",&(l_key[0])) < 1)
      { write_net(msg[M501],stm); return; }

    l_key[1] = cxt[stm].curr_group;

    itmrab.rab$l_kbf = (char *) l_key;
    itmrab.rab$b_ksz = 8;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_KEY;

    if (!sys_get_nornf(&itmrab) || !sys_get_nornf(&grprab))
      { write_net(msg[M423],stm); return; }
    cxt[stm].curr_item = newsitm.itm_num;
    sprintf(itm_fname,Itm_template,util_dir(newsgrp.grp_name),newsitm.itm_num);
    if (!(fpr = fopen(itm_fname,OPEN_READ_OPT0)))
      { write_net(msg[M430],stm); return; }
    }

  sprintf(sbuf,msg[M220 + cmd_type],newsitm.itm_num,newsitm.itm_id);
  write_net(sbuf,stm);

  if (cmd_type != STAT) {
    int header = 1;

    *sbuf = '.';	/* for when we need to pre-pend a period */
    while (fgets(sbuf+1, sizeof(sbuf)-3, fpr)) {
      if ( (cp1 = strchr(sbuf+1, '\n')) ) *cp1 = '\0';
      cp = (*(sbuf+1) == '.') ? sbuf : sbuf+1;
      if (((cmd_type == HEAD) && header && *(sbuf+1))
          || ((cmd_type == BODY) && !header)
          || (cmd_type == ARTICLE)) {
        while (!cp1) {		/* allow really long lines */
          write_net(cp, stm);
          cp = fgets(sbuf+1, sizeof(sbuf)-3, fpr);
          if (!cp) {
            cp = sbuf+1;
            *cp = '\0';
            break;
            }
          if ( (cp1 = strchr(cp, '\n')) ) *cp1 = '\0';
          }
        strcat(cp, "\r\n");
        write_net(cp, stm);
        }
      else if (!cp1) {		/* skip over remainder of a long line */
        while (fgets(sbuf+1, sizeof(sbuf)-3, fpr) && !strchr(sbuf+1, '\n'));
        }
      else if (cp1 == sbuf+1) header = 0;
      }

    write_net(msg[EOM],stm);
    cxt[stm].arts_read++;
    }
  if (fclose(fpr)) _ck_close(fpr);
}


/*
 *  group
 *
 *  GROUP newsgroup
 */

static void group(argc,argv,stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  char l_grp[GRPNAMELEN];
  unsigned int l_key[2];

/*  if (argc != 2) { write_net(msg[M501],stm); return; } Netscape RRS*/
  if (argc < 2) { write_net(msg[M501],stm); return; }
  if (strlen(argv[1]) > GRPNAMELEN_F) { write_net(msg[M411],stm); return; }
  if (!cxt[stm].canread) { write_net(msg[M502],stm); return; }
  lower_case(argv[1]);
  util_cvrt(l_grp,argv[1]);
  grprab.rab$l_kbf = l_grp;
  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;
  if (!sys_get_nornf(&grprab)) { write_net(msg[M411],stm); return; }
  if (!check_ngperm(util_grp_name_str(newsgrp.grp_name),stm))
    { write_net(msg[M502],stm); return; }
  cxt[stm].curr_group = newsgrp.grp_num;
  cxt[stm].high_item  = newsgrp.grp_topnum;
  l_key[1] = cxt[stm].curr_group;
  strnncpy(cxt[stm].curr_grpnam,sizeof(cxt[stm].curr_grpnam),
           newsgrp.grp_name,util_grp_name_len(newsgrp.grp_name), 0);
  l_key[0] = 0;
  l_key[1] = cxt[stm].curr_group;
  itmrab.rab$l_kbf = (char *) l_key;
  itmrab.rab$b_ksz = 8;
  itmrab.rab$b_krf = 0;
  itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
  itmrab.rab$b_rac = RAB$C_KEY;
  if (!sys_get_nornf(&itmrab)) cxt[stm].curr_item = 0;
  else if (newsitm.itm_grp != newsgrp.grp_num) cxt[stm].curr_item = 0;
  else cxt[stm].curr_item = newsitm.itm_num;

  sprintf(sbuf,msg[M211],newsgrp.grp_count,
          (cxt[stm].curr_item ? cxt[stm].curr_item :  0),
          (cxt[stm].curr_item ? newsgrp.grp_topnum : 0),
          util_grp_name_str(newsgrp.grp_name));
  write_net(sbuf,stm);
  ++cxt[stm].grps_read;
#ifdef XOVER
  if  (ovr && strcmp(ovr_groupname,cxt[stm].curr_grpnam)) /* Same newsgroup? */
    {
    ovr_free();         /* No -- cache not valid */
    }
#endif
}


/*
 *  help
 *
 *  HELP
 */

static void help(argc,argv,stm)
  int argc;
  char *argv[];
  int stm;
{
  int i;

  write_net(msg[M100],stm);
  for (i=0; *help_cmds[i]; i++)
    write_net(help_cmds[i],stm);
}

/*
 *  ihave
 *
 *  news item distribution command
 */

int hist_check(id)
  const char *id;
{
  int status;
  char l_id[IDLEN];

  if (!hist_file_open) return(0);
  util_idcpy(l_id,id);
  histrab.rab$l_kbf = l_id;
  histrab.rab$b_krf = 0;
  histrab.rab$b_ksz = IDLEN;
  histrab.rab$l_rop= RAB$M_RRL | RAB$M_NLK ;
  histrab.rab$b_rac = RAB$C_KEY;
  return(sys_find_nornf(&histrab));
}

static int itm_check(id)
  char *id;
{
  int status;

  itmrab.rab$l_kbf = id;
  itmrab.rab$b_ksz = IDLEN + 4;
  itmrab.rab$b_krf = 1;
  itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
  itmrab.rab$b_rac = RAB$C_KEY;

  if ((!sys_get_nornf(&itmrab)) || strcmp(id,newsitm.itm_id)) return(0);
  return(1);
}

static int id_check(id,stm)
  const char *id;
  int stm;
{
  char l_id[IDLEN + 4];

  memset(&l_id[IDLEN],0,4);
  util_idcpy(l_id,id);

  strnncpy(cxt[stm].cache_id,sizeof(cxt[stm].cache_id),
           l_id,-1, 0);  /* remember cache id for later */
  if (cache_check(l_id,0) || itm_check(l_id) || hist_check(l_id)) return(1);
  return(0);
}

static FILE *append_line(buf,len,fpw,msg,stm)
  const char *buf;		/* pointer to stuff to append to article */
  int len;			/* length of string in buf */
				/* if len != strlen(buf) bad things will happen */
  FILE *fpw;			/* pointer to temporary file (if open) */
  char *msg;			/* where to store an error message */
  int stm;
{
  if (fpw) {
    fputs(buf,fpw);
    return(fpw);
    }
  if (sizeof(artbuf) - (artptr - artbuf) < len + 1) {
    if (!(fpw = open_file(stm))) {
      sprintf(msg,"Cannot create temporary file: %.512s",strerror(errno,vaxc$errno));
      return(0);
      }
    fputs(artbuf,fpw);
    artptr = artbuf;
    *artptr = '\0';
    fputs(buf,fpw);
    return(fpw);
    }

  strcpy(artptr,buf);
  artptr += len;
  return(0);
}

static void ihave_2(int);

static void ihave(argc,argv,stm)
  int argc;
  char *argv[];
  int stm;
{
  if (argc != 2) { write_net(msg[M501],stm); return; }
  cxt[stm].offered++;
  if (id_check(argv[1],stm)) {
    write_net(msg[M435],stm);
    cxt[stm].rejected++;
    return;
    }
  if (!cxt[stm].canxfr)
    { write_net(msg[M502],stm); return; }
  if (diskdanger()) {
    write_net(msg[M502D],stm);
    cxt[stm].failed++;
    c$free_tmp();
    return;
    }
  write_net(msg[M335],stm);
  next_call(stm,ihave_2,FILE_INPUT);
}

static void ihave_2(stm)
  int stm;
{
  char msgbuf[BUFF_STRLEN], *cp;
  int mdx, len;
  FILE *fpl = 0;

  next_call(stm,parser,CMD_INPUT);
  ihave_size = 0;
  artptr = artbuf;
  *artptr = '\0';
  *msgbuf = '\0';
  elapsed_time(1);
  while ( (len = read_net(ibuf,sizeof(ibuf),stm)) ) {
    if (*ibuf == '.') {
      cp = ibuf + 1;
      len--;
      if (*cp == '\n') break;
      }
    else cp = ibuf;
    if (!*msgbuf) fpl = append_line(cp,len,fpl,msgbuf,stm);
    ihave_size += len;
    if (*(cp + len - 1) != '\n') {				/* WAS !strchr(cp, '\n')) { */
      while ( (len = read_net(ibuf,sizeof(ibuf),stm)) ) {
        if (!*msgbuf) fpl = append_line(ibuf,len,fpl,msgbuf,stm);
        ihave_size += len;
        if (*(cp + len - 1) == '\n') break;
        }
      }
    }
  if (!*msgbuf && (fpl || *artbuf)) {
    if (close_file(fpl,artbuf,FEED,msgbuf,stm)) {
      mdx = 235;
      cxt[stm].accepted++;
      cache_check(cxt[stm].cache_id,1);
      }
    else {
/*
 * wcf 7/23/92
 * This looks like it should be allowed to be tried again
      mdx = 437;
 */
      mdx = 436;
      cxt[stm].failed++;
      }
    sprintf(sbuf,"%d %.512s\r\n",mdx, msgbuf);
    write_net(sbuf,stm);
    }
  else {
    cxt[stm].failed++;
    if (*msgbuf) {
      sprintf(sbuf,"436 %.512s\r\n",msgbuf);
      write_net(sbuf,stm);
      }
    else write_net(msg[M437],stm);
    }
  total_elapsed_time += elapsed_time(0);
  total_bytes_received += ihave_size;
  c$free_tmp();
}

/*
 *  list_cmd
 *
 *  LIST command
 */

static void list(argc,argv,stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  int list_sw = 1;
  char list_grp[1024] = "*";

  if (argc > 3) { write_net(msg[M501],stm); return; };
  if (argc >= 2) {
    lower_case(argv[1]);	/* fix - Bill Glass 4/4/91 */
    if (argc == 3)
      strnncpy(list_grp,sizeof(list_grp), lower_case(argv[2]),-1, 0);
    if (!strcmp(argv[1],"active"))  list_sw = 1;
    else if (!strcmp(argv[1],"newsgroups")) list_sw = 2;
    else if (!strcmp(argv[1],"distributions"))list_sw = 3;
#ifdef XOVER
    else if (!strcmp(argv[1],"overview.fmt")) list_sw = 4;
#endif
    else { write_net(msg[M501],stm); return; }
    }
#ifdef XOVER
  if (list_sw == 4) {
    write_net("215 Order of fields in overview database.\r\n",stm);
    write_net("Subject:\r\n",stm);
    write_net("From:\r\n",stm);
    write_net("Date:\r\n",stm);
    write_net("Message-ID:\r\n",stm);
    write_net("References:\r\n",stm);
    write_net("Bytes:\r\n",stm);
    write_net("Lines:\r\n",stm);
    write_net(msg[EOM],stm);
    return;
    }
#endif
  if (list_sw == 3) {
    FILE *fpr;
    char inpline[512], *cp1;

    if ( !(fpr = fopen(DISTRIBUTIONS_FILE,OPEN_READ_OPT0)) )
      write_net(msg[M503],stm);
    else {
      write_net(msg[M215],stm);
      while (fgets(inpline,sizeof(inpline),fpr)) {
        if ( (cp1 = strchr(inpline,'#')) ) *cp1 = '\0';
        if ( (cp1 = strchr(inpline,'\n')) ) *cp1 = '\0';
        if (*inpline) {
          sprintf(sbuf,"%.512s\r\n",inpline);
          write_net(sbuf,stm);
          }
        }
      write_net(msg[EOM],stm);
      }
    return;
    }
  grprab.rab$b_krf = 0;
  grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
  grprab.rab$b_rac = RAB$C_SEQ;

  write_net(msg[M215],stm);

  sys_rewind(&grprab);
  while (sys_get_noeof(&grprab)) {
    if (!newsgrp.grp_num ||
        !check_ngperm(util_grp_name_str(newsgrp.grp_name),stm)) continue;
    if (list_sw == 2) {
      sprintf(sbuf,"%.*s\t%.512s\r\n",
              GRPNAMELEN_F,newsgrp.grp_name, newsgrp.grp_topic);
      }
    else {
      char post_char;
      if (newsgrp.grp_flags & NEWS_M_MAILMODERATE) post_char = 'm';
      else if (!cxt[stm].canpost) post_char = 'n';
      else post_char = 'y';
      sprintf(sbuf,"%.*s %d %d %c\r\n",
              GRPNAMELEN_F,newsgrp.grp_name,
              newsgrp.grp_topnum, newsgrp.grp_firstnum, post_char);
      }
    if ( wild_match(util_grp_name_str(newsgrp.grp_name),list_grp) )
      write_net(sbuf,stm);
    }
  write_net(msg[EOM],stm);
}

static void newgroups(argc,argv,stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  unsigned int l_key[2];
  int first_num,
      filt_date,
      distcount = 0;
  char post_char;
  struct il *tmp;

  if ((argc < 3) || ((filt_date = cvt_date_str(argv[1], argv[2])) < 0))
    { write_net(msg[M501],stm); return; }
  argc -= 3;
  argv += 3;
  if (argc) lower_case(*argv);
  if (argc  && !strcmp(*argv,"gmt")) {
    filt_date += gmt_offset;
    --argc;
    ++argv;
    }
  if (argc) lower_case(*argv);
  if (argc) distcount = get_distlist(&itm_list, *argv);
  if (distcount < 0) { write_net(msg[M501],stm); return; }

  grprab.rab$b_krf = 0;
  grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
  grprab.rab$b_rac = RAB$C_SEQ;

  itmrab.rab$l_kbf = (char *) l_key;
  itmrab.rab$b_ksz = 8;
  itmrab.rab$b_krf = 0;
  itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
  itmrab.rab$b_rac = RAB$C_KEY;

  write_net(msg[M231],stm);

  sys_rewind(&grprab);
  while (sys_get_noeof(&grprab)) {
    char *gn = util_grp_name_str(newsgrp.grp_name);
    if ( !newsgrp.grp_num ||
         (newsgrp.grp_credate < filt_date) ||
         !check_ngperm(gn,stm) ||
         (distcount && !loc_grp(gn,itm_list)))
      continue;
    l_key[0] = 0;
    l_key[1] = newsgrp.grp_num;
    if (sys_get_nornf(&itmrab) && (newsitm.itm_grp == newsgrp.grp_num))
      first_num = newsitm.itm_num;
    else first_num = newsgrp.grp_topnum + 1;
    if (newsgrp.grp_flags & NEWS_M_MAILMODERATE) post_char = 'm';
    else if (!cxt[stm].canpost) post_char = 'n';
    else post_char = 'y';

    sprintf(sbuf,"%.*s %d %d %c\r\n",
            GRPNAMELEN_F,newsgrp.grp_name,
            newsgrp.grp_topnum, first_num, post_char);
    write_net(sbuf,stm);
    }
  write_net(msg[EOM],stm);
  while (itm_list) {
    tmp = itm_list->next;
    news_free(itm_list);
    itm_list = tmp;
    }
}

static void newnews(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  char *cp1, *cp2,
       last_id[IDLEN];
  int filt_date,
      distcount = 0;
  struct il *tmp;
  unsigned int i_key[2];

  if ((argc < 4) || ((filt_date = cvt_date_str(argv[2], argv[3])) < 0))
    { write_net(msg[M501],stm); return; }
  if (!cxt[stm].canread) { write_net(msg[M502],stm); return; }
  ngp_list = 0;
  lower_case(argv[1]);
  for (cp1=argv[1]; cp1; cp1=cp2) {
    if ( (cp2 = strchr(cp1,',')) ) *cp2++ = '\0';
    add_list(cp1,&ngp_list);
    }
  argc -= 4;
  argv += 4;
  if (argc) lower_case(*argv);
  if (argc  && !strcmp(*argv,"gmt")) {
    filt_date += gmt_offset;
    --argc;
    ++argv;
    }
  if (argc) lower_case(*argv);
  if (argc) distcount = get_distlist(&itm_list, *argv);
  if (distcount < 0) {
    while (ngp_list) {
      tmp = ngp_list->next;
      news_free(ngp_list);
      ngp_list = tmp;
      }
    write_net(msg[M501],stm); return;
    }

  grpnum_list = 0;
  convert_filter(ngp_list,itm_list,stm);

  write_net(msg[M230],stm);

  if (gl_all) {
    itmrab.rab$b_krf = 1;
    itmrab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    itmrab.rab$b_rac = RAB$C_SEQ;

    *last_id = '\0';
    sys_rewind(&itmrab);
    while (sys_get_noeof(&itmrab)) {
      if ((!newsitm.itm_cachedate) || (newsitm.itm_recvdate < filt_date) || (!strcmp(newsitm.itm_id,last_id)) || (itm_list && !check_dist(itm_list))) continue;
      sprintf(sbuf,"%.512s\r\n",newsitm.itm_id);
      write_net(sbuf,stm);
      util_idcpy(last_id,newsitm.itm_id);
      }
    }
  else {
    struct gl *gtmp;

    itmrab.rab$l_kbf = (char *) i_key;
    itmrab.rab$b_ksz = 8;
    itmrab.rab$b_krf = 0;
    itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
    for (gtmp=grpnum_list; gtmp; gtmp=gtmp->gl_next) {
      i_key[0] = 0;
      i_key[1] = gtmp->gl_index;
      itmrab.rab$b_rac = RAB$C_KEY;
      while (sys_get_nornf(&itmrab)) {
        if (newsitm.itm_grp != gtmp->gl_index) break;
        itmrab.rab$b_rac = RAB$C_SEQ;

        if ((!newsitm.itm_cachedate) || (newsitm.itm_recvdate < filt_date) || (itm_list && !check_dist(itm_list))) continue;
        sprintf(sbuf,"%.512s\r\n",newsitm.itm_id);
        write_net(sbuf,stm);
        }
      }
    }
  write_net(msg[EOM],stm);
  while (itm_list) {
    tmp = itm_list->next;
    news_free(itm_list);
    itm_list = tmp;
    }
  while (ngp_list) {
    tmp = ngp_list->next;
    news_free(ngp_list);
    ngp_list = tmp;
    }
  while (grpnum_list) {
    struct gl *next = grpnum_list->gl_next;
    news_free(grpnum_list);
    grpnum_list = next;
    }
}

/*
 *  next
 *  last
 */

static void nextlast(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  int next;
  unsigned int l_key[2];
  ITM savitm;

  if (!cxt[stm].canread) { write_net(msg[M502],stm); return; }
  if (argc != 1) { write_net(msg[M501],stm); return; }
  if (!cxt[stm].curr_group) { write_net(msg[M412],stm); return; }
  if (!cxt[stm].curr_item) { write_net(msg[M420],stm); return; }
  l_key[1] = cxt[stm].curr_group;
  itmrab.rab$l_kbf = (char *) l_key;
  itmrab.rab$b_ksz = 8;
  itmrab.rab$b_krf = 0;
  itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
  itmrab.rab$b_rac = RAB$C_KEY;

  if ( (next = (*argv[0]=='n')) ) {
    l_key[0] = cxt[stm].curr_item + 1;
    if (!sys_get_nornf(&itmrab) || (newsitm.itm_grp != cxt[stm].curr_group))
      { write_net(msg[M421],stm); return; }
    }
  else {
    l_key[0] = 0;                       /* last command */
    if (!sys_get_nornf(&itmrab) || (newsitm.itm_grp != cxt[stm].curr_group) || (newsitm.itm_num >= cxt[stm].curr_item))
      { write_net(msg[M422],stm); return; }
    itmrab.rab$b_rac = RAB$C_SEQ;
    do {
      savitm = newsitm;
      if (!sys_get_noeof(&itmrab)) break;
      if (newsitm.itm_grp != cxt[stm].curr_group) break;
      } while (newsitm.itm_num < cxt[stm].curr_item);
    newsitm = savitm;
    }
  cxt[stm].curr_item = newsitm.itm_num;
  sprintf(sbuf,msg[M223],newsitm.itm_num,newsitm.itm_id);
  write_net(sbuf,stm);
}

/*
 *  post
 */

static void post_2(int);

static void post(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  if (argc > 1) { write_net(msg[M501],stm); return; }
  if (!cxt[stm].canpost) { write_net(msg[M502],stm); return; }
  if (diskdanger()) { c$free_tmp(); write_net(msg[M502D],stm); return; }
  write_net(msg[M340],stm);
  next_call(stm,post_2,FILE_INPUT);
}

static void post_2(stm)
  int stm;
{
  char msgbuf[BUFF_STRLEN], *cp;
  int mdx, len;
  FILE *fpl = 0;

  next_call(stm,parser,CMD_INPUT);
  artptr = artbuf;
  *artptr = '\0';
  *msgbuf = '\0';
  while ( (len = read_net(ibuf,sizeof(ibuf),stm)) ) {
    if (*ibuf == '.') {
      cp = ibuf + 1;
      len--;
      if (*cp == '\n') break;
      }
    else cp = ibuf;
    if (!*msgbuf) fpl = append_line(cp,len,fpl,msgbuf,stm);
    if (*(cp + len - 1) != '\n') {		/* handle really long lines */
      while ( (len = read_net(ibuf,sizeof(ibuf),stm)) ) {
        if (!*msgbuf) fpl = append_line(ibuf,len,fpl,msgbuf,stm);
        if (*(cp + len - 1) == '\n') break;
        }
      }
    }
  if (!*msgbuf && (fpl || *artbuf)) {
    if (close_file(fpl,artbuf,POST,msgbuf,stm)) {
      mdx = 240;
      cxt[stm].posted++;
      }
    else {
      mdx = 441;
      cxt[stm].failed++;
      }
    sprintf(sbuf,"%d %.512s\r\n",mdx, msgbuf);
    write_net(sbuf,stm);
    }
  else {
    if (*msgbuf) {
      sprintf(sbuf,"441 %.512s",msgbuf);
      write_net(sbuf,stm);
      }
    else write_net(msg[M441],stm);
    }
  c$free_tmp();
}

/*
 *  quit_cmd
 */

static void quit(argc,argv,stm)
  int argc;
  char *argv[];
  int stm;
{
  write_net(msg[M205],stm);
  if (leave_spool_open) {
    if (fpb) {
      if (fclose(fpb)) _ck_close(fpb);
      rename(batchname_tmp,batchname);
      fpb = 0;
      }
    }
  next_call(stm,CLOSE_LINK,NO_INPUT);
}

/*
 *  slave_cmd
 */

static void slave(argc, argv, stm)
  int argc;
  const char *argv[];
  int stm;
{
  write_net(msg[M202],stm);
}

/*
 *  xupdgroup
 *
 *  Used by remote VMS NEWS client to update served news item database entries.
 */

static void xupd_2(int);

static void xupdgroup(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  char l_g[GRPNAMELEN];

  if (argc < 2 || argc > 5) { write_net(msg[M501],stm); return; }
  if (!cxt[stm].canread) { write_net(msg[M502],stm); return; }
  lower_case(argv[1]);
  util_cvrt(l_g,argv[1]);

  if (argc == 2) cxt[stm].cfilt_date = 0;
  else if (argc == 3) cxt[stm].cfilt_date = cvt_date_str(argv[2],"000000");
  else cxt[stm].cfilt_date = cvt_date_str(argv[2],argv[3]);
  if (cxt[stm].cfilt_date < 0) { write_net(msg[M501],stm); return; }
  if (argc == 5) lower_case(argv[4]);
  if (argc == 5 && !strcmp(argv[4],"gmt"))
    cxt[stm].cfilt_date += gmt_offset;

  grprab.rab$l_kbf = l_g;
  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;

  if (!sys_get_nornf(&grprab)) { write_net(msg[M480],stm); return; }
  if (!check_ngperm(util_grp_name_str(newsgrp.grp_name),stm))
    { write_net(msg[M502],stm); return; }
  cxt[stm].cgrp = newsgrp.grp_num;
  write_net(msg[M380],stm);
  next_call(stm,xupd_2,FILE_INPUT);
}

static void xupd_2(stm)
  int stm;
{
  int status;
  unsigned int i_key[2], cpl;
  struct il *tmp;

  next_call(stm,parser,CMD_INPUT);
  itm_list = 0;
  for (;;) {
    if ( (cpl = read_net(ibuf,sizeof(ibuf),stm)) ) {
      if (strcmp(ibuf,".\n")) {
        ibuf[--cpl] = '\0';
        add_list(ibuf,&itm_list);
        }
      else break;
      }
    }
  write_net(msg[M280],stm);
  i_key[0] = 0;
  i_key[1] = cxt[stm].cgrp;
  itmrab.rab$l_kbf = (char *) i_key;
  itmrab.rab$b_ksz = 8;
  itmrab.rab$b_krf = 0;
  itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
  itmrab.rab$b_rac = RAB$C_KEY;

  while (sys_get_nornf(&itmrab)) {
    itmrab.rab$b_rac = RAB$C_SEQ;
    if (newsitm.itm_grp != newsgrp.grp_num) break;
    if (newsitm.itm_recvdate < cxt[stm].cfilt_date) continue;
    if (!loc_id(newsitm.itm_id)) {
      sprintf(sbuf,"%.512s %d %d %.512s\r\n",newsitm.itm_id,newsitm.itm_recvdate,newsitm.itm_lines,newsitm.itm_title);
      write_net(sbuf,stm);
      }
    }
  while (itm_list) {
    tmp = itm_list->next;
    sprintf(sbuf,"D %.512s\r\n",itm_list->id);
    write_net(sbuf,stm);
    news_free(itm_list);
    itm_list = tmp;
    }
  write_net(msg[EOM],stm);
}

static void xgtitle(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  int wild, rcount = 0, scount = 0;
  char *cp1, *cp2;
  struct il *tmp;

  if (argc > 2) { write_net(msg[M501],stm); return; }
  if (argc < 2) {
    grprab.rab$l_kbf = (char *) &(cxt[stm].curr_group);
    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;
    if (!sys_get_nornf(&grprab)) { write_net(msg[M480],stm); return; }
    if (!newsgrp.grp_topic[0]) { write_net(msg[M481],stm); return; }
    write_net(msg[M282],stm);
    sprintf(sbuf,"%.*s %.512s\r\n",
            GRPNAMELEN_F,newsgrp.grp_name, newsgrp.grp_topic);
    write_net(sbuf,stm);
    write_net(msg[EOM],stm); return;
    }

  wild = (strchr(argv[1],'*') || strchr(argv[1],'?') || strchr(argv[1],'%'));
  lower_case(argv[1]);
  ngp_list = 0;
  for (cp1=argv[1]; cp1; cp1=cp2) {
    if ( (cp2 = strchr(cp1,',')) ) *cp2++ = '\0';
    add_list(cp1,&ngp_list);
    }

  if (wild) {
    grprab.rab$b_krf = 0;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_SEQ;
    sys_rewind(&grprab);
    while (sys_get_noeof(&grprab)) {
      for (tmp=ngp_list; tmp; tmp=tmp->next) {
        if (wild_match(util_grp_name_str(newsgrp.grp_name),tmp->id)) {
          ++scount;
          if (newsgrp.grp_topic[0]) {
            if (!rcount++) write_net(msg[M282],stm);
            sprintf(sbuf,"%.*s %.512s\r\n",
                    GRPNAMELEN_F,newsgrp.grp_name, newsgrp.grp_topic);
            write_net(sbuf,stm);
            }
          break;
          }
        }
      }
    while (ngp_list) {
      tmp = ngp_list->next;
      news_free(ngp_list);
      ngp_list = tmp;
      }
    if (rcount) { write_net(msg[EOM],stm); return; }
    if (scount) { write_net(msg[M481],stm); return; }
    write_net(msg[M480],stm); return;
    }

  while (ngp_list) {
    if (getgroupbyname(ngp_list->id)) {
      ++scount;
      if (newsgrp.grp_topic[0]) {
        if (!rcount++) write_net(msg[M282],stm);
        sprintf(sbuf,"%.*s %.512s\r\n",
                GRPNAMELEN_F,newsgrp.grp_name,newsgrp.grp_topic);
        write_net(sbuf,stm);
        }
      }
    tmp = ngp_list->next;
    news_free(ngp_list);
    ngp_list = tmp;
    }
  if (rcount) { write_net(msg[EOM],stm); return; }
  if (scount) { write_net(msg[M481],stm); return; }
  write_net(msg[M480],stm);
}

static void xgnotice(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  int wild, rcount = 0, scount = 0;
  char *cp1, *cp2;
  struct il *tmp = NULL;

  if (argc > 2) { write_net(msg[M501],stm); return; }
  if (argc < 2) {
    grprab.rab$l_kbf = (char *) &(cxt[stm].curr_group);
    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;
    if (!sys_get_nornf(&grprab)) { write_net(msg[M480],stm); return; }
    if (!newsgrp.grp_notice[0]) { write_net(msg[M481],stm); return; }
    write_net(msg[M282],stm);
    sprintf(sbuf,"%.*s %.512s\r\n",
            GRPNAMELEN_F,newsgrp.grp_name,newsgrp.grp_notice);
    write_net(sbuf,stm);
    write_net(msg[EOM],stm); return;
    }

  wild = (strchr(argv[1],'*') || strchr(argv[1],'?') || strchr(argv[1],'%'));
  lower_case(argv[1]);
  ngp_list = 0;
  for (cp1=argv[1]; cp1; cp1=cp2) {
    if ( (cp2 = strchr(cp1,',')) ) *cp2++ = '\0';
    add_list(cp1,&ngp_list);
    }

  if (wild) {
    grprab.rab$b_krf = 0;
    grprab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
    grprab.rab$b_rac = RAB$C_SEQ;
    sys_rewind(&grprab);
    while (sys_get_noeof(&grprab)) {
      for (tmp=ngp_list; tmp; tmp=tmp->next) {
        if (wild_match(util_grp_name_str(newsgrp.grp_name),tmp->id)) {
          ++scount;
          if (newsgrp.grp_notice[0]) {
            if (!rcount++) write_net(msg[M282],stm);
            sprintf(sbuf,"%.*s %.512s\r\n",
                    GRPNAMELEN_F,newsgrp.grp_name,newsgrp.grp_notice);
            write_net(sbuf,stm);
            }
          break;
          }
        }
      }
    while (ngp_list) {
      tmp = ngp_list->next;
      news_free(ngp_list);
      ngp_list = tmp;
      }
    if (rcount) { write_net(msg[EOM],stm); return; }
    if (scount) { write_net(msg[M481],stm); return; }
    write_net(msg[M480],stm); return;
    }

  while (ngp_list) {
    if (getgroupbyname(ngp_list->id)) {
      ++scount;
      if (newsgrp.grp_notice[0]) {
        if (!rcount++) write_net(msg[M282],stm);
        sprintf(sbuf,"%.*s %.512s\r\n",
                GRPNAMELEN_F,newsgrp.grp_name,newsgrp.grp_notice);
        write_net(sbuf,stm);
        }
      }
    tmp = ngp_list->next;
    news_free(ngp_list);
    ngp_list = tmp;
    }
  if (rcount) { write_net(msg[EOM],stm); return; }
  if (scount) { write_net(msg[M481],stm); return; }
  write_net(msg[M480],stm);
}

static void xhdr(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  int status;
  FILE *fp;
  int high, low, sl = 0;
  char buf[10], *cp;

  if (argc < 2 || argc > 3) { write_net(msg[M501],stm); return; }
  if (!cxt[stm].canread) { write_net(msg[M502],stm); return; }
  lower_case(argv[1]);
  if (   !strcmp(argv[1],"subject")
      || !strcmp(argv[1],"from")
      || !strcmp(argv[1],"lines")
      || !strcmp(argv[1],"postmark-local")
      || !strcmp(argv[1],"message-id")) sl = 1;
  if (argc == 3 && *argv[2] == '<') {
    int mdx;

    if (!(fp = openartbyid(argv[2],&mdx,stm,sl)))
      { write_net(msg[mdx],stm); return; }

    sprintf(sbuf,"221 %d %.512s header %.512s of article follows.\r\n",
                  newsitm.itm_num,argv[1],newsitm.itm_id);
    write_net(sbuf,stm);
    if (sl) {
      if ((*argv[1] == 'f') && newsitm.itm_from[0])
        sprintf(sbuf,"%.512s %.512s\r\n", argv[2], newsitm.itm_from);
      else if (*argv[1] == 'f') sl = 0;
      else if (*argv[1] == 's')
        sprintf(sbuf,"%.512s %.512s\r\n", argv[2], newsitm.itm_title);
      else if (*argv[1] == 'm')
        sprintf(sbuf,"%.512s %.512s\r\n", argv[2], newsitm.itm_id);
      else if (*argv[1] == 'p')
        sprintf(sbuf,"%.512s %d\r\n", argv[2], newsitm.itm_recvdate);
      else
        sprintf(sbuf,"%.512s %d\r\n", argv[2], newsitm.itm_lines);
      if (sl) write_net(sbuf,stm);
      }
    if (!sl) print_header(fp, argv[1], argv[2], stm);
    write_net(msg[EOM],stm);
    if (fclose(fp)) _ck_close(fp);
    return;
    }
  if (!cxt[stm].curr_group) { write_net(msg[M412],stm); return; }
  if (argc == 2) {
    if (!cxt[stm].curr_item) { write_net(msg[M420],stm); return; }
    high = low = cxt[stm].curr_item;
    }
  else {
    if (!(cp = strchr(argv[2], '-'))) low = high = atoi(argv[2]);
    else {
      *cp++ = '\0';
      low = atoi(argv[2]);
      high = atoi(cp);
      if (high < low) high = 0;
      *--cp = '-';
      }
    if (!low && !high) { write_net(msg[M501],stm); return; }
    }
  sprintf(sbuf,"221 %.512s fields follow\r\n", argv[1]);
  write_net(sbuf,stm);
  low = start_seq(low,stm);
  while (low && (!high || (low <= high))) {
    int isl = sl;

    if (isl) {
      if ((*argv[1] == 'f') && newsitm.itm_from[0])
        sprintf(sbuf,"%d %.512s\r\n", low, newsitm.itm_from);
      else if (*argv[1] == 'f') isl = 0;
      else if (*argv[1] == 's')
        sprintf(sbuf,"%d %.512s\r\n", low, newsitm.itm_title);
      else if (*argv[1] == 'm')
        sprintf(sbuf,"%d %.512s\r\n", low, newsitm.itm_id);
      else if (*argv[1] == 'p')
        sprintf(sbuf,"%d %d\r\n", low, newsitm.itm_recvdate);
      else
        sprintf(sbuf,"%d %d\r\n", low, newsitm.itm_lines);
      if (isl) write_net(sbuf,stm);
      }
    if (!isl) {
      sprintf(itm_fname,Itm_template,util_dir(newsgrp.grp_name),newsitm.itm_num);
      sprintf(buf,"%d",low);
      if ( (fp = fopen(itm_fname,OPEN_READ_OPT0)) ) {
        print_header(fp,argv[1],buf,stm);
        if (fclose(fp)) _ck_close(fp);
        }
      }
    if (sys_get_nornf(&itmrab)) {
      if (newsitm.itm_grp != newsgrp.grp_num) low = 0;
      else low = newsitm.itm_num;
      }
    else low = 0;
    }
  write_net(msg[EOM],stm);
}
#ifdef XOVER


static void xover(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  FILE *fp, *ofp;
  /*FILE *xfp;  /* Alan XOVER log */
  int high, low;
  int len;
  char ovr_name[FILE_STRLEN];
  char ovr_buf[4096];
  int ovr_buf_len =4095;
  char *buf, *cp;
  char *get_head();
  char *subjh=0,*fromh=0,*dateh=0,*msgidh=0,*refrh=0,byteh[10],*lineh=0;
  struct stat stat_buffer;

  if (argc > 2)             { write_net(msg[M501],stm); return; }
  if (!cxt[stm].canread)    { write_net(msg[M502],stm); return; }
  if (!cxt[stm].curr_group) { write_net(msg[M412],stm); return; }

  if (argc == 1) {
    if (!cxt[stm].curr_item) { write_net(msg[M420],stm); return; }
    high = low = cxt[stm].curr_item;
    }
  else {
    if (!(cp = strchr(argv[1], '-'))) low = high = atoi(argv[1]);
    else {
      *cp++ = '\0';
      low = atoi(argv[1]);
      if ( *cp ) high = atoi(cp);
      else       high = cxt[stm].high_item;
      if (high < low) high = low;
      *--cp = '-';
      }
    if (!low && !high) { write_net(msg[M501],stm); return; }
    }

/* Alan -- log usage */

/* Or don't 

  xfp = fopen("news_manager:xover_usage.log",OPEN_APPEND_OPT0);
  if (xfp != NULL)
    fprintf(xfp,"XOVER %s: %s %d-%d %s ",cxt[stm].rhst,cxt[stm].curr_grpnam,
          low,high,time_str());
  printf("XOVER %s: %s %d-%d %s\n ",cxt[stm].rhst,cxt[stm].curr_grpnam,
          low,high,time_str());
*/
  sprintf(sbuf,"224 data follows\r\n");
  write_net(sbuf,stm);
  len = 1024; 
  buf = news_malloc(len+1); /* buffer for reading headers MUST be malloced */

/* Alan - Read in overview file */
  if  (ovr && strcmp(ovr_groupname,cxt[stm].curr_grpnam)) /* Same newsgroup? */
   {
/*
    printf("xover: Clearing cached overview old: %s, new: %s\n",
           ovr_groupname,cxt[stm].curr_grpnam);
*/
    ovr_free(); 	/* No -- cache not valid */
    }

  if (!ovr)
    {
/*
    printf("xover: reading overview data\n");
*/
    index_first = start_seq(1,stm);
    index_last =  newsgrp.grp_topnum;
    ovr = (char **) news_malloc(sizeof(ovr) * (1+ index_last - index_first));
    for (item = index_first; item <= index_last;item++) {
      ovr[item - index_first] = 0;
    }
    ovr_start = ovr_highest = ovr_ostart = ovr_ohighest = 0;
    ovr_modified = ovr_append = FALSE;
    sprintf(ovr_name,"NEWS_DEVICE_OVERVIEW:[%.200s]overview.nntp",
            util_dir(cxt[stm].curr_grpnam));
    strcpy(ovr_groupname,cxt[stm].curr_grpnam);
#ifdef XOVER_SAVE_TO_DISK
/*    printf("XOPEN: Opening %s ",ovr_name); */
    ofp = fopen(ovr_name,OPEN_READ_OPT1);
#else
    ofp = NULL;
#endif
    if (ofp == NULL) {
#ifdef XOVER_SAVE_TO_DISK
/*      printf(" - failed\n"); */
#endif
      ovr_modified = TRUE;
    }
    else {
/*      printf(" - OK\n"); */
      while (fgets(ovr_buf, ovr_buf_len, ofp) != NULL) {
        item = atoi(ovr_buf);
        if (item >= index_first && item <= index_last && !ovr[item -index_first]) {
	  ovr[item - index_first] = news_strdup(ovr_buf,-1);
	  strcpy(ovr[item - index_first], ovr_buf);
	  if (!ovr_start)
	    ovr_start = item;
	  if (item > ovr_highest)
	    ovr_highest = item;
        }
        else {
/*	  printf("XOVER: Ignoring item: %d, group %s\n",
		  item,cxt[stm].curr_grpnam);  
          printf("xover: modified because of duplicate in overview\n"); */
	  ovr_modified = TRUE;
        }
      } /* While */
      ovr_ostart = ovr_start;
      ovr_ohighest = ovr_highest;
      fclose(ofp);
      if (ovr_highest == 0)
        {
        delete(ovr_name);
/*        printf("xover: modified because of zero length overview\n"); */
        ovr_modified = TRUE;
        }
    }
  }

/* Now have array of overview info (or blank array...) */
  previous = 0;
  for (low=start_seq(low,stm); low>0 && low<=high; low=start_seq(low+1,stm)) {
    if ((previous > 0) && ((low - previous) != 1)) /* Cancel or skim */
      for (item=previous+1; item<low; item++) {
        if (ovr[item - index_first]) {
/*          printf("XOVER: Delete overview %d\n",item);    */
          news_free(ovr[item - index_first]);
          ovr[item - index_first] = 0;
	  ovr_modified = TRUE;
/*          printf("xover: modified because not in database\n"); */
          }
	}
    previous = low;
    if (ovr[low - index_first]) {
      write_net(ovr[low - index_first],stm);
/*      printf("XOVER: Item %d from overview\n",low); */
      continue;
    }
/*    printf("XOVER: Item %d from file\n",low); */
    sprintf(itm_fname,Itm_template,util_dir(cxt[stm].curr_grpnam),low);
    if ( (fp = fopen(itm_fname,"r","mbc=4","mbf=1")) ) { 
      while(get_head(&buf,&len,fp)) {
        if ( (cp = strchr(buf,':')) != 0 ) *cp++ = '\0';
        else break;  /* no ":" in line, not a header RRS */
	cp++; /* Alan - fix */
        if (!strcmp(buf,"Subject"))    subjh = news_strdup(cp,-1);
        if (!strcmp(buf,"Message-ID")) msgidh = news_strdup(cp,-1);
        if (!strcmp(buf,"From"))       fromh = news_strdup(cp,-1);
	if (!strcmp(buf,"Date"))       dateh = news_strdup(cp,-1);
	if (!strcmp(buf,"References")) refrh = news_strdup(cp,-1);
	if (!strcmp(buf,"Lines"))      lineh = news_strdup(cp,-1);
	}
      fstat(fileno(fp), &stat_buffer);  /* get number of bytes */
      sprintf(byteh,"%lu",stat_buffer.st_size);
      if (fclose(fp)) _ck_close(fp);
/*
 *    count as one article read
 */
      cxt[stm].arts_read++;
/*
 * all information is now collected, send it to the client
 */
      if (low > ovr_highest) {
	ovr_append = TRUE;
	ovr_highest = item;
/*        printf("xover: append because not in file\n"); */
      }
      else {
	ovr_modified = TRUE;
/*        printf("xover: modified because of not in overview\n"); */
	}
      sprintf(buf,"%d\t",low);
      write_net(buf,stm);
      ovr_len = 20;	/* account for tabs/newlines etc */
      if (subjh) ovr_len = ovr_len + strlen(subjh);
      if (fromh) ovr_len = ovr_len + strlen(fromh);
      if (dateh) ovr_len = ovr_len + strlen(dateh);
      if (msgidh) ovr_len = ovr_len + strlen(msgidh);
      if (refrh) ovr_len = ovr_len + strlen(refrh);
      if (byteh) ovr_len = ovr_len + strlen(byteh);
      if (lineh) ovr_len = ovr_len + strlen(lineh);

      ovr[low - index_first] = news_malloc(ovr_len);
      sprintf(ovr[low - index_first],buf);
      if (subjh) {
        while ( (cp=strchr(subjh,'\t')) != 0 ) *cp=' ';
        write_net(subjh,stm);
	strcat(ovr[low - index_first],subjh);
        news_free(subjh);
        subjh = 0;
	}
      write_net("\t",stm);
      strcat(ovr[low - index_first],"\t");
      if (fromh) {
        while ( (cp=strchr(fromh,'\t')) != 0 ) *cp=' ';
        write_net(fromh,stm);
	strcat(ovr[low - index_first],fromh);
        news_free(fromh);
	fromh = 0;
	}
      write_net("\t",stm);
      strcat(ovr[low - index_first],"\t");
      if (dateh) {
        while ( (cp=strchr(dateh,'\t')) != 0 ) *cp=' ';
        write_net(dateh,stm);
	strcat(ovr[low - index_first],dateh);
        news_free(dateh);
	dateh = 0;
	}
      write_net("\t",stm);
      strcat(ovr[low - index_first],"\t");
      if (msgidh) {
        while ( (cp=strchr(msgidh,'\t')) != 0 ) *cp=' ';
        write_net(msgidh,stm);
	strcat(ovr[low - index_first], msgidh);
        news_free(msgidh);
	msgidh = 0;
	}
      write_net("\t",stm);
      strcat(ovr[low - index_first],"\t");
      if (refrh) {
        while ( (cp=strchr(refrh,'\t')) != 0 ) *cp=' ';
        write_net(refrh,stm);
	strcat(ovr[low - index_first],refrh);
        news_free(refrh);
	refrh = 0;
	}
      write_net("\t",stm);
      strcat(ovr[low - index_first],"\t");
      write_net(byteh,stm);
      strcat(ovr[low - index_first],byteh);
      write_net("\t",stm);
      strcat(ovr[low - index_first],"\t");
      if (lineh) {
        while ( (cp=strchr(lineh,'\t')) != 0 ) *cp=' ';
        write_net(lineh,stm);
	strcat(ovr[low - index_first],lineh);
        news_free(lineh);
	lineh = 0;
	}
      write_net("\r\n",stm);
      strcat(ovr[low - index_first],"\r\n");
      }
  }
  news_free(buf);
  write_net(msg[EOM],stm);

/* Write out new overviewfile if needed */

#ifdef XOVER_SAVE_TO_DISK
  if (ovr_modified) {
/*    printf("XOVER: Writing new overview "); */
    delete(ovr_name);
    ofp = fopen(ovr_name,OPEN_WRITE_OPT1);
    if (ofp == NULL) {
/*      printf(" - failed\n"); */
      }
    else {
      for (item=index_first;item<=index_last;item++) {
        if (ovr[item - index_first]) {
/*	  printf("XOVER: Write %d\n",item); */
	  fputs(ovr[item - index_first],ofp);
	  }
        }
      fclose(ofp);
/*      printf(" - ok\n"); */
      }
    }
  else if (ovr_append) {
/*    printf("XOVER: Appending new overview "); */
    ofp= fopen(ovr_name,OPEN_APPEND_OPT0);
    if (ofp == NULL) {
/*      printf(" - failed\n"); */
      }
    else {
      for (item=ovr_ohighest+1;item<=index_last;item++) {
        if (ovr[item - index_first]) {
/*	  printf("XOVER: Append %d\n",item); */ 
	  fputs(ovr[item - index_first],ofp);
/*          printf("ok\n"); */
	  }
        }
      fclose(ofp);
/*      printf(" - ok\n"); */
      }
    }
  
#endif

  ovr_ostart=ovr_start;
  ovr_ohighest = ovr_highest = index_last;
  ovr_modified = ovr_append = FALSE;

/* Alan XOVER log */
/*
  if (xfp != NULL) {
    fprintf(xfp," - %s\n",time_str());
    fclose(xfp);
    xfp = NULL;
    }
*/
}


/*
 *    get_head: get one header line from the input file. Continuation lines
 *    are concatenated into one long line. Memory allocated as needed. It is
 *    assumed that "line" was allocated by news_malloc to start off with.
 */
char *get_head( char **line, int *len, FILE *fd ) {
    int ch;
    char cline[1025];
    char *cp;
    extern char *mygets();
/*
 *    get the next header line from the input file.
 */
    if ( !mygets(*line,*len,fd) ) {
	return 0;                  /* shouldn't get EOF here */
    }
/*
 *    if this line is null, it marks the end of the headers.
 */
    if ( **line == '\n' ) return 0;   /* return false if no more headers */
/*
 * see if the line has a newline, and if so, delete it.
 */
    if ( (cp=strchr(*line,'\n')) != 0 ) *cp = '\0';
/*
 *    if the next input line starts with a blank, it is a continuation of this
 *    line, so concatenate it onto the end of this line.
 */
    while ( ungetc(ch=fgetc(fd),fd) == ' ' || ch == '\t' ) {
	if ( !mygets(cline,1024,fd) )
	    return *line;     /* this shouldn't happen; return what we have */
	if ( ( strlen(*line) + strlen(cline) + 1 ) > *len ) {
	    *len += strlen(cline) + 1;
	    *line = news_realloc(*line, *len);
	}
	strcat(*line,cline);
	if ( (cp=strchr(*line,'\n')) != 0 ) *cp = '\0';
    }
    return *line;
}
/*
 *    You may have been wondering why I used mygets rather than the standard
 *    C library routine gets.  I started out that way, but there seems to be
 *    a problem in the C library when using ungetc, getc, and gets together
 *    on the same file.  It was easier to just write my own than try to find
 *    out what the problem was with DEC's routine.
 */
char *mygets( char *line, int maxi, FILE *fd ) {
    int i,ch;
/*
 *    read up to maxi characters from the input file
 */
    for ( i=0; i<maxi-1; ++i ) {
/*
 *    if there are no more characters, exit
 */
	if ( (ch=getc(fd)) == EOF ) {
	    line[i] = '\0';
	    return 0;
	}
/*
 *    stuff the current character in the users line buffer
 */
	line[i] = ch;
/*
 *    if this is a newline, then we are done for now, so return.
 */
	if ( ch == '\n' ) {
	    line[i+1] = '\0';          /* put EOS on the end of the string */
	    return line;
	}
    }
/*
 *    if we get here, the line was longer than the users buffer.
 *    put and EOS on the string, and return.
 */
    line[maxi] = '\0';
    return line;
}
#endif

/*
 *  xconn
 *
 *  Reply with connection information about the remote client.
 */

static void xconn(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  if (argc > 1) { write_net(msg[M501],stm); return; }
  sprintf(sbuf,msg[M281],cxt[stm].rusr,cxt[stm].rhst);
  write_net(sbuf,stm);
}
/*
 *  date
 *
 *  Reply with the date in ISO3307 format.
 */

static void date_cmd(argc, argv, stm)
  int argc;
  char *argv[];
  int stm;
{
  time_t ctime;
  struct tm *tstm;

  if (argc > 1) { write_net(msg[M501],stm); return; }
  time(&ctime);
  ctime -= gmt_offset;
  tstm = localtime(&ctime);
  sprintf(sbuf,"111 %04d%02d%02d%02d%02d%02d\r\n",
    (tstm->tm_year)+1900, (tstm->tm_mon)+1, tstm->tm_mday,
    tstm->tm_hour, tstm->tm_min, tstm->tm_sec);
  write_net(sbuf,stm);
}

/*
 *  argparse
 *
 *  Break up a line into an array of words
 */

static int argparse(buff, array)
  char *buff,
       ***array;
{
  char **argv,
       *cp = buff;
  int i, j, num_words = 0;

  if ( (argv = *array) ) {
    for (i = 0; argv[i]; i++) { news_free(argv[i]); argv[i] = NULL; }
    news_free(argv); argv = NULL;
    }

  while (*cp) {                       /* count words in input */
    for (; *cp == ' ' || *cp == '\t'; ++cp);
    if (!*cp) break;
    for (; *cp != ' ' && *cp != '\t' && *cp; ++cp);
    ++num_words;
    }
  if (!(argv = (char **) news_malloc((num_words + 1) * sizeof(char *))))
    return(fprintf(stderr, "argparse: malloc returned no space.\n"),0);

  for (j=0; *buff; j++) {                      /* Now build the list of words */
    for (; *buff == ' ' || *buff == '\t'; ++buff) {}
    if (!*buff) break;
    i = strcspn(buff," \t");
    argv[j] = news_strdup(buff,i);
    buff += i;
  }
  argv[j] = 0;
  *array = argv;
  return(j);
}

static
struct cmdent {
          const char *cmd_name;
          void (*cmd_fctn)(int, char **, int);
          } cmdtbl[] = {
                { "article",      ahbs },
                { "body",         ahbs },
                { "date",         date_cmd },
                { "group",        group },
                { "head",         ahbs },
                { "help",         help },
                { "ihave",        ihave },
                { "last",         nextlast },
                { "list",         list },
                { "newgroups",    newgroups },
                { "newnews",      newnews },
                { "next",         nextlast },
                { "post",         post },
                { "quit",         quit },
                { "slave",        slave },
                { "stat",         ahbs },
                { "xconn",        xconn },
                { "xhdr",         xhdr },
#ifdef XOVER
		{ "xover",        xover },
#endif
                { "xupdgroup",    xupdgroup },
                { "xgtitle",      xgtitle },
                { "xgnotice",     xgnotice }
                };

#define NUMCMDS (sizeof(cmdtbl) / sizeof(struct cmdent))

/*
 *  parser
 *
 *  Invoke the function associated with the command line.
 */

static
char **argp = 0;

static void parser(stm)
  int stm;
{
  char *cp;
  int argnum, i;

  next_call(stm, parser, CMD_INPUT);
  read_net(ibuf, sizeof(ibuf), stm);
  if (news_lock_alarm) {
    strcpy(sbuf,"400 NNTP service shutting down.\r\n");
    write_net(sbuf,stm);
    next_call(stm,CLOSE_LINK,NO_INPUT);
    return;
    }
  if ( (cp = strchr(ibuf, '\r')) ) *cp = '\0';
  else if ( (cp = strchr(ibuf, '\n')) ) *cp = '\0';
  strip_compress(ibuf);
  if (!(argnum = argparse(ibuf, &argp))) return;
  lower_case(argp[0]);
  for (i = 0; i < NUMCMDS; ++i)
    if (!strcmp(cmdtbl[i].cmd_name, argp[0]))
      { (*cmdtbl[i].cmd_fctn)(argnum,argp,stm); return; }
  write_net(msg[M500],stm);
}

/*
 *  server_init
 *
 *  Initialize the server.
 */

int server_init(max_stms)
  int max_stms;
{
  char *np;
  int i;
  unsigned short len;
  struct crelnm_record {
        short bl;
        short ic;
        const char *ba;
        unsigned short *rl;
        int enditm;
        } scritm;

  init_mem();

  if (!news_getenv("SYS$SCRATCH",0)) {
    scritm.ic = LNM$_STRING;
    scritm.ba = "NEWS_MANAGER";
    scritm.bl = strlen(scritm.ba);
    scritm.rl = &len;
    scritm.enditm = 0;
    c$cks(sys$crelnm(0,c$dsc("LNM$PROCESS"),c$dsc("SYS$SCRATCH"),0,&scritm));
    }


  if ( (np = news_getenv("NEWS_NNTP_SERVER_ALWAYS_SPOOL",0)) ) {
    if (isdigit(*np))
      always_spool = (1 & strtol(np, NULL, 0));
    else {
      lower_case(np);
      np[1] = '\0';
      always_spool = strspn(np, "ty");
      }
    }
  if ( (np = news_getenv("NEWS_NNTP_SERVER_LEAVE_SPOOL_OPEN",0)) ) {
    if (isdigit(*np))
      leave_spool_open = (1 & strtol(np, NULL, 0));
    else {
      lower_case(np);
      np[1] = '\0';
      leave_spool_open = strspn(np, "ty");
      }
    }

  if ( (np = news_getenv("NNTP_SCRATCH",0)) )
    strcpy(scratchfile,"NNTP_SCRATCH:NNTP_%X_%X.TMP");
  else if (!news_getenv("SYS$SCRATCH",0))
    strcpy(scratchfile,"NEWS_MANAGER:NNTP_%X_%X.TMP");
  else
    strcpy(scratchfile,"SYS$SCRATCH:NNTP_%X_%X.TMP");
  
  init_names();

  init_tz();

  if (!(cxt = (struct context *) news_malloc((max_stms + 1) * (sizeof *cxt)))) 
    return(fprintf(stderr,"server_init: malloc returned no space\n"),0);
  for (i = 0; i <= max_stms; ++i) cxt[i].grpv = 0;
  if (!init_lock()) return(open_server_files());
  return(1);
}

/*
 *  server_init_unit
 *
 *  Initialize a processing stream.
 */

void server_init_unit(stm)
  int stm;
{
  char localtime[TIME_STRLEN],
       gdbuf[BUFF_STRLEN],
       *cp;
  time_t ltime;

  time(&ltime);
  strnncpy(localtime,sizeof(localtime), ctime(&ltime),-1, 0);
  if ( (cp = strchr(localtime,'\n')) ) *cp = '\0';
  getremhost(cxt[stm].rhst,cxt[stm].rusr,stm);
  lower_case(cxt[stm].rhst);
  lower_case(cxt[stm].rusr);

  cxt[stm].accepted = cxt[stm].offered = cxt[stm].rejected = 0;
  cxt[stm].failed = cxt[stm].posted = cxt[stm].arts_read = cxt[stm].grps_read=0;
  cxt[stm].curr_group = cxt[stm].curr_item = 0;

  if (news_lock_alarm) {
    sprintf(sbuf,"502 %.100s VMS NNTP server version %.100s. NNTP service temporarily unavailable.\r\n",
                  news_node,nntp_version);
    write_net(sbuf,stm);
    next_call(stm,CLOSE_LINK,NO_INPUT);
    return;
    }
  host_access(&(cxt[stm].canread),&(cxt[stm].canpost),&(cxt[stm].canxfr),gdbuf,stm);
  if (!(cxt[stm].canread || cxt[stm].canxfr || cxt[stm].canpost)) {
    sprintf(sbuf,"502 %.100s VMS NNTP server version %.100s. Access denied for %.100s@%.100s.\r\n",
                  news_node,nntp_version,cxt[stm].rusr,cxt[stm].rhst);
    write_net(sbuf,stm);
    next_call(stm,CLOSE_LINK,NO_INPUT);
    return;
    }
  if (!cxt[stm].canread && !cxt[stm].canpost && diskdanger()) {
    sprintf(sbuf,"400 %.100s VMS NNTP server version %.100s. Out of disk space, try later.\r\n",
      news_node,nntp_version);
    write_net(sbuf,stm);
    next_call(stm,CLOSE_LINK,NO_INPUT);
    return;
    }
  if (!strcmp(gdbuf,"*")) cxt[stm].grpc = -1;
  else {
    for (cp=gdbuf; (cp=strchr(cp,',')); cp++) *cp = ' ';
    if (!(cxt[stm].grpc = argparse(gdbuf,&(cxt[stm].grpv)))) {
      sprintf(sbuf,"502 %.100s VMS NNTP server version %.100s. No permissions for %.100s@%.100s.\r\n",
                    news_node,nntp_version,cxt[stm].rusr,cxt[stm].rhst);
      write_net(sbuf,stm);
      next_call(stm,CLOSE_LINK,NO_INPUT);
      return;
      }
    }
  if (cxt[stm].canpost && diskdanger()) {
    cxt[stm].canpost = 0;
    sprintf(sbuf,msg[M201 - cxt[stm].canpost],news_node,nntp_version,localtime,
               "no posting: low server disk space");
    }
  else
    sprintf(sbuf,msg[M201 - cxt[stm].canpost],news_node,nntp_version,localtime,
               (cxt[stm].canpost ? "posting ok" : "no posting"));
  c$free_tmp();
  write_net(sbuf,stm);
  next_call(stm,parser,CMD_INPUT);
}

/*
 *  server_shut
 *
 *  Close down RMS access to the group and item files.
 */

void server_shut()
{
  int status;

  if (leave_spool_open) { max_batch = 0; spool_flush_batch(); }
  if (grp_file_open)  { sys_close(&grpfab);  grp_file_open = 0; }
  if (itm_file_open)  { sys_close(&itmfab);  itm_file_open = 0; }
  if (hist_file_open) { sys_close(&histfab); hist_file_open = 0; }
}

/*
 * write logging information to logical file NEWS_NNTP_LOG
 * -- added 23-Apr-1989 - MRT
 */

static char *dat(void)
{
  static char datum[32];
  struct tm *stm;
  time_t cur_time;
  char *p;

  time(&cur_time);
  p = ctime(&cur_time);
  p += 4;
  stm = localtime(&cur_time);
  sprintf(datum,"%d %.3s %d %02d:%02d:%02d",
          stm->tm_mday, p, (stm->tm_year)+1900,
          stm->tm_hour,stm->tm_min,stm->tm_sec);
  return(datum);
}

int open_nntp_log_file(void)
{                                       /* logging declarations */
  static char *logfile = NULL;

  if (!logfile) {
    char *gotenv;

    if (!logfile && (gotenv = news_getenv("NEWS_NNTP_LOG",1)))
      logfile = news_strdup(gotenv,-1);
    }
  if (!logfile) return 0;
  if (!(fplog = fopen(logfile,OPEN_APPEND_OPT0,"alq=32","deq=32","fop=cbt"))) {
    sleep(1);				/* maybe someone has the file open */
    fplog = fopen(logfile,OPEN_APPEND_OPT0,"alq=32","deq=32","fop=cbt");
    if (!fplog) return 0;
    }
  return 1;
}

void close_nntp_log_file(void)
{
  if (fplog) {
    if (fclose(fplog)) _ck_close(fplog);
    fplog = 0;
    }
}

void log_to_file(unit)
  int unit;
{
  if (open_nntp_log_file()) {
    fprintf(fplog,"%s %s: nntprecv: %s@%s received: %d  posted: %d  failed: %d rejected: %d  read: %d articles, %d newsgroups\n",
            dat(), (news_node ? news_node : ""), cxt[unit].rusr, cxt[unit].rhst,
            cxt[unit].accepted, cxt[unit].posted, cxt[unit].failed,
            cxt[unit].rejected, cxt[unit].arts_read, cxt[unit].grps_read);
    if (total_elapsed_time)
      fprintf(fplog,"\t%u bytes, %u.%02d seconds, %u bytes/second\n",
              total_bytes_received, total_elapsed_time / 100,
              total_elapsed_time % 100,
              (total_bytes_received * 100) / total_elapsed_time);
    close_nntp_log_file();
    }
}

void closefiles()
{
  return;
}

/*
 * mem_fail - called by memory allocation routines in NewsRTL when allocation
 * request fails.  Free memory reserve in case driver's write_net() or
 * close_net() needs a small amount of scratch space, output failure message,
 * and quit.  Note: Error message written to stream 1 only; I don't
 * know how you'd find out here what other streams were around, since this
 * is tracked in the driver module.
 */


void *do_mem_fail(bytesize,do_exit)
  size_t bytesize;
  int do_exit;
{

  if (mem_reserve) {
    mem_reserve = 0;
    write_net(msg[M503F],1);
    close_net();
    fprintf(stderr,"Out of memory - exiting\n");
    }
  if (do_exit) exit(LIB$_INSVIRMEM);
  return((void *) 0);
}


void *mem_fail(bytesize)
  size_t bytesize;
{
  return(do_mem_fail(bytesize,1));   /* do_mem_fail(foo,0) exits */
}


static int putmsg_action_routine(str_dsc, actprm)
  struct dsc$descriptor_s *str_dsc;
  int actprm;
{
/* Write error message to wherever appropriate;
 * (writing to SYS$ERROR will be done by last chance handler)
 */
  if (fplog)
    fprintf(fplog,"  %.*s\n", str_dsc->dsc$w_length, str_dsc->dsc$a_pointer);
  return 0;               /* value 0 inhibits $PUTMSG to write message itself */
}

int server_condition_handler(v_sigargs,v_mechargs)
  void *v_sigargs, *v_mechargs;
{
  static volatile int this_handler_active = 0;
/*struct chf$mech_array *mechargs = v_mechargs;  * unused */
  int cond,nargs,severity,j;
  int new_sa[16];         /* copy of the signal array to be passed to $PUTMSG */

  cond  = ((struct chf$signal_array *) v_sigargs) -> chf$l_sig_name;
  nargs = ((struct chf$signal_array *) v_sigargs) -> chf$l_sig_args;
#if GNUC_HANDLER_HACK
  if (cond == C$_LONGJMP)
    return(this_handler_active=0,vaxc$unwind_longjmp(v_sigargs,v_mechargs));
#endif
  if (cond == SS$_UNWIND)return(this_handler_active=0,SS$_CONTINUE);/*anything*/
  if (cond == SS$_ASTFLT)return(this_handler_active=0,SS$_RESIGNAL);/*VAXC signals*/
  if (cond == SS$_DEBUG) return(this_handler_active=0,SS$_RESIGNAL);
  if (this_handler_active) return(SS$_RESIGNAL);
  this_handler_active = 1;
  severity = $VMS_STATUS_SEVERITY(cond);
  new_sa[0] = min(16-1,nargs-2);
  for (j = 1; j < 16; j++)
    if (j <= nargs-2) new_sa[j] = ((int *)v_sigargs)[j]; else new_sa[j] = 0;
  if (open_nntp_log_file())
    fprintf(fplog, "%s %s: ERROR\n",
            dat(), (news_node ? news_node : ""));
  sys$putmsg(new_sa,&putmsg_action_routine,0,0);
  close_nntp_log_file();

  if (cond == LIB$_INSVIRMEM ||       /* do low-memory stuff */
      cond == OTS$_INSVIRMEM || 
      cond == SHR$_INSVIRMEM ||
      cond == STR$_INSVIRMEM) do_mem_fail(0,0);

  if (severity == STS$K_ERROR) {
    /* change severity to SEVERE to abort with traceback */
    cond = (cond & (~7)) | STS$K_SEVERE;
    ((struct chf$signal_array *) v_sigargs) -> chf$l_sig_name = cond;
    }
  return(this_handler_active=0,SS$_RESIGNAL);
}
