/*
**++
**  FACILITY:
**      NEWSADD
**
**  ABSTRACT:
**      This module parses input files according to the NEWS message format
**      standards and adds parsed items into the local NEWS database.
**
**      The local acceptance filter (as defined in NEWS.SYS) is applied
**      to the newsgroups and distribution control lines before adding to the
**      local database.
**
**      The module also passes items to the outgoing mailbox routines to
**      determine whether the item should be passed to downstream sites.
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright, Geoff Huston © 1988,1989,1990,1991
**
**  VERSION:
**	V6.0-1	11/12/90	glass@mgi.com
**	The major conceptual change is that the headers stored in the itm array
**	are later used to actually write out the article.  This means that in
**	general, the itm data should NOT be modified after the scan_header reads
**	it in.  Specific examples of this were the newsgroup and control headers
**	which were modified for various purposes.  I create separate copies of this
**	stuff when necessary.  The body of the article is held in the array
**	artbuf (which is malloc'd and free'd with each batch file).  If the body is
**	more than ART_BUFSIZ bytes (I've set it to 8192), then the article will
**	be stored in bigitem_scratchfilename.
**
**	Here's a synopsis of the major changes:
**
**	1) mail_add_item has two new parameters.  ng is a char pointer to the
**	list of newsgroups to use for that item.  This list may be different
**	than itm[NEWSGROUPS] because of aliases and/or removing names without a
**	dot.  no_control is a flag that indicates that the control header should
**	not be processed.  This is used instead of setting the first byte of
**	itm[CONTROL] to zero.  For example, when mail_add_item recursively calls
**	itself, it passes a 1 as no_control to prevent a second processing of the
**	control header.
**
**	2) mail_add_item creates a duplicate copy of the newsgroup list passed
**	to it (*ng) so that it can change the newsgroup list to "control" or
**	"junk".
**
**	3) open_out_file is no longer used.
**
**	4) The routine out_header is not responsible for writing all the header
**	lines to the final article file.  It had a couple of minor changes to
**	deal with such things as the APPROVED header.
**
**	5) The routine out_body was created to write out the body of an article.
**	It gets the body from either the memory buffer or the
**	bigitem_scratchfilename.
**
**	6) The routine out_data is no longer used.
**
**	7) A new extern routine called create_article was created.  This routine
**	is called when you want to create a file containing the news article.  The
**	routine takes two parameters.  fn is a pointer to a filename string.
**	This pointer should be NULL if you just want to create a scratch file version
**	of the article (used before calling parse_control_item or sys_local_accept
**	if the article exists only in memory).  The second parameter is xref, which
**	is a pointer to the xref header string if needed.  This is the one header
**	line that is not output by out_header.
**
**	8) The stripnodot routine got moved into the process_item routine because
**	of the constraints on modifying itm[NEWSGROUPS].
**
**	9) The routine append_line was created to handle incoming lines from the
**	body of an article.  It either appends the line to the memory buffer (artbuf)
**	or to the end of a temporary scratch file.
**
**	10) I added some code to get_line to try to handle lines that required
**	multiple calls to fgets.  It's not bullet proof, and only marginally better
**	than the old code.
**
**	11) I changed the nesting of some of the tests in
**	scan (now scan_header_line, mark.martinec@ijs.si) to improve
**	the response for header lines that don't match one of the
**	standard header lines.
**
**	12) I changed scan_header to use realloc instead of (malloc, strcpy, free).
**
**	V6.0-4	15-Apr-1991	glass@vivax.mgi.com
**	     1) I discovered a problem with adding batch files being created by the
**		NNTP_SERVER if /DELETE was enabled.  The problems was that the ADD routine
**		would close the file before deleting it.  There was a slight possibility
**		that the NNTP_SERVER might append another article to the file during that
**		time, and consequently the article would be lost.  The solution is to
**		delete the file before closing it.  With this change, once the ADD routine opens
**		the file for processing, there is no way that the NNTP_SERVER can ever
**		touch it again.
**	V6.1	 4-May-1991	gh
**		get_line now reads the !# rnews byte count and implements it
**	V6.1	 2-Feb-1992	rankin@eql.caltech.edu
**		lint cleanup from gcc -Wall
**	V6.1	 17-Feb-1992	mark@infocomm.com
**	      - bumped ART_BUFSIZE to 60000 since there is no particular reason
**		where another 100 pages of virtual memory will bother any
**		process which is doing add processing (i.e. the NEWS MANAGER
**		account and NOT general users)
**	      - corrected calls to fgets to make sure that the buffer read 
**		will always fit and be NUL terminated.
**	      - Added call to flush_downstream at the end of an ADD FILE
**		command.
**	      - Fixed bug dealing with input batch parsing when the input file
**		has been extracted from Mail and contained more than one
**		message.  This was fixed by moving the contents of the 
**		look_ahead buffer to the primary line buffer (add_inline)
**		in the routine init_context.
**	      - Timing test of various VAXCRTL I/O routines vs. file formats
**		reveals the following:
**		 1) RMS - Multi Buffer Count (Process or System) is not used
**		    when dealing with STREAM_LF files, so explicitly specifying
**		    a value for "mbc=" when opening a file is desired for more
**		    efficient file I/O.  This efficiency is realized by:
**			a) lower I/O counts
**			b) faster I/O throughput
**			c) lower CPU time.
**		 2) The VAX CRTL is MUCH more efficient at reading files which 
**		    are STREAM_LF in format.  For example, large file (1 mb)
**		    read with calls to fgets, with the file opened with a
**		    mbc=120, took 1.67 CPU seconds when the file as STREAM_LF
**		    and 6.64 seconds when it was Variable.
**		    Writing the same file with the same buffering, consumed 
**		    8.96 CPU seconds when the file was variable vs. 1.96 CPU
**		    seconds when the file was STREAM_LF.
**		 The general message is clear.  Stream_LF file format is 
**		 preferred, hence the files created here are no longer created
**		 as variable format files.
**
**	The general message is clear.  Stream_LF file format is preferred.
**
**	Well, I questioned this conclusion, and compared the VAXC RTL
**	I/O vs. direct calls to RMS $GET for the same files.  With a large
**	MBC, RMS could read the variable file in 3.82 CPU seconds vs the 
**	6.64 for VAXC RTL.  The Stream_LF file was read by RMS in 9.68 CPU
**	seconds verses 1.67.  Writing a variable format file with RMS took 
**	5.81 CPU seconds vs. the 8.96 for VAXCRTL.  Writing the Stream_LF 
**	file took 6.36 CPU seconds with RMS, vs the 1.96 CPU seconds for 
**	the VAXC RTL.
**
**	NOW, the real conclusion should be: depending on how you are 
**	going to read (most frequently), or write the data (VAXC RTL (fgets,
**	fputs)), or direct RMS calls (SYS$GET, SYS$PUT) will determine the
**	preferred file format.  VAXC RTL implies STREAM_LF, and RMS implies
**	VARIABLE/CR.
**
**	The changes I've implemented (in create_article), actually creates 
**	item files with Stream_LF format, since a cursory observation showed
**	that things use do_open_item to open an article followed by fgets to
**	read it.
**	
**	I have also explicitly specified values for mbc here in NEWSADD and
**	elsewhere, to help realize the performance gains noted above.  I may
**	have been too extravagant with my choices ov the mbc values in each
**	case, but I have selected values which seem appropriate to let the
**	runtime environment either read ahead (or write behind), enough so 
**	that most files are read in one or two I/O's.
**
**	V6.1	 26-Feb-1992	mark@infocomm.com
**	      - added status messages indicating why control messages may not
**		have been processed.
**	V6.1	17-Apr-1992	ewilts@galaxy.gov.bc.ca
**	      - eliminated logging of each article processed.
**	        Only warning messages (containing article ID) are generated
**	V6.1	23-Jul-1992	fenner@cmf.nrl.navy.mil
**	      - added sending system name to log file
**	V6.1	 9-Aug-1992	ewilts@galaxy.gov.bc.ca
**	      - 2 printf's to log file fixed
**	V6.1b8  10-Sep-1993	mark.martinec@ijs.si
**	      - instrument the I/O (VAXC RTL i/o) with
**	        error checking and reporting
**	V6.1b8	17-Sep-1993	bailey@genetics.upenn.edu
**	      - add /FeedThrough qualifier to Add File
**	V6.1b8	31-Mar-1994	mark.martinec@ijs.si
**		MAJOR REHASH OF THE CODE IN THIS MODULE.  Due to the memory
**		corruption problems when articles with very long header lines
**		were received, the fixed-size 'itm' storage for storing header
**		lines was inappropriate (and memory consuming).  I changed
**		the data representation completely, which affected mostly
**		the low-level procedures, which I have either rewritten
**		completely or changed heavily.  The high-level procedures
**		(process_item, mail_add_item) remained mostly intact.
**
**		The new data representation now handles arbitrary length
**		headers, poses almost no burden on the malloc/free storage
**		allocation, uses on the average much less virtual memory
**		and the accessing/house-keeping is very fast.  By storing
**		string sizes explicitly I managed to get rid of numerous
**		strlen(), strcat(str,"char"), strchr, chop_str(str,'\n'),
**		copying strings from one temporary array into another etc.
**		All larger string copying is now handled by memcpy or memmove
**		and many avoided altogether.  The tricky boundary conditions
**		are now better handled.
**
**		get_line() and scan_header() now properly handle reading
**		lines longer than the read buffer size by doing partial (split)
**		reads - properly, this time.  This eliminated a need for
**		very long read buffer (actually two of them).  Also the need
**		to copy strings from look_ahead buffer into add_inline is
**		now eliminated by just swapping pointers to buffers.
**
**		There was already one previous attempt at rewriting the
**		code in this module.  It was bracketed by '#ifdef NEWCODE'.
**		Its logic was much cleaner, but did not (yet) handle many
**		different multi-article input formats.  For this reason
**		(and unfortunately for the author) I decided to base my
**		changes on the old code, which (although not perfect) was
**		already proven 'in the field'.  The NEWCODE pieces are now
**		eliminated from this module, but are available in the file
**		NEWSADD_OLD.C, which contains the state of this module
**		before my overhaul.
**
**		Two NEWSSSITE.H parameters (macros) are now added which control
**		JUNKing of articles with very long newsgroups list.  Usenet was
**		recently struk several times by heavily crossposted articles,
**		which caused numerous problems to many ANU-NEWS sites (due to
**		the before-mentioned memory corruption when storing news
**		headers).
**
**		The patch from ewilts@galaxy.gov.bc.ca (17-Apr-1992) which
**		eliminated logging of each article processed (only warning
**		messages were generated) is now functionally incorporated
**		in the code.  The logging may now be turned off or on
**		dynamically at the ADD FILE command by the qualifier /[NO]LOG
**
**		Added code to produce statistics report.
**
**		Reduced the size of ART_BUFSIZE to 32000, based on statistics.
**		Also the initial size of itm_text and its increments is
**		based on statistical measurements.
**
**	V6.1b9	24-May-1994	mark.martinec@ijs.si
**	      - avoid looping when an empty article is received like in:
**		  #! rnews 1
**		  #! rnews 1798
**	V6.1b9	14-Jun-1994 Saul Tannenbaum saul@hnrc.tufts.edu
**	      - add rms tuning hooks
**	V6.1b9	22-Jun-1994	mark.martinec@ijs.si
**	      - make header_size and body_size module-global variables;
**	      - create_article, length_header:  make a more accurate
**		estimate on article size (based on previous patch by ST)
**		in order to pre-allocate the proper file size
**		during the .ITM file creation.
**		Also remove an unnecessary fstat used to get the tmp file size.
**	      - add RELAY_VERSION to the set of header lines that should
**		not be wrapped;
**	      - better handle cases when batch file can not be opened
**		(more informative diagnostics, skip the file)
**	V6.1b9	17-Aug-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to make it compile under gcc 2.6.0 with full
**	    warnings reporting turned on - with no or very few harmless warnings
**	V6.1b9	17-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to preserve the read-only nature of string literals
**	    (strategically placed 'const' attribute to string parameters)
**	V6.1b9	 5-Jan-1995	Mark Martinec   mark.martinec@ijs.si
**	  - added to statistics report: CPU load, average article crossposting
**	V6.1b9	 2-Mar-1995	Mark Martinec   mark.martinec@ijs.si
**	  - commented out chmod(fn,0755) in create_article() to save
**	    about 4% in elapsed time during ADD FILE
**	V6.1b9	 9-Mar-1995	Mark Martinec   mark.martinec@ijs.si
**	  - call new site-specific routine try_to_make_some_free_space()
**	    when file creation fails (specially due to insufficient
**	    contiguous free space on NEWS_DEVICE)
**	V6.1b10	22-Feb-1996	Mark Martinec   mark.martinec@ijs.si
**	  - in add_file() added option "shr=nil" to fopen and ignore
**	    RMS$_FLK & RMS$_FNF errors to enable multiple ADD FILE processes
**	    to be running concurrently on the same set of batch files
**	  - correctly handle /DEFNEWSGROUP and /NEWSGROUP qualifiers
**	    in a combinations of global and positional placements
**	  - allow qualifiers /DEFNEWSGROUP and /NEWSGROUP to specify
**	    a list of values, which are then concatenated using commas
**	    as separators. This is just for convenience, the meaning of
**	    /NEWSGROUP=(a,b,c) is the same as /NEWSGROUP="a,b,c".
**	    As a side effect longer lists (up to 1023 characters)
**	    can now be specified (not that we would need that, but ...)
**	V6.1b10 16-Mar-1996 	Mark Martinec   mark.martinec@ijs.si
**	  - new functions lock_filename() and unlock_filename() call
**	    lock manager directly to avoid timing problems with
**	    nonatomic call to { fclose(); delete(); }
**	    NOTE: All ANU-NEWS jobs running ADD FILE on the same set of files
**	          should be running under the same gid (group id) to enable
**	          locks to be recognized between jobs !
**	V6.1b11 24-Mar-1997	Mark Martinec   mark.martinec@ijs.si
**	  - recognize header 'X-PGP-Sig:' and store information
**	    required for checking PGP signature of the control mesages
**	  - collect common code in routine find_header_name
**	  - remove array 'headers' (with lowercased names from fheaders),
**	    now 'fheaders' is used for both purposes (matching and writing).
**	V6.1b11 14-Apr-1997	Mark Martinec   mark.martinec@ijs.si
**	  - replace several calls to strcpy with safer strnncpy
**	  - replace pattern strcpy(news_malloc(strlen(x)+1),x) with news_strdup
**	  - fix the year 2000 problem when generating the missing 'Date:' header
**	  - quick check of pgp_verify() before ADD FILE processing starts
**	V6.1b11 22-May-1997	Mark Martinec   mark.martinec@ijs.si
**	  - replace single flag EXECUTE_CONTROL with one for each type
**	    of control messages: EXECUTE_CONTROL_CHECKGROUPS,
**	    EXECUTE_CONTROL_NEWGROUP and EXECUTE_CONTROL_RMGROUP.
**	  - flags EXECUTE_CONTROL_RMGROUP_DELETE and
**	    EXECUTE_CONTROL_RMGROUP_LOCAL are now used strictly as a modifier
**	    to the way groups are deleted/removed and affect the commands
**	    placed in DCL procedures as well as immediate execution.
**	  - add qualifier /RMGROUP to ADD FILE which implements part
**	    of the semantics formerly under /EXECUTE.  Default: /RMGROUP=RETAIN
**	  - add list of keywords to qualifier /EXECUTE to distinguish between
**	    different types of control messages by accepting qualifier values
**	    CHECKGROUPS, NEWGROUP and RMGROUP. If only /EXECUTE is specified,
**	    it defaults to /EXECUTE=(NEWGROUP,RMGROUP).
**	    /EXECUTE=ALL equals /EXECUTE=(CHECKGROUPS,NEWGROUP,RMGROUP).
**	    This permits us to use /EXECUTE qualifier with PGP signature
**	    verification (which presently works only on newgroup & rmgroup
**	    control messages) without fear that Checkgroups control messages
**	    would also be executed, and at the same time keeping
**	    backwards compatibility.
**/

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

#define _NEWSADD_C
#define module_name "NEWSADD"

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

#if NAKED_INCLUDES
#include lckdef
#include psldef
#else
#include <lckdef.h>
#include <psldef.h>
#endif

#if NNTP_CLIENT_ONLY

int do_add_net(void) { return(0); }

char *create_article(char *fn, char *xref) { return(NULL); }

#else

#if ADDFILE_NEWSGROUPS_SIZE_LIMIT
#define NEWSGROUPS_MAX_SIZE (ADDFILE_NEWSGROUPS_SIZE_LIMIT+1+128)
#else
#define NEWSGROUPS_MAX_SIZE (6*1024)       /* max size of the newsgroups list */
#undef  ADDFILE_NEWSGROUPS_SIZE_LIMIT
#define ADDFILE_NEWSGROUPS_SIZE_LIMIT (NEWSGROUPS_MAX_SIZE-1-128)
                    /* 128 bytes safety margin to allow for aliases expansion */
#endif

#define ART_BUFSIZE 32000              /* article size kept in memory;
                                          the rest goes into scratch file */

static
int debug_switch = DEBUG,
    single_header_scan = 1,
    log_file_line_not_terminated = 0,  /* '\n' not yet printed */
    log_file_article_id_written = 0,   /* article ID already printed */
    log_every_article = 1;   /* report every article ID during ADD FILE
                                (errors are always reported, regardless
                                of setting of this parameter)
                              */

/* statistics */
#if ADDFILE_STATISTICS_ENABLE
#define STAT_HDR_SIZE_BINS_M 200
#define STAT_ART_SIZE_BINS_M 350 /* suggested size:
                                      above (ART_BUFSIZE / STAT_ART_BIN_SIZE) */
#define STAT_HDR_BIN_SIZE 10     /* bin size (in bytes) of stat_hdr_size_bins */
#define STAT_ART_BIN_SIZE 100    /* bin size (in bytes) of stat_art_size_bins */
int stat_artbuf_too_small,       /* no. of times scratch file was used */
    stat_itm_text_too_small,     /* no. of times itm_text was extended */
    stat_itm_text_peak,          /* max size of itm_text used */
    stat_hdr_size_peak,          /* max size of an article header */
    stat_article_size_peak,      /* max size of an article (header+body) */
    stat_ng_size_peak,           /* max size of "Newsgroups:" list in articles*/
    stat_ng_crosspost_peak,      /* max count of "Newsgroups:" list elements  */
    stat_ng_crosspost_total,     /* total number of "Newsgroups:" list elements  */
    stat_articles,               /* number of articles processed */
    stat_articles_cumulative_size, /* number of characters in all articles processed */
    stat_hdr_size_bins[STAT_HDR_SIZE_BINS_M], /* bins for article header sizes*/
    stat_art_size_bins[STAT_ART_SIZE_BINS_M]; /* bins for total article sizes */
unsigned long
    stat_start_time[2];          /* ADD FILE start time (sys time - quadword) */
clock_t
    stat_start_cpu;              /* processor time at the start of ADD FILE */
#endif

/* HDR_TEXT(header_id) evaluates to the address of the string,
     representing the header line contents for the given header id;
     macro may be used as lexpr.

   HDR_TEXT_L(header_id) it the length of the string HDR_TEXT(header_id);
     macro may be used as lexpr.

   The UNK_HDR_TEXT and UNK_HDR_TEXT_L are variations of the above,
   used for accessing header lines unrecognized by ANU-NEWS.

   (N.B.: addressing of the strings in *itm_text must always
          be done through indexing (not through stored pointers)
          because of the possible reallocation of the buffer)
 */
#define HDR_TEXT(header_id)	(itm_text+itm_hdr_ind[header_id])
#define HDR_TEXT_L(header_id)	(itm_hdr_l[header_id])
#define UNK_HDR_TEXT(header_id)	(itm_text+itm_unk_hdr_ind[header_id])
#define UNK_HDR_TEXT_L(header_id) (itm_unk_hdr_l[header_id])

/*
 * The following data structures are used to temporarily store
 * header lines of the article being processed:
 */
int  itm_hdr_ind[NHEADERS]; /* indices into the *itm_text array -
                               - pointing to the text of the given header line*/
int  itm_hdr_l[NHEADERS];   /* actual lengths of header text string in
                               *itm_text starting at index itm_hdr_ind[i];
                               Length does not include the terminal '\0' char;
                               (keeping the lengths in this array avoids
                               multiple calls to strlen() )                   */
int  itm_unk_hdr_ind[MAX_UNK]; /* indices into the *itm_text array -
                               - pointing to the text of the given header line.
                               This is similar to itm_hdr_ind, except that
                               is used for storing headers unrecognized 
                               by ANU-NEWS.  These headers are stored unchanged
                               and in full - including the header keyword.    */
int  itm_unk_hdr_l[MAX_UNK];   /* actual lengths of foreign header text string;
                               This is similar to itm_hdr_l, except that is used
                               for storing headers unrecognized by ANU-NEWS.  */
int  itm_unk_l = 0;         /* number of foreign headers currently stored in
                               itm_unk_hdr_ind */
int  itm_text_m = 0;        /* allocated length of the *itm_text buffer       */
char *itm_text = NULL;      /* pointer to allocated storage where text from
                               item headers is being accumulated during parsing 
                               of item header.  This storage is generously
                               allocated and is usually never reallocated.
                               - strings are '\0' terminated;
                               - array itm_hdr_ind contains indices where header
                                 text strings start;
                               - array itm_unk_hdr_ind contains indices where
                                 foreign header text strings start;
                               - the actual size is:
                                 (itm_text_tmp_in_use?itm_text_tmp_l:itm_text_l)
                             */
int itm_text_l = 0;         /* actual length of the used storage in the
                               *itm_text buffer when itm_text_tmp_in_use=false*/
int itm_text_tmp_in_use = 0;/* true while the we are using the remaining
                               space in itm_text buffer for assembling
                               long header lines (or other purposes).
                               Helps us make sure we do not use normal methods
                               during that time */
int itm_text_tmp_l = 0;     /* when we are using space in itm_text buffer
                               bypassing the normal allocation, this variable
                               takes over the role of itm_text_l and keeps
                               track of the actual size used (which is always
                               beyond the permanent part (itm_text_l) ).
                               When itm_text_tmp_in_use is false, this variable
                               carries no meaning  */

static
int  scr_files_created = 0,
     relay_vers_str_l = 0;
static
char
     relay_vers_str[256],
     scratchfilename[256],
     bigitem_scratchfilename[256],
     def_newsgroup[1024],
     force_newsgroup[1024],
     mod[256],
     *artbuf = 0;     /* N.B.: *artbuf not terminated with '\0'; see atrbuf_l */
const char
     junk[] = "junk",
     *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: "};
/*
 * to avoid calling strlen() all the time, array fheaders_l
 * is provided; it is initialized before the first use
 */
static int fheaders_l[NHEADERS] = { 0 };   /* string lengths of fheaders */

static
int infile_eof = 0,
    infile_eom = 0,
    exec_switch = 0,
    infile_initial_fragment = 1,  /* add_inline contains the beginning of a
                                     possibly split source line */
    previous_line_complete = 1,   /* previous add_inline terminated by '\n' */
    line_count = 0,               /* article body size in lines */
    body_size = 0,                /* article body size in characters */
    header_size = 0,              /* article header size in characters */
    n_stripping = 0,
    del_after = 0,
    del_after_save,
    skip_loop_test,
    junk_it = 1,
    accept_it = 0,
    no_add = 0,
    mod_add = 0,
    control_squelch = 0,
    genid = 0,
    feed_through = 0,
    temp_file_error = 0,
    loc_junk = 0,
    artbuf_l = 0;     /* actual string size in *artbuf;
                         N.B: string in *artbuf is not terminated by '\0' !!! */

static
FILE *fpr = 0,
     *fpt = 0;

/* the size of buffers buf1 and buf2 should be larger than the majority
   of line lengths from articles, but longer lines are handled properly
   so there is no need to increase this size too much */
static char buf1[256] = "", buf2[sizeof(buf1)] = "";
char *add_inline = buf1;    /* current line of the article being processed */
char *look_ahead = buf2;    /* next line of the article being processed */
int  add_inline_l = 0;      /* actual length of string in add_inline */
int  look_ahead_l = 0;      /* actual length of string in look_ahead */
int  look_ahead_eof = 0;    /* end-of-file while reading look_ahead  */
int  look_ahead_in_use = 0; /* next line already read in look_ahead  */

static const char *controlmsgs[] = {"",
			    "Can't create scratch file",
			    "No mail return address",
			    "Error creating new newsgroup",
			    "Missing newsgroup field",
			    "Non Approved",
			    "Invalid for local newsgroup",
			    "No such newsgroup",
			    "Can't access file",
			    "Missing sender address",
			    "Invalid format message",
			    "ihave/sendme not \"to\" me",
			    "ihave/sendme missing remote system",
			    "Unknown control message"};

/*
 *  find_header_name
 *
 *  Search through a list of known article header names
 *  and return 1 if found and 0 if not.
 *  If a variable 'index' is given (address not NULL),
 *  it receives the matched index from the 'fheaders' array
 *  if found (0..NHEADERS-1), or -1 otherwise.
 *  Search is case-sensitive if exact_match is true, case-insensitive if 0.
 */

int find_header_name(const char *str, int str_l, int *index, int exact_match)
{
  int j,found;

  str_l = (str_l>=0) ? str_l : strlen(str);
  found = 0;
  if (exact_match) {
    for (j=0; j < NHEADERS; ++j) {
      if ((str_l == fheaders_l[j]-2) &&
          !memcmp(str,fheaders[j],str_l)) { found = 1; break; }
      }
    }
  else {
    for (j=0; j < NHEADERS; ++j) {
      if ((str_l == fheaders_l[j]-2) &&
          !news_strncasecmp(str,fheaders[j],str_l)) { found = 1; break; }
      }
    }
  if (!found) j = -1;
  if (index) *index = j;
  return found;
}

/*
 *  append_itm_text_tmp
 *
 *  save string in temporary storage at the end of permanent part
 *  of *itm_text buffer.  Mark this temporary allocation as 'in-use'.
 *
 *  We guarantee that there will still be room in *itm_text for
 *  one more character (so the trailing '\0' may be appended without
 *  calling this procedure again).
 *
 *  New strings are always appended at the end of the buffer.
 *  No attempt is made to collect garbage if it would happen that the
 *  particular header text is no longer needed. There is no real need
 *  for that as the whole buffer gets scraped away after processing
 *  each article and we do not want to sacrifice speed and simplicity.
 */

void append_itm_text_tmp(str, str_l, append_eos)
  char *str;       /* string to be saved */
  int str_l;       /* string length if known, otherwise -1 */
  int append_eos;  /* true:  append '\0' to the end of the string;
                      false: just reserve one byte so the caller may
                             append '\0' when he pleases */
{
  str_l = (str_l>=0) ? str_l : strlen(str);

  /* start using temporary part of buffer (if not already started) */
  if (!itm_text_tmp_in_use)
    { itm_text_tmp_in_use = 1; itm_text_tmp_l = itm_text_l; }

  /* reserve str_l bytes + one more for possible '\0' */
  if (itm_text_tmp_l+(str_l+1) > itm_text_m) {
    int incr = max(2*1024, itm_text_tmp_l+(str_l+1)-itm_text_m);
    if (!itm_text)
      itm_text = news_malloc(itm_text_m=incr);
    else {
      itm_text = news_realloc(itm_text, itm_text_m+=incr);
#if ADDFILE_STATISTICS_ENABLE
      stat_itm_text_too_small++;
#endif
      }
    }

  if (str_l > 0) {
    memcpy(itm_text+itm_text_tmp_l, str, str_l);
    itm_text_tmp_l += str_l;
    }
  if (append_eos) itm_text[itm_text_tmp_l++] = '\0';

#if ADDFILE_STATISTICS_ENABLE
  if (itm_text_tmp_l > stat_itm_text_peak) stat_itm_text_peak = itm_text_tmp_l;
#endif
}

/*
 *  store_header, store_unk_header
 *
 *  Save text (item header line) into buffer *itm_text if not already there;
 *  adjust appropriate pointers (indices) to it.
 *
 *  For recognized headers (0 <= header_id <= NHEADERS) :
 *    set index itm_hdr_ind[header_id] to point to the stored string
 *    and save string length in itm_hdr_l[header_id] ;
 *
 *  For headers unrecognized by ANU-NEWS (header_id < 0) :
 *    set index itm_unk_hdr_ind[-header_id-1] to point to the stored string
 *    and save string length in itm_unk_hdr_l[-header_id-1]
 */

#define store_unk_header(unk_header_id, str, str_l) \
  store_header(-(unk_header_id)-1, (str), (str_l))

void store_header(header_id, str, str_l)
  int header_id;   /* which header ? */
  char *str;       /* string to be saved */
  int str_l;       /* string length if known, otherwise -1 */
{
  int ind;
  str_l = (str_l>=0) ? str_l : strlen(str);

  if (!itm_text_tmp_in_use)   /* first store text in temporary part of buffer */
    { ind = itm_text_l; append_itm_text_tmp(str,str_l,1); }
  else {                                             /* text already in place */
    ind = str - itm_text;            /* compute index within *itm_text buffer */
    news_assert(ind >= itm_text_l);        /* s must be within temporary part */
    news_assert(ind + str_l < itm_text_m);
    }
  news_assert(header_id < NHEADERS);
  if (header_id >= 0) {   /* recognized header */
    itm_hdr_ind[header_id] = ind;
    itm_hdr_l[header_id] = str_l;
    }
  else {                  /* header, unknown to ANU-NEWS */
    int unk_header_id = -header_id-1;
    itm_unk_hdr_ind[unk_header_id] = ind;
    itm_unk_hdr_l[unk_header_id] = str_l;
    }
  itm_text_l = itm_text_tmp_l; itm_text_tmp_in_use = 0;  /* make it permanent */
}


/*
 *  take_lookahead
 *
 *  Take the lookhead buffer and make it the current line (add_inline).    
 */

void take_lookahead(void)
{
/* swap buffers */
  { char *tmp = add_inline; add_inline = look_ahead; look_ahead = tmp; }
  { int tmp = add_inline_l; add_inline_l = look_ahead_l; look_ahead_l = tmp; }
  infile_eof = look_ahead_eof;
/* erase lookahead buffer */
  *look_ahead = '\0'; look_ahead_l = 0; look_ahead_in_use = 0;
}


/*
 *  get_line
 *
 *  Read the next line from the input file and leave it in add_inline.
 *  Scan for message-end delimiters of various known formats.
 *
 *  Returns 0 at end-of-message, otherwise 1.
 */

int get_line(void)
{
  static int rnews_active = 0;

  if (debug_switch) printf(">> ");
  infile_initial_fragment = previous_line_complete;

  if (look_ahead_in_use) take_lookahead(); /*if lookahead in use then take it */
  else if (fgets(add_inline,sizeof(buf1),fpr))        /* else read next chunk */
    add_inline_l = strlen(add_inline);
  else { /* eof or error */          /* if end of file then cleanup the flags */
    _ck_get(fpr); infile_eof = 1; add_inline_l = rnews_active = 0;
    }

  if (infile_eof) {
    look_ahead_eof = infile_eom = previous_line_complete = 1;
    if (debug_switch) printf("\\eof\\\n");
    return(!infile_eom);
    }

  previous_line_complete =
    (add_inline_l > 0) && (add_inline[add_inline_l-1] == '\n');

  /* if N encapsulation is active then strip off leading 'N' */
  if (n_stripping && infile_initial_fragment) {
    if ((add_inline_l > 0) && (add_inline[0] == 'N')) {
      memmove(add_inline,add_inline+1,--add_inline_l);
      add_inline[add_inline_l] = '\0';
      }
    else {        /* if no more N encapsulated lines, that is a good EOM flag */
      infile_eom = 1; rnews_active = 0; n_stripping = 0;
      if (debug_switch) printf("\\eom\\\n");
      infile_initial_fragment = 1; return(!infile_eom);
      }
    }

	/*------ if this is in the middle of an rnews batch then simply subtract
		 this line size from the outstanding count.
		 Attempt to resync with the stream with a lead-in of 20 chars.*/
  if (rnews_active) {
    rnews_active -= add_inline_l;
    if (rnews_active < 20) rnews_active = 0;
    else {
      if (debug_switch) printf("%s",add_inline);
      return(!infile_eom);
      }
    }

	/*------ If this is an #! rnews line then this is an EOM point. */
  if (infile_initial_fragment && !strncmp(add_inline,"#! rnews ",9)) {
    sscanf(add_inline,"#! rnews %d",&rnews_active);
    infile_eom = 1; single_header_scan = 1;
    if (debug_switch) printf("\\eom\\\n");
    return(!infile_eom);
    }

	/*------ If this is an N#! rnews then ditto with N stripping auto enabled */
  if (infile_initial_fragment && !n_stripping && !strncmp(add_inline,"N#! rnews ",10)) {
    n_stripping = 1;
    sscanf(add_inline,"N#! rnews %d",&rnews_active);
    infile_eom = 1; single_header_scan = 1;
    if (debug_switch) printf("\\eom\\\n");
    return(!infile_eom);
    }

	/*------ VMS Mail extract delimiters - the sequences this code looks
		 for are /f/nFrom: (extract/head) and /f/n/Path: (extract/nohead
		 from MAIL) and also /f/n/Relay-Version: (ditto). The N encapsulated
		 versions of these last two cases are also detected. */
  if (!n_stripping &&
	/*------ If the line began with a (now stripped) 'N' then there
		 is no point looking for a VMS Mail delimiter sequence */
      infile_initial_fragment && !strcmp(add_inline,"\f\n")) {
    if (fgets(look_ahead,sizeof(buf1),fpr))
      look_ahead_l = strlen(look_ahead);
    else /* eof or error */
      { _ck_get(fpr); look_ahead_l = 0; look_ahead_eof = 1; }
    look_ahead_in_use = 1;
    if (!look_ahead_eof) {
      if (   !strncmp(look_ahead,"From:",5)
          || !strncmp(look_ahead,"Path:",5)
          || !strncmp(look_ahead,"Newsgroups:",11)
          || !strncmp(look_ahead,"Relay-Version:",14)) {
        infile_eom = 1; single_header_scan = strncmp(look_ahead,"From:",5);
        if (debug_switch) printf("\\eom\\\n");
        take_lookahead();  /* discard \f\n */
        previous_line_complete =
          (add_inline_l > 0) && (add_inline[add_inline_l-1] == '\n');
        return(!infile_eom);
        }
      if (   !strncmp(look_ahead,"NFrom:",6)
          || !strncmp(look_ahead,"NPath:",6)
          || !strncmp(look_ahead,"NNewsgroups:",12)
          || !strncmp(look_ahead,"NRelay-Version:",15)) {
        infile_eom = 1; n_stripping = 1; single_header_scan = 0;
        if (debug_switch) printf("\\eom\\\n");
        take_lookahead();  /* discard \f\n */
        previous_line_complete =
          (add_inline_l > 0) && (add_inline[add_inline_l-1] == '\n');
        return(!infile_eom);
        }
      }
    }
	/*------ Otherwise nothing of significance was found I assume */
  if (debug_switch) printf("%s",add_inline);
  return(!infile_eom);
}

/*
 *  print_log
 *
 *  format and print the log report message
 *
 */

void print_log(msg,arg,newsgroup)
  char *msg;   /* message string;  if arg != NULL then  msg must include  %s  */
  char *arg;   /* NULL or string to be included with the message replacing %s */
  char *newsgroup;  /* NULL or list of newsgroups */
{
  if (!log_file_article_id_written) {
    char recvfrom[50];
    int j = min(HDR_TEXT_L(PATH),sizeof(recvfrom)-1);
    memcpy(recvfrom,HDR_TEXT(PATH),j); recvfrom[j] = '\0';
    chop_str(recvfrom,'!');
    printf("Add %s %s",
      recvfrom, (!HDR_TEXT_L(MESSAGE_ID) ? "<>" : HDR_TEXT(MESSAGE_ID)));
    if (newsgroup) printf(" %s", newsgroup);
    log_file_article_id_written = 1;
    }
  if (!msg)         /* normally the first call when log_every_article == true */
    { if (arg) printf(": %s", arg); }        /* normally this does not happen */
  else {            /* normal error message */
    if (msg[0] != '\t') printf(": "); else printf("\n");
    if (!arg) printf(msg); else printf(msg,arg);
    }
  log_file_line_not_terminated = 1;
}

/*
 *  mail_add_item
 *
 *  Add the temporary output file into the News database,
 *  creating new newsgroups where required
 */

static void mail_add_item(fn,ng,ng_l,linecount,no_control)
  char *fn;
  char *ng;   /* list of newsgroups */
  int ng_l;   /* size of string ng if known, otherwise -1 */
  int linecount;
  int no_control;
{
  int status;
  char newsgroup[NEWSGROUPS_MAX_SIZE], *m_id_bl, *m_id_br, *m_id;
  unsigned int cre_grp[100];

  ng_l = (ng_l >= 0) ? ng_l : strlen(ng);

  mail_add_expiry = parse_expiry_date(HDR_TEXT(EXPIRES));
  if (!itm_hdr_l[MESSAGE_ID] && genid) store_header(MESSAGE_ID,gen_id(),-1);

  /*  If the message id string includes '<' and '>' characters,
      then strip off all stuff outside these delimiters */
  m_id_bl = m_id = HDR_TEXT(MESSAGE_ID);
  if ((*m_id == '<') || (m_id_bl = strchr(m_id,'<'))) {
    m_id_br = strrchr(m_id_bl+1,'>');
    if (m_id_br) {
      int len = m_id_br - m_id_bl + 1;
      if (m_id_bl != m_id)
        memmove(m_id,m_id_bl,len);
      *(m_id+len) = '\0';
      HDR_TEXT_L(MESSAGE_ID) = len;
      }
    }
  news_assert(ng_l < sizeof(newsgroup));
  memcpy(newsgroup,ng,ng_l); newsgroup[ng_l] = '\0';

  if (log_every_article && !log_file_article_id_written)
    print_log(0,0,newsgroup);                        /* print each message ID */

  if (itm_hdr_l[CONTROL] && !no_control && itm_hdr_l[MESSAGE_ID]) {
    strcpy(newsgroup,"control");
    if (   !skip_loop_test
        && (
#if SELF_PATH_CHECK
            path_match(HDR_TEXT(PATH),news_pathname) ||
#endif
	       check_id(HDR_TEXT(MESSAGE_ID))))
      print_log("\tControl: Ignored (Loop Detected)",0,newsgroup);
    else if (!sys_local_accept(ng,HDR_TEXT(DISTRIBUTION)))
      print_log("\tControl: Ignored (SYS filter)",0,newsgroup);
    else {
      if (!fn) fn = create_article(NULL,NULL);
      if (fn) {
        int j,parse_sts;
        const char *known_headers[NHEADERS];
        if (log_file_line_not_terminated) {
          putchar('\n');
          log_file_line_not_terminated = log_file_article_id_written = 0;
          }
        for (j = 0; j < NHEADERS; ++j) known_headers[j] = HDR_TEXT(j);
        parse_sts = parse_control_item(known_headers,fn,exec_switch,newsgroup);
        if (!(parse_sts&1))
          print_log("\tControl: %s",controlmsgs[parse_sts>>1],newsgroup);
        }
      }
    }
  if (!strcmp(newsgroup,"junk") && !junk_it) {
    print_log("REJECT (No Newsgroup/NOJUNK)",0,newsgroup);
    return;
    }
  if (!*newsgroup) {
    if (!junk_it) {
      print_log("REJECT (No Newsgroup)",0,newsgroup);
      return;
      }
    print_log("JUNK (No Newsgroup)",0,newsgroup);
    strcpy(newsgroup,junk);
    }
  if (!itm_hdr_l[MESSAGE_ID]) {
    print_log("REJECT (No Message-ID)",0,newsgroup);
    return;
    }

#if SELF_PATH_CHECK
  if (!skip_loop_test && !mod_add && path_match(HDR_TEXT(PATH),news_pathname)) {
    print_log("REJECT (Path Loop)",0,newsgroup);
    return;
    }
#endif

  if (!sys_local_accept(newsgroup,HDR_TEXT(DISTRIBUTION)) && strcmp(newsgroup,junk)) {
    if (!junk_it) {
      print_log("REJECT (SYS filter)",0,newsgroup);
      if (feed_through) {
        if (!fn) fn = create_article(NULL,NULL);
        if (fn) {
          sys_remote_send(HDR_TEXT(PATH),HDR_TEXT(NEWSGROUPS),
                          HDR_TEXT(DISTRIBUTION),fn,HDR_TEXT(MESSAGE_ID),
                          !mod_add);
          }
        }
      return;
      }
    print_log("JUNK (SYS filter)",0,newsgroup);
    strcpy(newsgroup,junk);
    }
  do_new_group(newsgroup,0,cre_grp);
  if ((!*cre_grp) && (strcmp(newsgroup,junk))) {
    if (!junk_it) {
      print_log("REJECT (No local Newsgroup match)",0,newsgroup);
      if (feed_through) {
        if (!fn) fn = create_article(NULL,NULL);
        if (fn) {
          sys_remote_send(HDR_TEXT(PATH),HDR_TEXT(NEWSGROUPS),
                          HDR_TEXT(DISTRIBUTION),fn,HDR_TEXT(MESSAGE_ID),
                          !mod_add);
          }
        }
      return;
      }
    print_log("JUNK (No local Newsgroup match)",0,newsgroup);
    strcpy(newsgroup,junk);
    do_new_group(newsgroup,0,cre_grp);
    }
  if ((!*cre_grp) && (!strcmp(newsgroup,junk)))
    print_log("REJECT (No JUNK)",0,newsgroup);
  *itm_fname = '\0';
  status = 0;
  if (*cre_grp) {
    status = do_new_item((unsigned int *) cre_grp, HDR_TEXT(MESSAGE_ID),
                         HDR_TEXT(SUBJECT),HDR_TEXT(FROM),
                         fn,1,skip_loop_test,linecount,
                         try_to_quietly_handle_errors);
    if (status != 0) {
      if (   (status == RMS$_DUP)
          || (status == RMS$_NORMAL)
	  || (status == (RMS$_DUP|0xb0000000U))
	  || (status == (RMS$_NORMAL|0xb0000000U))
          ) {
          print_log("REJECT (Duplicate - Loop?)",0,newsgroup);
        return;
        }
      else if (!strcmp(newsgroup,junk))
        print_log("REJECT (%s)",no_new_item,newsgroup);
      else {
        if (!junk_it) {
          if (!*itm_fname && !fn &&
              !(fn = create_article(NULL,NULL))) return;
          sys_remote_send(HDR_TEXT(PATH), HDR_TEXT(NEWSGROUPS),
                          HDR_TEXT(DISTRIBUTION),
                          ((*itm_fname) ? itm_fname : fn),
                          HDR_TEXT(MESSAGE_ID), !mod_add);
          print_log("REJECT (%s)",no_new_item,newsgroup);
          return;
          }
        print_log("JUNK (%s)",no_new_item,newsgroup);
        mail_add_item(fn,junk,-1,linecount,1);
        return;
        }
      }
    else if (itm_hdr_l[SUPERSEDES]) {
      char *id,
           *ide,
           *mail_sender = HDR_TEXT(SENDER),
           net_sender[132],
	   save_itm_fname[256];

      strnncpy(save_itm_fname,sizeof(save_itm_fname), itm_fname,-1, 0);
      if ((id = strchr(HDR_TEXT(SUPERSEDES),'<')) != 0
       && (ide = strchr(id,'>')) != 0) {
        *(ide + 1) = '\0';
        if (!*mail_sender) mail_sender = HDR_TEXT(FROM);
        if ((ide = strchr(mail_sender,'<')) != 0) {
          strnncpy(net_sender,sizeof(net_sender), ++ide,-1, 0);
          chop_str(net_sender,'>');
          chop_str(net_sender,'\n');
          }
        else {
          char *cp1, *cp = net_sender;

          for (cp1=mail_sender; isspace(*cp1); cp1++) {}
          for (; *cp1 && !isspace(*cp1); cp1++) *cp++ = *cp1;
          *cp = '\0';
          chop_str(net_sender,'(');
          }
        if (!del_id(id,net_sender)) hist_add(id);
        print_log("(Supersedes %s)",HDR_TEXT(SUPERSEDES),newsgroup);
        }
      strnncpy(itm_fname,itm_fname_m, save_itm_fname,-1, 0);
      }
    }
  if (*itm_fname) sys_remote_send(HDR_TEXT(PATH),HDR_TEXT(NEWSGROUPS),
                                  HDR_TEXT(DISTRIBUTION),itm_fname,
                                  HDR_TEXT(MESSAGE_ID),!mod_add);
  else {
    if (strcmp(newsgroup,junk)) {
      if (!junk_it) {
        if (!fn && !(fn = create_article(NULL,NULL))) return;
        sys_remote_send(HDR_TEXT(PATH),HDR_TEXT(NEWSGROUPS),
                        HDR_TEXT(DISTRIBUTION),fn,HDR_TEXT(MESSAGE_ID),!mod_add);
        print_log("REJECT",0,newsgroup);
        return;
        }
   /* printf("\n"); */
      mail_add_item(fn,junk,-1,linecount,1);
      return;
      }
    }
}

/*
 *  fout_line
 *
 *  Output one header line, nicely wrapped if necessary.
 *
 *  fout_line may be used to write header keyword + header text in one call,
 *  or two calls may be used:
 *    - first to write a keyword:        fout_line(hdr_id, NULL, ...)
 *    - second to write the header text: fout_line(-1, string, ...)
 *  This two-part feature/hack is used in out_headers() to compile PATH.
 */

void fout_line(hdr_id, s, s_l, f)
  int hdr_id; /* header keyword to be written before the string s,
                 or -1 if no header keyword */
  char *s;    /* string to be written */
  int s_l;    /* length of string s if known, otherwise -1 */
  FILE *f;    /* file id */
{
  static int written_size = 0; /* number of chrs already written on this line */
  static int last_hdr_id = -1; /* saved hdr_id from the partial line, or -1   */

  if (hdr_id >= 0) {
    fwrite(fheaders[hdr_id],sizeof(char),fheaders_l[hdr_id],f);
    _ck_put(f);
    written_size += fheaders_l[hdr_id];
    last_hdr_id = hdr_id;
    }
  if (s) {
    int wrap;       /* if true then lines are wrapped to a reasonable size
                       and continuation lines are used (as per RFC 1036);
                       otherwise s is written in one line, regardless of size */
    int pgp_wrap;   /* if true lines are wrapped at every whitespace
                       after the second word (= nonblank string) */
    s_l = (s_l>=0) ? s_l : strlen(s);                   /* actual string size */
    pgp_wrap = (last_hdr_id == X_PGP_SIG);
    wrap     = (last_hdr_id != NEWSGROUPS) &&
               (last_hdr_id != FOLLOWUP_TO) &&
               (last_hdr_id != PATH) &&
               (last_hdr_id != RELAY_VERSION);
    if (pgp_wrap) {
      int len,len1,len2, word_cnt=0;
      while (s_l > 0) {    /* 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,f); _ck_put(f); word_cnt++;
        if (word_cnt > 1) { fputc('\n',f); _ck_put(f); written_size = 0; }
        s += len; s_l -= len;
        }
      }
    else if (wrap) {
      int len;
      char *c, *cp, save_c;
      while (s_l > 80-written_size) {
        save_c = s[78-written_size]; s[78-written_size] = '\0';
        cp = &s[20];                             /* find a good place to wrap */
        if ( ! (  (c = strrchr(cp,' ')) || (c = strrchr(cp,'\t')) ) ) {
	  s[78-written_size] = save_c; /* can't wrap, go write rest of line */
	  break;
          }
        s[78-written_size] = save_c;
        /* c now points to a good separator */
        len = c-s;
        fwrite(s,sizeof(char),len,f); _ck_put(f);
        fputc('\n',f); _ck_put(f);
        written_size = 0;        /* nothing written out yet */
        s = c; s_l -= len;
        }
      }  /* wrap */
    if (s_l > 0) { fwrite(s,sizeof(char),s_l,f); _ck_put(f); }
    fputc('\n',f); _ck_put(f);
    written_size = 0; last_hdr_id = -1;
    }
}

/*
 *  out_header
 *
 *  Output all header lines of an article.
 */

void out_header(fpl)
  FILE *fpl;
{
  int i;

  fout_line(RELAY_VERSION,relay_vers_str,relay_vers_str_l,fpl);
  for (i = 0; i < OHEADERS; ++i) {
    if (i == PATH && !mod_add) {
      fout_line(i,0,0,fpl);         /* first part of the line: header keyword */
      fputs(news_pathname,fpl); _ck_put(fpl);
      fputc('!',fpl); _ck_put(fpl);
      fout_line(-1,HDR_TEXT(i),HDR_TEXT_L(i),fpl);/* second part: header text */
      }
    else if (HDR_TEXT_L(i) > 0) {
      if ((i == APPROVED) && mod_add && !itm_approved) /* no write */;
      else fout_line(i,HDR_TEXT(i),HDR_TEXT_L(i),fpl);
      }
    }
  /* Lines: */
  fout_line(LINES,HDR_TEXT(LINES),HDR_TEXT_L(LINES),fpl);
  /* append all the remaining header lines, unrecognized by ANU-NEWS */
  for (i = 0; i < itm_unk_l; ++i) {
    if (UNK_HDR_TEXT_L(i) > 0)
      fout_line(-1,UNK_HDR_TEXT(i),UNK_HDR_TEXT_L(i),fpl);
    }
  /* put PGP signature at the end (as a convention) */
  if (HDR_TEXT_L(X_PGP_SIG) > 0)
    fout_line(X_PGP_SIG,HDR_TEXT(X_PGP_SIG),HDR_TEXT_L(X_PGP_SIG),fpl);
}

/*
 *  out_body
 *
 *  Write the body of an article out to a file
 */

void out_body(fpl)
  FILE *fpl;
{
  if (!fpt)
    { fwrite(artbuf,sizeof(char),artbuf_l,fpl); _ck_put(fpl); }
  else {
    int len;
    char oline[4*1024];
    if (fseek(fpt,0,0)) _ck_seek(fpt);
    while (!feof(fpt)) {
      len = fread(oline,sizeof(char),sizeof(oline),fpt); _ck_get(fpt);
      if (len>0)
        { fwrite(oline,sizeof(char),len,fpl); _ck_put(fpl); }
      }
    }
}

/*
 *  length_header
 *
 *  Calculates approximate length of all header lines of an article.
 *  (Due to header line wrapping on 'nice' boundaries the header size
 *  is not exact).
 *	Modeled in out_header above ST 6/6/94
 */

int length_header(void)
{
  int i,ll,wrap;
  int total_length = (fheaders_l[RELAY_VERSION] + relay_vers_str_l + 1) +
                     (strlen(news_pathname) + 1);
  for (i = 0; i < OHEADERS; ++i) {
    if ((ll=HDR_TEXT_L(i)) > 0) {
      wrap = (i != NEWSGROUPS) && (i != FOLLOWUP_TO) &&
             (i != PATH) && (i != RELAY_VERSION);
      total_length += fheaders_l[i] + ll+1 + ((!wrap) ? 0 : ll/60*2);
      }
    }
  for (i = 0; i < itm_unk_l; ++i) {
    if ((ll=UNK_HDR_TEXT_L(i)) > 0) total_length += ll+1 + ll/60*2;
    }
  return(total_length);
}


/*
 *  create_article
 *
 *  Create an article file from the headers and body stored in memory
 *
 *  return NULL on error, otherwise return ptr to filename
 *
 *  This routine is called when you want to create a file containing the
 *  news article.  The routine takes two parameters.  fn is a pointer
 *  to a filename string.  This pointer should be NULL if you just want
 *  to create a scratch file version of the article (used before calling
 *  parse_control_item or sys_local_accept if the article exists only
 *  in memory).  The second parameter is xref, which is a pointer to the
 *  xref header string if needed.  This is the one header line that is not
 *  output by out_header.
 *
 *   modified by saul@hnrc.tufts.edu to estimated article size and
 *	pre-extend the article file and open it with multiblocking,
 *	multibuffering, and write behind.
 */

char *create_article(fn,xref)
  char *fn;			/* filename - use scratch file if NULL  */
  char *xref;			/* xref header line for article or NULL */
{
  FILE *fpw;
  int estimated_length;         /* estimated file size in bytes */
  int estimated_length_blocks;  /* estimated file size in disk blocks */
  char alloc_size[32];
  int xref_l = (!xref) ? 0 : strlen(xref);
				/* calculated the estimated length of the
				   article and preextend the file */
  estimated_length = length_header() +
                     (fheaders_l[LINES] + HDR_TEXT_L(LINES) + 1) +
                     (xref ? xref_l+1 : 0) + 1 + body_size + 7;
  /* (MM) better an overestimate than underestimate:       ^^^ fudge factor */
  estimated_length_blocks = (estimated_length+511) / 512;
  sprintf(alloc_size, "alq=%d", estimated_length_blocks);
  if (!fn) fn = scratchfilename;
  sysprv();
  fpw = fopen(fn,"w",alloc_size, "mbc=16","mbf=2","rop=wbh","deq=16","fop=cbt,tef");
  if (!fpw) {
    if (errno == EVMSERR && vaxc$errno == RMS$_CRE)
      if (try_to_make_some_free_space(0,estimated_length_blocks,fn,0)) {
        fpw = fopen(fn,"w",alloc_size, "mbc=16","mbf=2","rop=wbh","deq=16","fop=cbt,tef");
        if (fpw) stat_make_space_retry_success++;
        }
  }
  if (!fpw) {
    nosysprv();
    del_after = 0;
    if (!try_to_quietly_handle_errors) _ck_open_w(fpw,fn);
    print_log("ERROR (Cannot open output file %s)", fn, 0);
    return(NULL);
    }
  if (fn == scratchfilename) scr_files_created++;
/*else chmod(fn,0755);  this call to chmod() wastes over 4% elapsed time during
                        ADD FILE;  we'll leave item file protection to VMS    */
  nosysprv();
  out_header(fpw);
  if (xref) fout_line(-1,xref,xref_l,fpw);
  fputc('\n',fpw); _ck_put(fpw); /* empty line separates header from the body */
  out_body(fpw);
  if (fclose(fpw)) _ck_close(fpw);
/*{ struct stat statb;
 *  stat(fn, &statb);
 *  fprintf(stderr, "%s est = %d, actual = %d\n", fn, estimated_length, statb.st_size);
 *}  */
  return(fn);
}

#if JUNKNODOT
/*
 *  stripnodot
 *
 *  remove all newsgroups not containing '.' in their name
 *  (with the exception of "junk" and "test") from the
 *  comma-separated list of newsgroups.
 *  All nonprintable characters are removed as well.
 *
 *  Expects sizeof(s) to be at least one character larger than s_l.
 *  Function returns new size of the list, which may be same or shorter
 *  than the original.  '\0' is always appended to the list.
 */

static int stripnodot(s, s_l)
  char *s;
  int s_l;                       /* length of string s if known, otherwise -1 */
{
  int j, dst_l, tmp_dst_l, has_dot;

  s_l = (s_l>=0) ? s_l : strlen(s);
  dst_l = tmp_dst_l = 0;  has_dot = 0;
  for (j = 0; j <= s_l; j++) {          /* note: one character beyond s_l !!! */
    if (j >= s_l || s[j] == ',') {
      s[tmp_dst_l] = '\0';
      if (!has_dot && strcmp(&s[dst_l],"junk") && strcmp(&s[dst_l],"test"))
        tmp_dst_l = dst_l;                           /* forget about this one */
      else { s[tmp_dst_l++] = ','; dst_l = tmp_dst_l; }            /* save it */
      has_dot = 0;
      }
    else if (isgraph(s[j])) {
      s[tmp_dst_l++] = s[j];
      if (s[j] == '.') has_dot++;
      }
    }
  if (dst_l > 0) dst_l--;                            /* delete the last comma */
  s[dst_l] = '\0'; return(dst_l);
}
#endif

/*
 *  count_newsgroups
 *
 *  count number of words in a comma-separated list
 */

int count_newsgroups(s, s_l)
  char *s;
  int s_l;    /* length of string s if known, otherwise -1 */
{
  int j, cnt = 0, in_word = 0;

  s_l = (s_l>=0) ? s_l : strlen(s);
  for (j = s_l; j > 0; j--,s++) {
    if (*s == ',') in_word = 0;
    else if (isgraph(*s)) { if (!in_word) cnt++;  in_word = 1; }
    }
  return(cnt);
}

/*
 *  process_item
 *
 *  Rebuild an item file with all necessary headers
 */

static void process_item(void)
{
  int status;
  char *xx,
       *cp1,
       *cp2,
       lg[132],
       *pa,
       newsgroups[NEWSGROUPS_MAX_SIZE],
       post_address[132],
       mod_group[132];
  unsigned int g;
  int newsgroups_l;
  int self_mod = 0;
  int no_post = 0;
  int crosspost_cnt;

  *post_address = '\0';
  news_assert(!log_file_line_not_terminated);
  news_assert(!log_file_article_id_written);

  newsgroups_l = min(HDR_TEXT_L(NEWSGROUPS), sizeof(newsgroups)-1);
  memcpy(newsgroups,HDR_TEXT(NEWSGROUPS),newsgroups_l);
  newsgroups[newsgroups_l] = '\0';

  crosspost_cnt = count_newsgroups(HDR_TEXT(NEWSGROUPS),HDR_TEXT_L(NEWSGROUPS));

#if ADDFILE_STATISTICS_ENABLE
  if (HDR_TEXT_L(NEWSGROUPS) > stat_ng_size_peak)
    stat_ng_size_peak = HDR_TEXT_L(NEWSGROUPS);
  if (crosspost_cnt > stat_ng_crosspost_peak)
    stat_ng_crosspost_peak = crosspost_cnt;
  stat_ng_crosspost_total += crosspost_cnt;
#endif

  if (ADDFILE_NEWSGROUPS_SIZE_LIMIT) {
    if (HDR_TEXT_L(NEWSGROUPS) > ADDFILE_NEWSGROUPS_SIZE_LIMIT) {
      char tmpstr[100];
      sprintf(tmpstr,"newsgroups list size (%d) longer than %d chars limit",
                     HDR_TEXT_L(NEWSGROUPS), ADDFILE_NEWSGROUPS_SIZE_LIMIT);
      print_log("JUNK (%s)",tmpstr,HDR_TEXT(NEWSGROUPS));
      strcpy(newsgroups,junk); newsgroups_l = strlen(newsgroups);
      }
    }
  if (ADDFILE_NEWSGROUPS_CROSSPOSTING_LIMIT) {
    if (crosspost_cnt > ADDFILE_NEWSGROUPS_CROSSPOSTING_LIMIT) {
      char tmpstr[100];
      sprintf(tmpstr,"crossposted to %d newsgroups, exceeded limit of %d",
                      crosspost_cnt, ADDFILE_NEWSGROUPS_CROSSPOSTING_LIMIT);
      print_log("JUNK (%s)",tmpstr,HDR_TEXT(NEWSGROUPS));
      strcpy(newsgroups,junk); newsgroups_l = strlen(newsgroups);
      }
    }

  lower_case(newsgroups);
  xx = (char *) aliases(newsgroups,newsgroups_l); newsgroups_l = strlen(xx);
  news_assert(newsgroups_l < sizeof(newsgroups));
  memcpy(newsgroups,xx,newsgroups_l); newsgroups[newsgroups_l] = '\0';

#if JUNKNODOT
  if (net_news && !*force_newsgroup) {
    newsgroups_l = stripnodot(newsgroups,newsgroups_l);
    if (newsgroups_l <= 0) {
      print_log("REJECT (<JUNKNODOT>)",0,newsgroups);
      if (!mod_add) return;
      }
    }
#endif
  if (mod_add) {
    xx = news_strdup(newsgroups,newsgroups_l);
    for (cp1=xx; cp1 && *cp1; cp1=cp2) {
      cp2 = chop_str_plus(cp1,',');
      util_cvrt(lg,cp1);
      if ((g = ga_exact_name(lg))
          && (ga[g]->grp_flags & NEWS_M_MAILMODERATE)) {
        if (strcmp(mod,(pa = moderator_address(ga[g]->grp_name)))) {
          strnncpy(mod_group,sizeof(mod_group), ga[g]->grp_name,-1, 0);
          strnncpy(post_address,sizeof(post_address), pa,-1, 0);
          }
        else {
          self_mod = 1;
          *post_address = '\0';
          store_header(APPROVED,mod,-1);
          itm_approved = 1;
          }
        }
      else if (g && (ga[g]->grp_flags & NEWS_M_MOD_ACCESS)) {
        self_mod = 1;
        *post_address = '\0';
        store_header(APPROVED,mod,-1);
        itm_approved = 1;
        }
      }
    news_free(xx);
    if (!strcmp(newsgroups,"junk")) {
      *post_address = '\0';
      no_add = 1; no_post = 1;
      }
    else if (!self_mod) {
      print_log("REJECT (From: %s, Item does not reference moderated newsgroup)",
                HDR_TEXT(FROM), newsgroups);
      no_add = 1; no_post = 1; *post_address = '\0';
      }
    else if (*post_address) {
      if (log_file_line_not_terminated) {
        putchar('\n');
        log_file_line_not_terminated = log_file_article_id_written = 0;
        }
      printf("Add %s, %s\n",HDR_TEXT(FROM),HDR_TEXT(MESSAGE_ID));
      printf("Additional moderated newsgroup specified: %s\n",mod_group);
      printf("  Newsgroups: %s\n",newsgroups);
      printf("  Next Moderator: %s\n",post_address);
      strcpy(err_oline,"Post item to next moderator ? [y]");
      status = get_input(&usr_inp_dsc,c$dscl(&err_oline_d),&usr_inp_l);
      if (status == SMG$_EOF || status == RMS$_EOF) *usr_inp = 'n';
      if (!((!usr_inp_l) || (tolower(*usr_inp) == 'y')))
        { no_post = 1; *post_address = '\0'; }
      no_add = 1;
      log_file_line_not_terminated = log_file_article_id_written = 0;
      }
    }
  if (!no_post) {
    { char tmpstr[16];
      store_header(LINES,tmpstr,sprintf(tmpstr,"%d",line_count));
    }
    if (!no_add)
      mail_add_item(NULL,newsgroups,newsgroups_l,line_count,control_squelch);
    else if (mod_add && *post_address) {
      if (create_article(scratchfilename,NULL)) {
        if (log_file_line_not_terminated) {
          putchar('\n');
          log_file_line_not_terminated = log_file_article_id_written = 0;
          }
        sprintf(err_oline,
          "Post to next Moderator (%.256s)\nSpawning MAIL task ..\n",
          post_address);
        call_mail(scratchfilename,HDR_TEXT(SUBJECT),add_transform(post_address),
          err_oline);
        }
      }
    }
  if (log_file_line_not_terminated) {
    putchar('\n');
    log_file_line_not_terminated = log_file_article_id_written = 0;
    }
}

void supply_default_header(void)
{
  struct tm *stm;
  time_t cur_time;
  int tmpstr_l;
  char *p;
  char tmpstr[256];

  if (!HDR_TEXT_L(FROM))
    store_header(FROM,"** Sender Unknown **",-1);
  if (!HDR_TEXT_L(DATE)) {
    cur_time = time(NULL);
    p = ctime(&cur_time);
    p += 4;
    stm = localtime(&cur_time);
    tmpstr_l = sprintf(tmpstr,"%d %.3s %04d %02d:%02d:%02d ",
                       stm->tm_mday, p, stm->tm_year + 1900,
                       stm->tm_hour, stm->tm_min, stm->tm_sec);
    strnncat(tmpstr,sizeof(tmpstr),tmpstr_l, news_timezone,-1, &tmpstr_l);
    store_header(DATE,tmpstr,tmpstr_l);
    }
  if (!HDR_TEXT_L(NEWSGROUPS) && strcmp(def_newsgroup,junk))
    store_header(NEWSGROUPS,def_newsgroup,-1);
  if (!HDR_TEXT_L(SUBJECT))
    store_header(SUBJECT,"<None>",-1);
  if (!HDR_TEXT_L(PATH)) {
    if (!mod_add) store_header(PATH,usr_username,-1);
    else {
      strnncpy(tmpstr,sizeof(tmpstr), news_pathname,-1, &tmpstr_l);
      strnncat(tmpstr,sizeof(tmpstr),tmpstr_l, "!",1,   &tmpstr_l);
      strnncat(tmpstr,sizeof(tmpstr),tmpstr_l, usr_username,-1, &tmpstr_l);
      store_header(PATH,tmpstr,tmpstr_l);
      }
    }
}

/*
 *  init_strings
 *
 *  Reset all globals at the start of each item
 */

void init_strings(void)
{
  int j;

  news_assert(!itm_text_tmp_in_use);
  if (itm_text_m > 300 /*20*1024*/) {
   /* if buffer has grown way too large, we just discard it,
    * we'll get another one automatically (in store_header() ).
    * This probably happened because of some weird article
    * with bad header lines and is unlikely to happen again
    * in the near future.
    */
    news_free(itm_text); itm_text = NULL; itm_text_m = 0;
    }
  itm_text_l = 0;
  /* first '\0' should permanently be there to cater for 'null pointers'
     (actually, indices with value 0) */
  append_itm_text_tmp(0,0,1); /* alloc buff if necessary and put '\0' in it */
  itm_text_l = itm_text_tmp_l; itm_text_tmp_in_use = 0;/* make it permanent */
  for (j = 0; j < NHEADERS; ++j)
    { itm_hdr_ind[j] = 0; HDR_TEXT_L(j) = 0; }
  no_add = loc_junk;
}

/*
 * append_line
 *
 * append a line from the body of an article to either the memory buffer
 * or the temporary file
 */

static int append_line(buf,buf_l)
  char *buf;    /* pointer to stuff to append to article */
  int buf_l;    /* length of buf if known, otherwise -1  */
{
  int buf_length = (buf_l>=0) ? buf_l : strlen(buf);

  if (temp_file_error) return(1);

  if (fpt)                                  /* file already open, write to it */
    { fwrite(buf,sizeof(char),buf_length,fpt); _ck_put(fpt); }
  else if (buf_length <= ART_BUFSIZE-artbuf_l)       /* still room in *artbuf */
    { memcpy(&artbuf[artbuf_l],buf,buf_length); artbuf_l+=buf_length; }
  else {                          /* no more room in the artbuf, dump to file */
#if ADDFILE_STATISTICS_ENABLE
    stat_artbuf_too_small++;
#endif
    if (!(fpt = fopen(bigitem_scratchfilename,"w+", "mbc=32",
						    "mbf=2","rop=wbh"))) {
      temp_file_error = 1; del_after = 0;
      if (!try_to_quietly_handle_errors) _ck_open_w(fpt,bigitem_scratchfilename);
      print_log("ERROR (Cannot open scratch file %s)", bigitem_scratchfilename, 0);
   /* printf("\tError: Add - Cannot open scratch file %s\n", bigitem_scratchfilename); */
      return(1);
      }
    fwrite(artbuf,sizeof(char),artbuf_l,fpt); _ck_put(fpt);
    artbuf_l = 0;
    fwrite(buf,sizeof(char),buf_length,fpt); _ck_put(fpt);
    }
  return(0);
}

/*  scan_body
 *
 *  pick off text body of message;
 *
 *  also adds size of the article body to 'body_size' and 'line_count'
 */

static void scan_body(void)
{
  /* clearing of body_size and line_count is done by the caller
   * to make possible parsing of multiple mail headers where an
   * illegal header line counts as the first body line
   */
  for (; (!infile_eom && !infile_eof); get_line()) {
    if (infile_initial_fragment) line_count++;
    append_line(add_inline,add_inline_l); body_size += add_inline_l;
    }
}

/*  body_orphan
 *
 *  handles a line (stored in temporary buffer) that appeared as a
 *  header line but was syntactically rejected and as such counts
 *  as the first line of the message body.
 */

static void body_orphan(void)
{
  int s_l;

  news_assert(itm_text_tmp_in_use);
  s_l = itm_text_tmp_l-itm_text_l-1;
  itm_text_tmp_l--;                           /* chop off trailing '\0' */
  itm_text[itm_text_tmp_l++] = '\n';  s_l++;  /* append '\n' */
  append_line(&itm_text[itm_text_l],s_l); body_size += s_l; line_count++;
  itm_text_tmp_in_use = 0;
}

/*
 *  scan_header_line
 *
 *  Parse a header line (tentatively stored in the temporary part
 *  of *itm_text), permanently storing only acceptable NEWS headers;
 *
 *  return: 1 = syntactically correct header; 0 = failure
 */

static int scan_header_line(void)
{
  char *s = &itm_text[itm_text_l];
  int s_l = itm_text_tmp_l-itm_text_l-1;

  char head_word[32], *h = head_word;
  int i, ip, h_l, header_recognized;

  news_assert(itm_text_tmp_in_use);
  ip = 0;
  while (ip<s_l && s[ip]!=':' && isgraph(s[ip])) ip++;

/*	The strict check is deliberately omitted - the strict check
	for valid header keywords: "<word><colon><space>" is violated
	by some backward systems who omit the space following the colon.
	The code as it stands now gently ignores the problem rather than
	junking on a malformed header  - gh  15/4/91		
*/
  if (ip >= s_l) { body_orphan(); return(0); }
  else if (s[ip] != ':') {
    if (!strncmp(s,"From ",5)) { itm_text_tmp_in_use = 0; return(1); }
    body_orphan(); return(0);
    }
  else /* (s[ip] == ':') */ {
    h_l = min(ip, sizeof(head_word)-1);
    if (h_l<=0) { body_orphan(); return(0); }
    memcpy(h,s,h_l); h[h_l] = '\0';
    lower_case(h);
    ip++;
    while (ip<s_l && isspace(s[ip])) ip++;
    header_recognized = find_header_name(h,h_l,&i,0);
    if (!header_recognized && !strcmp(h,"subj")
        && (HDR_TEXT_L(SUBJECT) == 0 || !strcmp(HDR_TEXT(SUBJECT),"<None>"))) {
      header_recognized = 1; i = SUBJECT;
      }
    if (header_recognized) {
      if (i == PATH && mod_add);   /* do not save */
      else store_header(i,&s[ip],s_l-ip);
      itm_text_tmp_in_use = 0;
      }
    else if (!n_stripping && s[0] == 'N') {
      ++h; h_l--;
      header_recognized = find_header_name(h,h_l,&i,0);
      if (header_recognized) {
        /* We just realized that we are receiving the 'N'-prefixed batch */
        int j,k = 0;

        if (i == PATH && mod_add);   /* do not save */
        else store_header(i,&s[ip],s_l-ip);
        itm_text_tmp_in_use = 0;

        /* It may be that the previous foreign headers (unrecognized
         * by ANU-NEWS) were already stored in itm_unk_hdr* structure,
         * so we have to strip off the leading 'N' from the already saved
         * foreign headers and discard the ones not starting with an 'N'.
         */
        for (j = 0; j < itm_unk_l; ++j) {
          if (UNK_HDR_TEXT(j)[0] != 'N')
            itm_unk_hdr_l[j] = 0;                 /* discard that header line */
          else {                                 /* strip off the leading 'N' */
            itm_unk_hdr_ind[k] = itm_unk_hdr_ind[j] + 1;
            itm_unk_hdr_l[k++] = itm_unk_hdr_l[j]   - 1;
            }
          }
        itm_unk_l = k;
        if (!n_stripping && add_inline_l > 0 && add_inline[0] == 'N') {
         /* we have to deal with the next line stored in add_inline
          * manually since get_line() has already dealt with it
          * and stripping was not enabled at that point.
          */
          memmove(add_inline,add_inline+1,--add_inline_l);
          }
        n_stripping = 1;
        }
      }
    if (!header_recognized && itm_unk_l < MAX_UNK) {    /* save unkown header */
     /* First check to see if the previous stored header text appears as a
      * leading substring in this header - and if so, discard the previous one.
      *   (don't know what good does that do, but I keeped this piece of the
      *    old code -- mark.martinec@ijs.si)
      */
      if ((itm_unk_l>0) && (s_l >= UNK_HDR_TEXT_L(itm_unk_l-1))
         && (!strncmp(s,UNK_HDR_TEXT(itm_unk_l-1),UNK_HDR_TEXT_L(itm_unk_l-1))))
         itm_unk_l--;
      store_unk_header(itm_unk_l,s,s_l);
      itm_unk_l++;
      }
    itm_text_tmp_in_use = 0;
    }
  return(1);
}

/*
 *  scan_header
 *
 *  pick off a set of header lines;
 *
 *  returns 1 if header finished-off normally with an empty line,
 *  or 0 if syntactically illegal header line was encountered;
 *  in the later case the line was already written out as the first line
 *  of the body by scan_header_line();
 *
 *  also adds header size to 'header_size' variable (for statistical purposes)
 */

static int scan_header(void)
{
  int abnormal_exit = 0;

  /* header_size = 0;  counter reset is done by the caller
                       to allow for multiple header groups */
  news_assert(!itm_text_tmp_in_use);

  if (infile_eom) abnormal_exit = 1;
  for (; (!infile_eof && !infile_eom); get_line()) {
    header_size += add_inline_l;
    if (itm_text[itm_text_tmp_l-1] == '\n') itm_text_tmp_l--;  /* chop off \n */
    if (!infile_initial_fragment) {          /* remainder of the split buffer */
      news_assert(itm_text_tmp_in_use);
      append_itm_text_tmp(add_inline,add_inline_l,0);
      }
    else if ((add_inline_l < 1) || (add_inline_l == 1 && add_inline[0] == '\n'))
      break;                     /* empty line ('\n' only ) terminates header */
    else if ((add_inline[0] == ' ') || (add_inline[0] == '\t')) {
                        /* append continuation line including the first space */
      if (itm_text_tmp_in_use)            /* is there anything to append to ? */
        append_itm_text_tmp(add_inline,add_inline_l,0);
      else {             /* this is not a continuation to a valid header line */
        abnormal_exit = 1;            /* must be the first line of an article */
        break;
        }
      }
    else {                 /* this is the real beginning of a new header line */
      if (itm_text_tmp_in_use) {    /* first handle the previous header line  */
        itm_text[itm_text_tmp_l++] = '\0';
        /* parse header, release itm_text_tmp_in_use */
        if (!scan_header_line()) {
          abnormal_exit = 1;
          break;          /* not a syntactically valid header line, break out */
          }
        }
      /* start saving the new line (or the beginning of a new line) in buffer */
      news_assert(!itm_text_tmp_in_use);
      append_itm_text_tmp(add_inline,add_inline_l,0);
      }
    }
    /* handle the last remaining header line in the buffer */
    if (itm_text_tmp_in_use) {
      itm_text[itm_text_tmp_l++] = '\0';
      if (!scan_header_line()) abnormal_exit = 1;
      }
    if (!abnormal_exit && !infile_eof && !infile_eom) {
      int j;
      /* step over empty line separating article header from the body */
      get_line();
      /* skip over any empty lines at the start of the body */
      for (; !infile_eof && !infile_eom; get_line()) {
        for (j = 0; j<add_inline_l && isspace(add_inline[j]); j++) ;
        if (j < add_inline_l) break;
        }
      }
    return(!abnormal_exit);
}

#if ADDFILE_STATISTICS_ENABLE
void update_article_statistics(void)
{
  int j;
  int article_size = header_size+1+body_size;

  stat_articles++;
  stat_articles_cumulative_size += article_size;

  if (header_size  > stat_hdr_size_peak)     stat_hdr_size_peak = header_size;
  if (article_size > stat_article_size_peak) stat_article_size_peak = article_size;

  j = header_size / STAT_HDR_BIN_SIZE;
  if (j >= STAT_HDR_SIZE_BINS_M) j = STAT_HDR_SIZE_BINS_M-1;
  stat_hdr_size_bins[j]++;

  j = article_size / STAT_ART_BIN_SIZE;
  if (j >= STAT_ART_SIZE_BINS_M) j = STAT_ART_SIZE_BINS_M-1;
  stat_art_size_bins[j]++;
}
#endif


/*
 *  lock_filename
 *
 *  Acquires an exclusive lock on a resource whose name
 *  is constructed from the given filename. This is used
 *  by ANU-NEWS when several processes may be doing ADD FILE
 *  on the same set of batch files (e.g. using wildcard).
 *
 *  This all complication would be unnecessary if fclose()
 *  would allow setting the FAB$V_DLT and allow us to delete
 *  a file in the same operation.  Doing fclose() and then delete()
 *  introduces a window during which another process could open
 *  the same file.  Doing it the other way around is not possible
 *  (delete fails) if exclusive lock on file ("shr=nil")
 *  is specified during fopen().
 *
 *  NOTE: All ANU-NEWS jobs running ADD FILE on the same set of files
 *        should be running under the same gid (group id) to enable
 *        locks to be recognized between jobs !
 *
 *  Function returns immediately - never enqueues a lock.
 *  Return value is 1 if lock was successfully acquired, otherwise 0.
 *                              mark.martinec@nsc.ijs.si, March 1996
 */

int lock_filename(fnam, lock_id)
  char *fnam;   /* fully specified file name */
  int *lock_id; /* returned lock id to be used with unlock_filename() */
{
  int status, j;
  struct {
    short lksb_l_status;
    short lksb_l_reserved;
    int lksb_l_lock_id;
  } lksb;
  static char resname[31];  /* name of the resource (31 char max, no '\0') */
  static struct dsc$descriptor_const_s resnam_dsc =
    { 31, DSC$K_DTYPE_T, DSC$K_CLASS_S, resname };

  /* fold the arbitrary-size name into 31-char resource name */
  strncpy(resname,"ANU-NEWS",sizeof(resname));
  for (j=0; *fnam; j = (j+1) % sizeof(resname)) { resname[j] ^= *fnam++; }

  *lock_id = 0;
  /* try to acquire exclusive lock */
  status = sys$enqw(0, LCK$K_EXMODE, &lksb, LCK$M_NOQUEUE,
                    &resnam_dsc, 0, 0,0,0, PSL$C_USER, 0);
  if (status&1) status = lksb.lksb_l_status;
  if (status&1)                                        /* successfully locked */
    { *lock_id = lksb.lksb_l_lock_id; return 1; }
  else if (status != SS$_NOTQUEUED) _c$cks(status);
  return 0;                                            /* failed to lock */
}

void unlock_filename(lock_id)
  int *lock_id;
{
  int status;
  _c$cks(sys$deq(lock_id, 0, PSL$C_USER, 0));
}


/*
 *  add_file
 *
 *  Read in file "fnam" into the news system.
 */

int add_file(fnam, exec_sw)
  char *fnam;
  int exec_sw;
{
  struct stat statb;
  int lock_id;

  if (!*fnam) {
    printf("\tError: Add - No filename specified\n");
    return(0);
    }
  if (! lock_filename(fnam, &lock_id) ) {
    printf("Add - Skipping locked file:  %s\n", fnam);
    return(0);
  }
/*compared to other ANU-News i/o this one is so fast that it is worth */
/*saving some buffer space here  - Feb 1996, mark.martinec@nsc.ijs.si */
/*if (!(fpr = fopen(fnam,"r","shr=nil","mbc=64","mbf=2","rop=rah"))) {*/
  if (!(fpr = fopen(fnam,"r","shr=nil","mbc=32"))) {
    /* ignore certain errors, probably another ADD FILE process
       is running on the same set of batch files */
    if (errno==EVMSERR && vaxc$errno==RMS$_FLK)
    /* NOTE: this should never happen since we now call lock manager explicitly
             before even trying to open a file - but just in case ... */
      printf("Add - Skipping locked file (somebody ignored our manual lock!?): %s\n", fnam);
    else if (errno==ENOENT && vaxc$errno==RMS$_FNF)
      printf("Add - Skipping deleted file: %s\n", fnam);
    else {
      if (!try_to_quietly_handle_errors) _ck_open_r(fpr,fnam);
      printf("\tError: Add - Cannot read file %s, %s\n", fnam,strerror(errno,vaxc$errno));
      }
    unlock_filename(lock_id);
    return(0);
    }
  if (fstat(fileno(fpr), &statb)) statb.st_size = 0;
  printf("Add - Reading file: %s (%lu bytes)\n", fnam, statb.st_size);
  *add_inline = *look_ahead = '\0';
  add_inline_l = look_ahead_l = 0; look_ahead_in_use = 0;
  infile_eof = look_ahead_eof = 0; n_stripping = 0;
  infile_initial_fragment = previous_line_complete = 1;
  scr_files_created = 0;
  get_line();
  /* skip leading \f\n */
  while (!infile_eof && add_inline_l == 2 && !strcmp(add_inline,"\f\n"))
    get_line();
  /* loop goes by articles */
  while (!infile_eof) {
    header_size = body_size = line_count = 0;
    infile_eom = 0;
    init_strings();
    itm_unk_l = artbuf_l = 0;
    temp_file_error = n_stripping = 0;
    itm_approved = accept_it;

    if (!strncmp(add_inline,"#! rnews ",9)) get_line();
    if (!strncmp(add_inline,"N#! rnews ",10)) { n_stripping = 1; get_line(); }

    if ( debug_switch ) printf("<Headers:>\n");

    /* merge all kinds of headers until a line not looking like
       a valid header line is hit or a header group which contains
       'Newsgroups:' header line is encountered */
    while (scan_header() && !HDR_TEXT_L(NEWSGROUPS)) ;

    if (*force_newsgroup) store_header(NEWSGROUPS,force_newsgroup,-1);
    supply_default_header();

    no_add = 0;
    if (HDR_TEXT_L(APPROVED) > 0) itm_approved = 1;
    if ( debug_switch ) printf("<Body:>\n");
    scan_body();
    if ( debug_switch ) printf("<EOM>\n");
    if (HDR_TEXT_L(NEWSGROUPS) > 0 && !temp_file_error) {
#if ADDFILE_STATISTICS_ENABLE
      update_article_statistics();
#endif
      process_item();
      }
    if (fpt) {
      if (fclose(fpt)) _ck_close(fpt);
      fpt = 0;
      if (delete(bigitem_scratchfilename)) _ck_delete(bigitem_scratchfilename);
      }
    for (; scr_files_created > 0; scr_files_created--) {
      if (delete(scratchfilename)) _ck_delete(scratchfilename);
      }
    }
  if (fclose(fpr)) _ck_close(fpr);
  if (del_after)
    { if (delete(fnam)) _ck_delete(fnam); }
  unlock_filename(lock_id);
  return(1);
}

int do_add_mail()
{
  return(0);
}

#if ADDFILE_STATISTICS_ENABLE
void init_statistics(void)
{ int status;
  int j;
  stat_articles = stat_articles_cumulative_size = 0;
  stat_artbuf_too_small = stat_itm_text_too_small = 0;
  stat_itm_text_peak = stat_article_size_peak = stat_hdr_size_peak = 0;
  stat_ng_size_peak = stat_ng_crosspost_peak = stat_ng_crosspost_total = 0;
  for (j = 0; j < STAT_HDR_SIZE_BINS_M; j++) stat_hdr_size_bins[j] = 0;
  for (j = 0; j < STAT_ART_SIZE_BINS_M; j++) stat_art_size_bins[j] = 0;
  _c$cks(sys$gettim(&stat_start_time));
  stat_start_cpu = clock();
/* the following are global variables: */
  stat_make_space_called = stat_make_space_succeeded = stat_make_space_retry_success = 0;
}
#endif

#if ADDFILE_STATISTICS_ENABLE
void report_statistics(void)
{ int status;
  int j,cum,tot;
  int dt100;    /* delta time in 1/100 s units (still fits into one longword) */
  unsigned long stat_end_time[2], interval[2];
  time_t currtime;
  float a;
  double cpu;

  /* compute processor time */
#ifdef CLOCKS_PER_SEC
  cpu = (clock()-stat_start_cpu) / CLOCKS_PER_SEC;
#else
  cpu = (clock()-stat_start_cpu) / CLK_TCK;
#endif
  /* compute elapsed time */
  _c$cks(sys$gettim(&stat_end_time));
  _c$cks(lib$sub_times(&stat_end_time,&stat_start_time,&interval));
  a = 1e-5; /* convert delta time into 0.01 s units (sys time unit is 100 ns) */
  _c$cks(lib$multf_delta_time(&a,&interval)); dt100 = -interval[0];

  currtime = time(NULL);
  printf("- Finished at %s", asctime(localtime(&currtime))); /*'\n'not needed */
  printf("- Number of articles processed:  %d\n", stat_articles);
  if (stat_articles > 0) {
    printf("- Processing speed:  %.2f articles per second (elapsed time)\n",
           (100.0*stat_articles)/dt100);
    printf("- CPU load:  %.1f %%  (processor_time / elapsed_time)\n",
           (10000.0*cpu)/dt100);
    printf("             %.1f ms per article (processor time)\n",
           (cpu*1000.0/stat_articles));
    }
  printf("- Crosspostings:\n");
  printf("    Peak size of article 'Newsgroups:' header line:  %d characters\n", stat_ng_size_peak);
  printf("    Peak number of newsgroups an article was crossposted to:  %d newsgroups\n", stat_ng_crosspost_peak);
  if (stat_articles > 0)
    printf("    Average number of newsgroups an article was crossposted to:  %.2f newsgroups\n",
           (float)stat_ng_crosspost_total / (float)stat_articles);
  for (tot=j=0; j < STAT_HDR_SIZE_BINS_M; j++) tot += stat_hdr_size_bins[j];
  news_assert_nonfatal(tot == stat_articles);
  printf("- Headers:\n");
  if (tot > 0) {
    char fmt1[] = "    %2d %% of all headers were smaller than %d characters\n";
    int a50=0, a95=0;
/*  int a05=0, a99=0;  */
    for (cum=j=0; j < STAT_HDR_SIZE_BINS_M-1; j++) {
      cum += stat_hdr_size_bins[j]; a = 100.0*cum/tot;
/*    if (!a05 && a >=  5) { printf(fmt1,  5, (j+1)*STAT_HDR_BIN_SIZE); a05++; } */
      if (!a50 && a >= 50) { printf(fmt1, 50, (j+1)*STAT_HDR_BIN_SIZE); a50++; }
      if (!a95 && a >= 95) { printf(fmt1, 95, (j+1)*STAT_HDR_BIN_SIZE); a95++; }
/*    if (!a99 && a >= 99) { printf(fmt1, 99, (j+1)*STAT_HDR_BIN_SIZE); a99++; } */
      }
    }
  printf("    Maximal size of an article header:    %d characters\n", stat_hdr_size_peak);
/*printf("    Number of times the header size exceeded itm_text buffer size\n"); */
/*printf("      and the buffer was extended:  %d\n", stat_itm_text_too_small);   */
  printf("    Peak size of itm_text buffer:  %d characters\n", stat_itm_text_peak);

  for (tot=j=0; j < STAT_ART_SIZE_BINS_M; j++) tot += stat_art_size_bins[j];
  news_assert_nonfatal(tot == stat_articles);
  if (tot > 0) {
    char fmt1[] = "    %2d %% of all articles were smaller than %d characters\n";
    int a05 = 0, a50 = 0, a95 = 0, a99 = 0;
    printf("- Articles (header+body):\n");
    for (cum=j=0; j < STAT_ART_SIZE_BINS_M-1; j++) {
      cum += stat_art_size_bins[j]; a = 100.0*cum/tot;
      if (!a05 && a >=  5) { printf(fmt1,  5, (j+1)*STAT_ART_BIN_SIZE); a05++; }
      if (!a50 && a >= 50) { printf(fmt1, 50, (j+1)*STAT_ART_BIN_SIZE); a50++; }
      if (!a95 && a >= 95) { printf(fmt1, 95, (j+1)*STAT_ART_BIN_SIZE); a95++; }
      if (!a99 && a >= 99) { printf(fmt1, 99, (j+1)*STAT_ART_BIN_SIZE); a99++; }
      }
    printf("    Maximal article size:  %d characters\n",stat_article_size_peak);
    if (stat_articles > 0)
      printf("    Average article size:  %d characters\n",
             stat_articles_cumulative_size / stat_articles);
    printf("    Number of times the article size exceeded buffer size and a\n");
    printf("      scratch file was used:  %d\n", stat_artbuf_too_small);
  }
  if (stat_make_space_succeeded > 0) {
    printf("- Requests for making available more disk space after item file creation failed:\n");
    printf("    No. of requests: %d, of which %d caused some action followed by a retry\n",
           stat_make_space_called, stat_make_space_succeeded);
    printf("    No. of times the file creation retry succeeded: %d, %.1f %%\n",
           stat_make_space_retry_success,
           (100.0*stat_make_space_retry_success)/stat_make_space_succeeded);
    }
  printf("\n");
}
#endif

static int get_qualifier_list (
  const char *qual,  /* qualifier name */
  char *str,         /* returned string (value of the qualifier) */
  int *str_len,      /* returned string length (or NULL if unwanted) */
  int str_size,      /* sizeof(str)  (including space for terminating '\0') */
  const char *default_str,  /* default string or NULL;
                        typically a value of the global qualifier
                        in case we are parsing positional qualifiers;
                        the default will be copied into str
                        if there are no explicit qualifier values */
  int default_str_len  /* length of default_str or -1 if length is unknown */
  )  /* return value: number of elements of the qualifier's value */
{
  int status, n_elem, str_l;
  unsigned short tmpstr_len;
  char tmpstr[256];
  $DESCRIPTOR(tmpstr_dsc,tmpstr);
  struct dsc$descriptor_const_s qual_dsc
    = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL};
  qual_dsc.dsc$w_length = strlen(qual);
  qual_dsc.dsc$a_pointer = qual;

  if (default_str_len < 0)
    default_str_len = (default_str ? strlen(default_str) : 0);
  n_elem = str_l = 0;
  while ((status=cli$get_value(&qual_dsc,&tmpstr_dsc,&tmpstr_len)) & 1) {
    if (str_l+1+tmpstr_len >= str_size)
     { _ck_sys(NEWS$_STRTRUNC,(int)&qual_dsc,0); break; }
    if (n_elem) str[str_l++] = ',';
    n_elem++;
    memcpy(&str[str_l],tmpstr,tmpstr_len); str_l += tmpstr_len;
    }
  if (n_elem<=0 && default_str_len>0) {   /* use the default string */
    if (default_str_len >= str_size) _ck_sys(NEWS$_STRTRUNC,(int)&qual_dsc,0);
    str_l = min(default_str_len,str_size-1);
    memcpy(str,default_str,str_l);
    n_elem = 1;
    }
  str[str_l] = '\0';
  if (str_len) *str_len = str_l;
  return n_elem;
}

/*
 *  do_add_net -> calls dan_1 at execution level 1
 *
 * Add network forwarded news into the local database
 */

static int dan_1(void)
{
  int status;
  char fnam[256], rnam[255];
  unsigned short fnam_len;
  char def_newsgroup_global[1024]; /* global qualifier value for DEFNEWSGROUP */
  char force_newsgroup_global[1024];  /* global qualifier value for NEWSGROUP */
  int def_newsgroup_global_len, force_newsgroup_global_len;
  int report_stat;                            /* turn on statistics reporting */
  int savscrn = 0, mfo = mailfile_open;
  struct FAB *context = 0;
  $DESCRIPTOR(fnam_dsc,fnam);
  $DESCRIPTOR(rnam_dsc,rnam);
  $DESCRIPTOR_CONST(add_dsc,fnam);

#if ADDFILE_STATISTICS_ENABLE
  init_statistics();
#endif
  { struct stat s_stat;
    int sts = stat(PGPCALL_FILE, &s_stat);
    pgp_verification_available = (sts == 0);
    if (pgp_verification_available) {
                     /* quick tryout of the procedure, checks its environment */
      char err_msg[512];
      int j,pgp_code;
      const char *known_headers[NHEADERS];
      for (j = 0; j < NHEADERS; ++j) known_headers[j] = "";
      pgp_code = pgp_verify(known_headers, "", "xxxblabla",
                            err_msg, sizeof(err_msg), 0);
      if (pgp_code == 1 || pgp_code == 11) { /* ok */ }
      else {
        sprintf(err_oline,
                "%s detected errors, disabling PGP verification", PGPCALL_FILE);
        err_line(err_oline);
        strcpy(err_oline, "Problem detected: ");
        if (pgp_code != 255)
          sprintf(&err_oline[strlen(err_oline)], "code %d, ", pgp_code);
        err_oline_cat(err_msg); err_line(err_oline);
        pgp_verification_available = 0;
        }
      }
  }
  log_file_line_not_terminated = log_file_article_id_written = 0;
  log_every_article = (cli$present(c$dsc("LOG")) & 1);
  report_stat = (cli$present(c$dsc("STATISTICS")) & 1);
  debug_switch = (DEBUG || (cli$present(c$dsc("DEBUG")) & 1));

  if ((mod_add = (cli$present(c$dsc("MODERATOR")) & 1)) != 0) {
    strnncpy(mod,sizeof(mod), usr_username,-1, 0);
    strnncat(mod,sizeof(mod),-1, "@",1, 0);
    strnncat(mod,sizeof(mod),-1, Node_address,-1, 0);
    if (!check_moderator(mod)) {
      err_line("Error: Add/mod - Not a moderator");
      return(0);
      }
    }
  else if (no_priv()) {
    err_line("Error: Add - No privilege for operation");
    return(0);
    }

#if FAST_LOAD
   if (fast_loading) {

      _ck_sys(NEWS$_NOADDWITHFASTLD,0,0);    /* see problem description below */

      set_level(1);  /* ensure the directory is shown */
      if (! all_loaded ) {
        if (!nntp_client) {
          sysprv();
          sys_close(&itmfab);
          sys_close(&grpfab);
          nosysprv();               /* note this code is taken from the */
          close_hist_file();        /* closefiles function in the       */
          };                        /* NEWSFILES module.                */
        close_mail_file();  
        close_nntp_file();
        write_reg_file();
        all_loaded = 1;
        first_retr_call = 0;
        openfiles(nntp_client ? 4 : -1);
/*
   mark.martinec@ijs.si  23 Feb 1993
   This still needs to be fixed !

   Problem description:
     A call to CLI$DCL_PARSE from within profile_read (called from
     openfiles) overwrites the current CLI context, which is still
     required for obtaining values of ADD qualifiers and parameters.
     This leads to CLI complaining about undefined qualifiers
     and in consequance the command 'ADD BATCH' does nothing.

     Problem occurs when fast loading is used ('>' in line 1 of NEWSRC)
     and DIRECTORY or PRINT options are specified in profile.

  Suggested solution:
     Move all calls to cli$present and cli$get_value in this function
     before this 'if (fast_loading)' block.
*/
        }
      }
#endif
  if (smg_active) {
    savscrn = 1;
    noscreen();
    }
  set_level(1);

  if (mfo) do_close_mail();

  if (!*scratchfilename)
    sprintf(scratchfilename,"SYS$SCRATCH:ADD_%X.ITM",getpid());
  if (!*bigitem_scratchfilename)
    sprintf(bigitem_scratchfilename,"SYS$SCRATCH:ADD_BIG_%X.ITM",getpid());
  auto_cre_grp = cli$present(c$dsc("CREGRP")) & 1;
  del_after = (cli$present(c$dsc("DELETE")) & 1);
  skip_loop_test = cli$present(c$dsc("RETRY")) & 1;
  genid = cli$present(c$dsc("GENID")) & 1;
  feed_through = (cli$present(c$dsc("FEEDTHROUGH")) & 1);

  exec_switch = 0;
  if ((cli$present(c$dsc("EXECUTE")) & 1) != 0) {
    if (cli$present(c$dsc("EXECUTE.ALL")) & 1)
      exec_switch |= (EXECUTE_CONTROL_CHECKGROUPS |
                      EXECUTE_CONTROL_NEWGROUP    |
                      EXECUTE_CONTROL_RMGROUP);
    if (cli$present(c$dsc("EXECUTE.CHECKGROUPS")) & 1)
      exec_switch |= EXECUTE_CONTROL_CHECKGROUPS;
    if (cli$present(c$dsc("EXECUTE.NEWGROUP")) & 1)
      exec_switch |= EXECUTE_CONTROL_NEWGROUP;
    if (cli$present(c$dsc("EXECUTE.RMGROUP")) & 1)
      exec_switch |= EXECUTE_CONTROL_RMGROUP;

    if (cli$present(c$dsc("EXECUTE.DELETE")) & 1)
      exec_switch |= EXECUTE_CONTROL_RMGROUP_DELETE;
    if (cli$present(c$dsc("EXECUTE.LOCAL")) & 1)
      exec_switch |= EXECUTE_CONTROL_RMGROUP_LOCAL;
    }
  if (cli$present(c$dsc("RMGROUP.DELETE")) & 1)
    exec_switch |= EXECUTE_CONTROL_RMGROUP_DELETE;
  if (cli$present(c$dsc("RMGROUP.LOCAL")) & 1)
    exec_switch |= EXECUTE_CONTROL_RMGROUP_LOCAL;

  get_qualifier_list("DEFNEWSGROUP", def_newsgroup_global,
    &def_newsgroup_global_len, sizeof(def_newsgroup_global), NULL, 0);

  get_qualifier_list("NEWSGROUP", force_newsgroup_global,
    &force_newsgroup_global_len, sizeof(force_newsgroup_global), NULL, 0);

  net_news = (cli$present(c$dsc("NETFEED")) & 1);

  control_squelch = (cli$present(c$dsc("NOCONTROL")) & 1);

  junk_it = 1;
  if (cli$present(c$dsc("JUNK")) == CLI$_NEGATED) junk_it = 0;

  accept_it = 0;
  if (cli$present(c$dsc("ACCEPT")) & 1) accept_it = 1;

  /* steps to the positional qualifiers! */
  if (cli$get_value(c$dsc("FILE"),&fnam_dsc,&fnam_len) == CLI$_ABSENT)
    get_input(&fnam_dsc,c$dsc("Add File: "),&fnam_len);

  if (!fheaders_l[0]) {                         /* init header keywords array */
    int j;
    for (j=0; j<NHEADERS; ++j) {
      fheaders_l[j] = strlen(fheaders[j]);
      news_assert(fheaders[j][fheaders_l[j]-2] == ':');
      news_assert(fheaders[j][fheaders_l[j]-1] == ' ');
      }
    }
  if (!relay_vers_str_l)
#ifdef __ALPHA
    relay_vers_str_l = sprintf(relay_vers_str, "%s %s OpenVMS AXP %s; site %.64s",
                               NEWS_VERSION, NEWS_DDATE, VMS_VERS, news_node);
#else
    relay_vers_str_l = sprintf(relay_vers_str, "%s %s OpenVMS VAX %s; site %.64s",
                               NEWS_VERSION, NEWS_DDATE, VMS_VERS, news_node);
#endif
  do {
    fnam[fnam_len] = '\0';
    auto_cre_grp = cli$present(c$dsc("CREGRP")) & 1;
    del_after    = cli$present(c$dsc("DELETE")) & 1;
    skip_loop_test = cli$present(c$dsc("RETRY")) & 1;

    exec_switch = 0;
    if ((cli$present(c$dsc("EXECUTE")) & 1) != 0) {
      if (cli$present(c$dsc("EXECUTE.ALL")) & 1)
        exec_switch |= (EXECUTE_CONTROL_CHECKGROUPS |
                        EXECUTE_CONTROL_NEWGROUP    |
                        EXECUTE_CONTROL_RMGROUP);
      if (cli$present(c$dsc("EXECUTE.CHECKGROUPS")) & 1)
        exec_switch |= EXECUTE_CONTROL_CHECKGROUPS;
      if (cli$present(c$dsc("EXECUTE.NEWGROUP")) & 1)
        exec_switch |= EXECUTE_CONTROL_NEWGROUP;
      if (cli$present(c$dsc("EXECUTE.RMGROUP")) & 1)
        exec_switch |= EXECUTE_CONTROL_RMGROUP;
  
      if (cli$present(c$dsc("EXECUTE.DELETE")) & 1)
        exec_switch |= EXECUTE_CONTROL_RMGROUP_DELETE;
      if (cli$present(c$dsc("EXECUTE.LOCAL")) & 1)
        exec_switch |= EXECUTE_CONTROL_RMGROUP_LOCAL;
      }
    if (cli$present(c$dsc("RMGROUP.DELETE")) & 1)
      exec_switch |= EXECUTE_CONTROL_RMGROUP_DELETE;
    if (cli$present(c$dsc("RMGROUP.LOCAL")) & 1)
      exec_switch |= EXECUTE_CONTROL_RMGROUP_LOCAL;

    net_news = (cli$present(c$dsc("NETFEED")) & 1);
    junk_it = 1;
    if (((status = cli$present(c$dsc("JUNK"))) == CLI$_NEGATED) ||
              (status == CLI$_LOCNEG)) junk_it = 0;
    accept_it = 0;
    if (cli$present(c$dsc("ACCEPT")) & 1) accept_it = 1;
    feed_through = (cli$present(c$dsc("FEEDTHROUGH")) & 1);

    loc_junk = 0;
    if (!get_qualifier_list("DEFNEWSGROUP", def_newsgroup,
          NULL, sizeof(def_newsgroup),
          def_newsgroup_global, def_newsgroup_global_len))
       { strcpy(def_newsgroup,junk); loc_junk = 1; }
    lower_case(def_newsgroup);

    if (get_qualifier_list("NEWSGROUP", force_newsgroup,
          NULL, sizeof(force_newsgroup),
          force_newsgroup_global, force_newsgroup_global_len))
       loc_junk = 0;
    lower_case(force_newsgroup);

    artbuf = NULL; artbuf_l = 0;
    news_assert(!itm_text); itm_text = NULL; itm_text_m = itm_text_l = 0;
    itm_text_tmp_in_use = 0; itm_text_tmp_l = 0;
    add_dsc.dsc$w_length = strlen(fnam);
    del_after_save = del_after; /* RRS save /DELETE flag for wildcards */
    while ((status = lib$find_file(&add_dsc,&rnam_dsc,&context,0,0,0,0)) & 1) {
      if (news_lock_alarm) {
        printf("NEWS - Manager has requested shutdown\n");
        closefiles();
        exit(1);
        }
      rnam[254] = '\0';
      chop_str(rnam,' ');
      if (!artbuf) { artbuf = news_malloc(ART_BUFSIZE); artbuf_l = 0; }
      add_file(rnam,exec_switch);
      del_after = del_after_save; /* RRS save /DELETE flag for wildcards */
      }
    if (!(status&1) &&
        (status != RMS$_NMF) && (status != RMS$_FNF)) _c$cks(status);
    _c$cks(lib$find_file_end(&context));
    if (artbuf)
      { news_free(artbuf); artbuf = NULL; artbuf_l = 0; }
    if (itm_text)
      { news_free(itm_text); itm_text = NULL; itm_text_m = 0; }
    } while (cli$get_value(c$dsc("FILE"),&fnam_dsc,&fnam_len) & 1);
  no_more_news = 0;
  mail_add_expiry = 0;
  flush_downstream(1);
#if ADDFILE_STATISTICS_ENABLE
  if (report_stat) report_statistics();
#endif
  if (savscrn) init_screen();
  if (mfo) do_open_mail();
  return(0);
}

int do_add_net(void)
{
  if (nntp_client) return(0);
  return(unwind_display(OUTER_LOOP,dan_1));
}
#endif
