/*
**++
**  FACILITY:
**      FEEDCHECK.C
**
**  ABSTRACT:
**      ANU NEWS Log File Analyzer
**
**  AUTHOR:
**      Mark Pizzolato (mark@infocomm.com)		3/27/92
**
**  COPYRIGHT:
**      Copyright © 1992
**
**  MODIFICATION HISTORY:
**	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	20-Dec-1994	Mark Martinec   mark.martinec@ijs.si
**	  - seems there might be a problem with uninitialized 'fr'.
**	    Added a check and initialized it to NULL.
**	V6.1b10 09-DEC-1996	Mark Pizzolato	mark@infocomm.com
**	  - minor declaration cleanup to get warningless compile under 
**	    DEC C V5.3 on VAX
**--
**/

#ifdef __GNUC__
#define variant_union	volatile union
#define variant_struct	volatile struct
#endif

#ifdef __DECC
#pragma message save
#pragma message disable (MACROEXT)
#endif

#ifndef NAKED_INCLUDES
#define NAKED_INCLUDES 0
#endif

#if NAKED_INCLUDES

#include starlet
#include clidef
#include climsgdef
#include chfdef
#include stsdef
#include descrip
#include rms

#include types
#include stddef
#include stdio
#include stdlib
#include string
#include ctype
#include errno
#include time
#include stat
#include unixio

#else	/*!NAKED_INCLUDES*/

#include <starlet.h>
#include <clidef.h>	/* Comamnd Line Interpreter Codes		*/
#include <climsgdef.h>	/* Command Line Interpreter Message Codes	*/
#include <chfdef.h>
#include <stsdef.h>	/* Status Code Definitions			*/
#include <descrip.h>	/* VMS Descriptor Definitions			*/
#include <rms.h>	/* All RMS Definitions				*/

#if defined(__GNUC__)
  /*  problem is that both multinet and gnu_cc_include contain sys/types.h    *
   *  and the multinet version is missing some declarations (dev_t,off_t,...) *
   *  which are necessary when stat.h gets included, so we force GNU types.h  */
#  include <gnu_cc_include:[sys]types.h>
#elif defined(MULTINET) || defined(TCPWARE)
#  include <sys/types.h>    /* take the Multinet (or other IP vendor) types.h */
#else
#  include <types.h>        /* take whatever we have */
  /* although TWG has types.h on sys subdirectory, due to a conflict
   * in declarations of off_t, ino_t and dev_t we have to take DEC's types.h */
#endif

#include <stddef.h>
#include <stdio.h>	/* Standard I/O Definitions			*/
#include <stdlib.h>
#include <string.h>	/* String Handling Routine Declarations		*/
#include <ctype.h>	/* Character Classifications			*/
#include <errno.h>	/* Error Codes					*/
#include <time.h>	/* Unix Time Definitions			*/
#include <unixio.h>
#include <unixlib.h>

#ifdef __GNUC__
#include <sys/stat.h>
#else
#include <stat.h>
#endif

#endif

#ifdef __DECC
#pragma message restore
#endif

#ifndef L_tmpnam
#define L_tmpnam	256	/* missing in gnu_cc_include  <stdio.h> */
#endif

#ifdef vaxc
/* note: viable for VAX C v2.3 and later; not for v2.2 or earlier */
#define PROTOTYPES	1
#endif

#ifndef	PROTOTYPES
#ifdef	__STDC__
#define	PROTOTYPES	1
#else
#define	PROTOTYPES	0
#endif
#endif

#if PROTOTYPES
#define __ARGS(args) args
#else
#define __ARGS(args) ()
#endif

extern int str$free1_dx();
extern int str$copy_r();
extern int str$copy_dx();

extern int cli$dispatch __ARGS((void));
extern int cli$present __ARGS((struct dsc$descriptor *));
extern int cli$get_value __ARGS((struct dsc$descriptor *,
                                 struct dsc$descriptor *, unsigned short *));
extern int cli$dcl_parse __ARGS((struct dsc$descriptor *, char *, int (*)(),
                                 int (*)(), struct dsc$descriptor *));

extern int lib$get_input __ARGS((void));
extern void lib$signal __ARGS((int, ...));
extern void lib$stop __ARGS((int, ...));
extern int lib$sig_to_ret __ARGS((struct chf$signal_array*, struct chf$mech_array*));
extern void lib$establish __ARGS((int (*exception_handler)(struct chf$signal_array*, struct chf$mech_array*)));

extern int lib$ediv(), lib$subx();
extern int lib$find_file __ARGS((const struct dsc$descriptor *,
				 struct dsc$descriptor *, void *, ...));
extern int lib$find_file_end __ARGS((void *));


#ifdef vaxc
#define c$ac(v)		&(v)
#else
#define c$ac(v)		c$rfi(v)
#endif

#define SS_FAILED(status)	(!((status)&STS$M_SUCCESS))
#define SS_OK(status)		(!SS_FAILED(status))

#define DLEN(x)  (x).dsc$w_length
#define DPTR(x)  (x).dsc$a_pointer

#define $DESCRIPTOR_D(x)  struct dsc$descriptor x = { 0, DSC$K_DTYPE_T, \
                          DSC$K_CLASS_D, 0 }

typedef struct {                  /* Quadword time structure */
   unsigned long int l0, l1;
   } uquad;

int rejects, junked, dups;

struct action_count
    {
    const char *reason;
    int length;
    int *counter;
    } reasons[] = 
    {
    {"JUNK (SYS filter)", 				17,	&junked},
    {"JUNK (No local Newsgroup match)", 		31,	&junked},
    {"REJECT (SYS filter)", 				19,	&rejects},
    {"REJECT (No local Newsgroup match)", 		33,	&rejects},
    {"REJECT (Path Loop)", 				18,	&dups},
    {"REJECT (Duplicate - Cannot add Locally: Loop?)",	46, 	&dups},
    {"REJECT (/NOJUNK)", 				16,	&rejects},
    {"REJECT (No Message-ID)", 				22,	&rejects},
    {"REJECT (No JUNK)",		 		16,	&rejects},
    {"REJECT\n",		 			 7,	&rejects},
    {"REJECT (No Newsgroup)", 				21,	&rejects},
    {"JUNK (No Newsgroup)", 				19,	&junked},
    {"REJECT (",		 			 8,	&rejects},
    {"JUNK (",		 				 6,	&junked},
    {NULL,						 0, 	NULL}
    };

struct junk_rec
    {
    struct junk_rec *link;
    struct junk_host_rec *hosts;
    int junked;
    char newsgroup[256];
    };

struct junk_host_rec
    {
    struct junk_host_rec *link;
    int count;
    char hostname[80];
    };

struct junk_rec *garb = NULL;

int group_count = 0;

struct junk_rec **grouporder = NULL;

struct file_rec
    {
    struct file_rec *link;
    int lines;
    int badlines;
    int batches;
    int batchsize;
    struct stat statb;
    char filename[L_tmpnam];
    char sourcename[80];
    };

struct batch_rec
    {
    struct batch_rec *link;
    struct file_rec *log;
    int articles;
    int dups;
    int junked;
    int rejects;
    int batchsize;
    char filename[L_tmpnam];
    char hostname[80];
    };

struct host_rec
    {
    struct host_rec *link;
    struct batch_rec *batches;
    int batch_count;
    int articles;
    int dups;
    int junked;
    int rejects;
    int batchsize;
    int xmit_articles;
    int xmit_bytes;
    int xmit_article_ids;
    char hostname[80];
    };

struct nntp_xmit_rec
    {
    struct nntp_xmit_rec *link;
    struct file_rec *log;
    int xmit_offered;
    int xmit_accepted;
    int xmit_rejected;
    int xmit_failed;
    int total_bytes;
    float xfer_time;
    };

struct nntp_recv_rec
    {
    struct nntp_recv_rec *link;
    struct file_rec *log;
    int recv_articles;
    int recv_posts;
    int failed_articles;
    int reject_articles;
    int xmit_articles;
    int xmit_newsgroups;
    int total_bytes;
    float xfer_time;
    };

struct nntp_host_rec
    {
    struct nntp_host_rec *link;
    struct nntp_xmit_rec *xmits;
    int xmit_connections;
    int xmit_offered;
    int xmit_accepted;
    int xmit_rejected;
    int xmit_failed;
    int xmit_expired;
    int xmit_total_bytes;
    float xmit_xfer_time;
    struct nntp_recv_rec *recvs;
    int recv_connections;
    int recv_articles;
    int recv_posts;
    int failed_articles;
    int reject_articles;
    int recv_xmit_articles;
    int recv_xmit_newsgroups;
    int recv_total_bytes;
    float recv_xfer_time;
    char hostname[80];
    };

struct nntp_host_rec *nntp_hosts = NULL;

int nntp_host_count = 0;

struct nntp_host_rec **nntp_printorder = NULL;

struct host_rec *hosts = NULL;

int host_count = 0;

struct host_rec **printorder = NULL;

struct file_rec *files = NULL;

/* Forward Declarations */
void init_cli(char *, const char *);
void cli_qualifier_action(const char *, void (*action_routine)());
void cli_get_time_value(const char *, uquad *);
void cli_get_bool_value(const char *, int *);
void cli_get_int_value(const char *, int *);
void cli_get_string_value(const char *, char **);
struct host_rec *get_host(char *hostname);
struct nntp_host_rec *get_nntp_host(char *hostname);
void printline(const char *, int,int,int,int,int,int,int,int,int,int);
void printnntpline(const char *, int , int , int, int, int, int, int, float, 
		   int , int, int, int, int, int, int, int, float);
static void usage();
static int compare();
static int compare_nntp();
static int compareg();
static char *strtolower();
struct dsc$descriptor *c$dsc();
struct dsc$descriptor *c$ldsc();
int *c$alloc_tmp(int);
int *c$rfi(int);

#ifdef	__GNUC__
#include <gnu_hacks.h>
GLOBALVALUEREF(char *,feedcheckcmd);
#define feedcheckcmd ((char *)feedcheckcmd)
#else
globalvalue char * feedcheckcmd;
#endif

int main(void)
{
    char *reason, *newsgroups, *p;
    char *opt_output = NULL;
    char *opt_Sourcename = NULL;
    char Sourcename[64] = "";
    FILE *Log;
    char filename[256];
    char bfilename[256];
    $DESCRIPTOR_D(fdesc);
    $DESCRIPTOR_D(rdesc);
    $DESCRIPTOR_D(lastdesc);
    int context, status_value, bfilesize;
    int verbose = 0;
    int record_junk = 0;
    int file_count = 0;
    int tot_articles = 0, tot_dups = 0, tot_junked = 0, tot_rejected = 0;
    int tot_xmit_articles = 0, tot_xmit_bytes = 0, tot_xmit_article_ids = 0;
    int tot_batchsize = 0;
    char logline[1024];
    struct action_count *a;
    struct stat statb;
    unsigned beforetim[2] = {0xffffffffUL, 0x7fffffffUL};
    time_t beforetime = 0x7fffffff;
    int sincetim[2] = {0, 0};
    time_t sincetime = 0;
    time_t vms_to_unix_time();
    struct file_rec *fr = NULL;
    struct batch_rec *br;
    struct host_rec *hr;
    struct junk_rec *jr;
    struct junk_host_rec *jhr;
    struct nntp_host_rec *nntp_hr;

    lib$establish(lib$sig_to_ret);
    strcpy(opt_Sourcename = (char *)malloc(64), "undetermined");

    init_cli(feedcheckcmd, "FEEDCHECK");

    cli_qualifier_action("HELP", usage);
    cli_get_time_value("BEFORE", (uquad *) beforetim);
    if (beforetim[1] != 0x7fffffff)
	beforetime = vms_to_unix_time(beforetim);
    cli_get_time_value("SINCE", (uquad *) sincetim);
    if ((sincetim[0]) || (sincetim[1]))
	sincetime = vms_to_unix_time(sincetim);
    cli_get_bool_value("JUNK", &record_junk);
    cli_get_int_value("VERBOSE", &verbose);
    cli_get_string_value("SOURCENAME", &opt_Sourcename);
    strtolower(opt_Sourcename);
    cli_get_string_value("OUTPUT", &opt_output);
    if (opt_output)
	if (NULL == freopen(opt_output, "w", stdout))
	    {
	    perror(opt_output);
	    exit(vaxc$errno);
	    }
    /* Assume the rest of the arguments are Logfiles to scan to report on */
    while (SS_OK(cli$get_value(c$dsc("FILENAME"), &fdesc, NULL)))
	{
	context = 0;
	while (SS_OK(lib$find_file(&fdesc, &rdesc, &context, 0, 
				   &lastdesc, &status_value, c$ac(0))))
	    {
	    strncpy(filename, DPTR(rdesc), DLEN(rdesc));
	    filename[DLEN(rdesc)] = '\0';
	    str$copy_dx(&lastdesc, &rdesc);
	    if (verbose>2)
		printf("Checking File: %s\n", filename);
	    if (stat(filename, &statb))
		{
		perror(filename);
		continue;
		}
	    if (statb.st_mtime < sincetime)
		continue;
	    if (statb.st_mtime > beforetime)
		continue;
	    if (NULL == (Log = fopen(filename, "r", "shr=get,put", "mbc=40")))
		{
		perror(filename);
		continue;
		}
	    ++file_count;
	    if (verbose>1)
		printf("Processing Log File: %s\n", filename);
	    fr = calloc(1, sizeof(*fr));
	    fr->statb = statb;
	    strcpy(fr->filename, filename);
	    fr->link = files;
	    files = fr;
	    strcpy(Sourcename, "");
	    br = NULL;
	    while (fgets(logline, sizeof(logline), Log))
		{
		++fr->lines;
		if (NULL != (p = strstr(logline, ": nntprecv: ")))
		    {
		    char hostname[80];
		    int received, posted, failed, rejected, articles, newsgroups, bytes = 0;
		    float xfer_time = 0.0;

		    if (9 != sscanf(p, ": nntprecv: %s received: %d  posted: %d  failed: %d rejected: %d  read: %d articles, %d newsgroups      %d bytes, %f seconds,",
				hostname, &received, &posted, &failed, &rejected,
				&articles, &newsgroups, &bytes, &xfer_time))
			if (7 != sscanf(p, ": nntprecv: %s received: %d  posted: %d  failed: %d rejected: %d  read: %d articles, %d newsgroups",
				    hostname, &received, &posted, &failed, &rejected,
				    &articles, &newsgroups))
			    continue;
		    nntp_hr = get_nntp_host(hostname);
		    ++nntp_hr->recv_connections;
		    nntp_hr->recv_articles += received;
		    nntp_hr->recv_posts += posted;
		    nntp_hr->failed_articles += failed;
		    nntp_hr->reject_articles += rejected;
		    nntp_hr->recv_xmit_articles += articles;
		    nntp_hr->recv_xmit_newsgroups += newsgroups;
		    nntp_hr->recv_total_bytes += bytes;
		    nntp_hr->recv_xfer_time += xfer_time;
		    continue;
		    }
		if (NULL != (p = strstr(logline, ": nntpxmit: ")))
		    {
		    char hostname[80];
		    int offered, accepted, rejected, failed, expired, bytes = 0;
		    float xfer_time = 0.0;

		    if (8 != sscanf(p, ": nntpxmit: %s %d offered, %d accepted, %d rejected, %d failed, %d expired, %d bytes, %f secs ",
				hostname, &offered, &accepted, &rejected, &failed, &expired, 
				&bytes, &xfer_time))
			continue;
		    nntp_hr = get_nntp_host(hostname);
		    ++nntp_hr->xmit_connections;
		    nntp_hr->xmit_offered += offered;
		    nntp_hr->xmit_accepted += accepted;
		    nntp_hr->xmit_rejected += rejected;
		    nntp_hr->xmit_failed += failed;
		    nntp_hr->xmit_expired += expired;
		    nntp_hr->xmit_total_bytes += bytes;
		    nntp_hr->xmit_xfer_time += xfer_time;
		    continue;
		    }
		if (0 == memcmp(logline, "...Processing BATCH - h:", 24))
		    {
		    strcpy(Sourcename, strtolower(strtok(&logline[24], " \t\n\r\f")));
		    strcpy(fr->sourcename, Sourcename);
		    continue;
		    }
		if (memcmp(logline, "Add ", 4))
		    {
		    char fhostname[80];
		    int fcount, fbytes, fids = 0;

		    if (memcmp(logline, "- Forwarded: ", 13))
			continue;
		    if (3 != sscanf(logline, "- Forwarded: %d/%d Articles/Bytes to site: %s",
				    &fcount, &fbytes, fhostname))
			if (2 != sscanf(logline, "- Forwarded: %d Article ID's to site: %s",
				    &fids, fhostname))
			    continue;
		    hr = get_host(fhostname);
		    if (fids)
			hr->xmit_article_ids += fids;
		    else
			{
			hr->xmit_articles += fcount;
			hr->xmit_bytes += fbytes;
			}
		    continue;
		    }
		if (2 == sscanf(logline, "Add - Reading file: %s (%d bytes)",
				bfilename, &bfilesize))
		    {
		    if (br)
			{
			br->dups = dups;
			br->junked = junked;
			br->rejects = rejects;
			tot_articles += br->articles;
			tot_dups += br->dups;
			tot_junked += br->junked;
			tot_rejected += br->rejects;
			rejects = junked = dups = 0;
			}
		    if (verbose>1)
			printf("Analyzing Log for Batch: %s\n", bfilename);
		    br = calloc(1, sizeof(*br));
		    strcpy(br->filename, bfilename);
		    fr->batchsize += (br->batchsize = bfilesize);
		    ++fr->batches;
		    br->log = fr;
		    continue;
		    }
		if (NULL == (newsgroups = strstr(logline, "> ")))
		    {
		    ++fr->badlines;
		    continue;
		    }
		else
		    {
		    char linehost[80];

		    if (1 == sscanf(logline, "Add %s <", linehost))
			if (br->hostname[0] == '\0')
			    {
			    strcpy(br->hostname, linehost);
			    hr = get_host(br->hostname);
			    br->link = hr->batches;
			    hr->batches = br;
			    }
			else
			    if (strcmp(br->hostname, linehost))
				printf("Unexpected source host change from %s to %s in file: %s which is logged in file: %s\n",
					br->hostname, linehost, br->filename, fr->filename);
		    }
		newsgroups += 2;
		++br->articles;
		if (NULL == (reason = strstr(logline, ": ")))
		    continue;
		reason += 2;
		for (a=reasons; a->reason; ++a)
		    if (0 == memcmp(a->reason, reason, a->length))
			{
			++*a->counter;
			if ((record_junk) && (a->counter == &junked))
			    {
			    *(reason-2) = '\0';
			    while (1)
				{
				if ( (p = strchr(newsgroups,',')) )
				    *p = '\0';
				for (jr=garb; jr; jr=jr->link)
				    if (0 == strcmp(newsgroups, jr->newsgroup))
					break;
				if (!jr)
				    {
				    ++group_count;
				    if (group_count == 1)
					grouporder = calloc(1, sizeof(grouporder[0]));
				    else
					grouporder = realloc(grouporder, group_count*sizeof(grouporder[0]));
				    jr = calloc(1, sizeof(*jr));
				    grouporder[group_count-1] = jr;
				    jr->link = garb;
				    garb = jr;
				    strcpy(jr->newsgroup, newsgroups);
				    }
				++jr->junked;
				for (jhr=jr->hosts; jhr; jhr=jhr->link)
				    if (0 == strcmp(jhr->hostname, br->hostname))
					break;
				if (!jhr)
				    {
				    jhr = calloc(1, sizeof(*jhr));
				    strcpy(jhr->hostname, br->hostname);
				    jhr->link = jr->hosts;
				    jr->hosts = jhr;
				    }
				++jhr->count;
				newsgroups += (1 + strlen(newsgroups));
				if (isspace(*newsgroups))
				    break;
				}
			    }
			break;
			}
		}
	    fclose(Log);
	    if (br)
		{
		br->dups = dups;
		br->junked = junked;
		br->rejects = rejects;
		tot_articles += br->articles;
		tot_dups += br->dups;
		tot_junked += br->junked;
		tot_rejected += br->rejects;
		rejects = junked = dups = 0;
		}
	    if (0 == fr->batches)
		{
		files = fr->link;
		free(fr); fr = NULL;
		continue;
		}
	    tot_batchsize += fr->batchsize;
	    }
	lib$find_file_end(&context);
	}
    if (host_count > 0)
	{
	printf("Incoming Batch Processing:\n");
	printf("Site:     Files:Accept: Dups:Junked:Reject:Total: %%Bad:%%TAcpt: I've: Xmit:Bytes:\n");
	printf("================================================================================\n");
	if (host_count > 1)
	    {
	    int i;

	    qsort((char *)printorder, host_count, sizeof(printorder[0]), compare);
	    hosts = printorder[0];
	    printorder[host_count-1]->link = NULL;
	    for (i=0; i<(host_count-1); ++i)
		printorder[i]->link = printorder[i+1];
	    }
	for (hr=hosts; hr; hr = hr->link)
	    {
	    for (br = hr->batches; br; br = br->link)
		{
		++hr->batch_count;
		hr->articles += br->articles;
		hr->dups += br->dups;
		hr->junked += br->junked;
		hr->rejects += br->rejects;
		hr->batchsize += br->batchsize;
		if (verbose>1)
		if (!fr) printf("Log File: (sorry, fr==NULL)\n");
		else printf("Log File: %s, Lines: %d, badlines: %d, batches: %d\n", 
			    fr->filename, fr->lines, fr->badlines, fr->batches);
		if (verbose)
		    printf("%-14s%13d  %6d  %6d %8d\n",
			    hr->hostname, br->articles, br->dups, br->junked, br->rejects);
		}
	    tot_xmit_articles += hr->xmit_articles;
	    tot_xmit_bytes += hr->xmit_bytes;
	    tot_xmit_article_ids += hr->xmit_article_ids;
	    /*
	    ** Anything "in-from" or "out-to" a host gets the host mention in the
	    ** report.
	    */
	    if ((hr->batch_count > 0) || (hr->xmit_articles) || (hr->xmit_article_ids))
		printline(hr->hostname, hr->batch_count, hr->batchsize, 
			hr->articles, hr->dups, hr->junked, hr->rejects, 
			tot_articles-(tot_dups+tot_junked+tot_rejected), 
			hr->xmit_articles, hr->xmit_bytes, hr->xmit_article_ids);
	    }
	if (host_count > 1)
	    {
	    printf("\n          Files:Accept: Dups:Junked:Reject:Total: %%Bad:%%TAcpt: I've: Xmit:Bytes:\n");
	    printline("TOTAL:", file_count, tot_batchsize, tot_articles, tot_dups, 
				tot_junked, tot_rejected, 0, tot_xmit_articles,
				tot_xmit_bytes, tot_xmit_article_ids);
	    }
	if (group_count > 1)
	    {
	    int i;

	    printf("\nJunked Newsgroups:\n");
	    printf("======================\n");
	    qsort((char *)grouporder, group_count, sizeof(grouporder[0]), compareg);
	    garb = grouporder[0];
	    grouporder[group_count-1]->link = NULL;
	    for (i=0; i<(group_count-1); ++i)
		grouporder[i]->link = grouporder[i+1];
	    for (jr=garb; jr; jr=jr->link)
		{
		printf("%5d Articles in: %s\n", jr->junked, jr->newsgroup);
		for (jhr=jr->hosts; jhr; jhr=jhr->link)
		    printf("\t%4d from: %s\n", jhr->count, jhr->hostname);
		}
	    }
	}
    if (nntp_host_count > 0)
	{
	if (host_count > 0)
	    printf("\n");
	printf("NNTP Processing:\n");
	printf("Site:     X-Con:X-Off:X-Acc:X-Rej:X-Fail:X-Exp:X-Tot:X-Rate:R-Con:R-Art:R-Pos:R-Fail:R-Rej:R-XAr:R-XNg:R-Tot:R-Rate:\n");
	printf("====================================================================================================================\n");
	if (nntp_host_count > 1)
	    {
	    int i;

	    qsort((char *)nntp_printorder, nntp_host_count, sizeof(nntp_printorder[0]), compare_nntp);
	    nntp_hosts = nntp_printorder[0];
	    nntp_printorder[nntp_host_count-1]->link = NULL;
	    for (i=0; i<(nntp_host_count-1); ++i)
		nntp_printorder[i]->link = nntp_printorder[i+1];
	    }
	for (nntp_hr=nntp_hosts; nntp_hr; nntp_hr = nntp_hr->link)
	    {
	    printnntpline(nntp_hr->hostname, nntp_hr->xmit_connections, nntp_hr->xmit_offered, 
			nntp_hr->xmit_accepted, nntp_hr->xmit_rejected, nntp_hr->xmit_failed, 
			nntp_hr->xmit_expired, nntp_hr->xmit_total_bytes, nntp_hr->xmit_xfer_time, 
			nntp_hr->recv_connections, nntp_hr->recv_articles, nntp_hr->recv_posts, 
			nntp_hr->failed_articles, nntp_hr->reject_articles, nntp_hr->recv_xmit_articles, 
			nntp_hr->recv_xmit_newsgroups, nntp_hr->recv_total_bytes, nntp_hr->recv_xfer_time);
	    }
	}
    return(1);
}

static void usage(void)
{
    fprintf(stderr, "\n\tFEEDCHECK\n\n");
    fprintf(stderr, "\tProcesses the log files produced during ANU-NEWS maintenance\n");
    fprintf(stderr, "\toperations and produces a report of the activities observed.\n");
    fprintf(stderr, "\n\tFormat\n");
    fprintf(stderr, "\t   FEEDCHECK filespec[,...]\n");
    fprintf(stderr, "\n\tPARAMETER\n");
    fprintf(stderr, "\t\t\tfilespec[,...]\n");
    fprintf(stderr, "\t\t\tSpecifies one or more files to be processed.\n");
    fprintf(stderr, "\n\tQUALIFIERS\n");
    fprintf(stderr, "\t\t/SOURCENAME=source\n");
    fprintf(stderr, "\t\t\tSpecifies the default source to attribute traffic\n");
    fprintf(stderr, "\t\t\tto if the input file does not contain a source\n");
    fprintf(stderr, "\t\t\tspecifier.\n");
    fprintf(stderr, "\t\t/BEFORE[=time]\n");
    fprintf(stderr, "\t\t\tSpecifies that files created before the specified\n");
    fprintf(stderr, "\t\t\ttime are to be processed.\n");
    fprintf(stderr, "\t\t/SINCE[=time]\n");
    fprintf(stderr, "\t\t\tSpecifies that files created after the specified\n");
    fprintf(stderr, "\t\t\ttime are to be processed.\n");
    fprintf(stderr, "\t\t/JUNK\n");
    fprintf(stderr, "\t\t/NOJUNK (default)\n");
    fprintf(stderr, "\t\t\tCauses a detailed summary of articles that have been\n");
    fprintf(stderr, "\t\t\tjunked, and where they were fed from.\n");
    fprintf(stderr, "\t\t/OUTPUT[=filespec]\n");
    fprintf(stderr, "\t\t\tControls where the output of the command is sent.\n");
    fprintf(stderr, "\t\t\tBy default, the display is written to the current\n");
    fprintf(stderr, "\t\t\tSYS$OUTPUT device.\n");
    fprintf(stderr, "\t\t/VERBOSE[=n]\n");
    fprintf(stderr, "\t\t\tSelects a more verbose output format at level n.\n");
    fprintf(stderr, "\n");
}

void printline(name, files, inbytes, articles, dups, junked, rejects, tot, xmit, bytes, ihave)
  const char *name;
  int files, inbytes, articles, dups, junked, rejects, tot, xmit, bytes, ihave;
{
    char files_bytes[40] = ". ";
    char buf[256];
    int accept;
    float kbytes, mbytes;

    if (files)
	{
	sprintf(files_bytes, "%d", files);
	kbytes = ((float)inbytes)/1024.0;
	mbytes = ((float)inbytes)/(1024.0*1024.0);
	if (kbytes > 1.0)
	    if (mbytes > 1.0)
	        sprintf(&files_bytes[strlen(files_bytes)], "(%0.1fM)", mbytes);
	    else
	        sprintf(&files_bytes[strlen(files_bytes)], "(%0.1fK)", kbytes);
	else
	    strcat(files_bytes, " ");
	}
    if (strlen(name)+strlen(files_bytes) > 17)
	sprintf(buf, "%s\n%17s", name, files_bytes);
    else
	sprintf(buf, "%s%*s", name, 17-strlen(name), files_bytes);
    accept = articles - (dups + junked + rejects);
    if (accept)
	sprintf(&buf[strlen(buf)], "%5d", accept);
    else
	strcat(buf, "    .");
    if (dups)
	sprintf(&buf[strlen(buf)], "%6d", dups);
    else
	strcat(buf, "     .");
    if (junked)
	sprintf(&buf[strlen(buf)], "%7d", junked);
    else
	strcat(buf, "      .");
    if (rejects)
	sprintf(&buf[strlen(buf)], "%6d", rejects);
    else
	strcat(buf, "     .");
    if (articles)
	sprintf(&buf[strlen(buf)], "%7d", articles);
    else
	strcat(buf, "      .");
    if (dups+junked+rejects)
	sprintf(&buf[strlen(buf)], "%6.1f%%", 
			  ((float)(100*(dups+junked+rejects)))/articles);
    else
	strcat(buf, "      .");
    if ((tot) && (accept))
	sprintf(&buf[strlen(buf)], "%6.1f%%",
			  ((float)(100*accept))/(float)tot);
    else
	strcat(buf, "      .");
    if (ihave)
	sprintf(&buf[strlen(buf)], "%6d", ihave);
    else
	strcat(buf, "     .");
    if (xmit)
	sprintf(&buf[strlen(buf)], "%6d", xmit);
    else
	strcat(buf, "     .");
    kbytes = ((float)bytes)/1024.0;
    mbytes = ((float)bytes)/(1024.0*1024.0);
    if (kbytes > 1.0)
	if (mbytes > 1.0)
	    sprintf(&buf[strlen(buf)], "%5.1fM", mbytes);
	else
	    if (kbytes > 100.0)
		sprintf(&buf[strlen(buf)], "%5.0fK", kbytes);
	    else
		sprintf(&buf[strlen(buf)], "%5.1fK", kbytes);
    else
	if (bytes)
	    sprintf(&buf[strlen(buf)], "%6d", bytes);
	else
	    strcat(buf, "     .");
    printf("%s\n", buf);
    }

void printnntpline(const char *name, 
	           int x_connections, int x_offered, int x_accepted, 
	    	   int x_rejected, int x_failed, int x_expired, int x_bytes, 
		   float x_time, 
		   int r_connections, int r_articles, int r_posts, int r_failed, int r_reject,
	    	   int r_xmit_articles, int r_xmit_newsgroups, int r_bytes, 
		   float r_time)
{
    char buf[256];
    float kbytes, mbytes;

    sprintf(buf, "%s\n%10s", name, "");
    if (x_connections)
	sprintf(&buf[strlen(buf)], "%4d", x_connections);
    else
	strcat(buf, "   .");
    if (x_offered)
	sprintf(&buf[strlen(buf)], "%6d", x_offered);
    else
	strcat(buf, "     .");
    if (x_accepted)
	sprintf(&buf[strlen(buf)], "%6d", x_accepted);
    else
	strcat(buf, "     .");
    if (x_rejected)
	sprintf(&buf[strlen(buf)], "%6d", x_rejected);
    else
	strcat(buf, "     .");
    if (x_failed)
	sprintf(&buf[strlen(buf)], "%5d", x_failed);
    else
	strcat(buf, "    .");
    if (x_expired)
	sprintf(&buf[strlen(buf)], "%5d", x_expired);
    else
	strcat(buf, "    .");
    kbytes = ((float)x_bytes)/1024.0;
    mbytes = ((float)x_bytes)/(1024.0*1024.0);
    if (kbytes > 1.0)
	if (mbytes > 1.0)
	    sprintf(&buf[strlen(buf)], "%5.1fM", mbytes);
	else
	    if (kbytes > 100.0)
		sprintf(&buf[strlen(buf)], "%5.0fK", kbytes);
	    else
		sprintf(&buf[strlen(buf)], "%5.1fK", kbytes);
    else
	if (x_bytes)
	    sprintf(&buf[strlen(buf)], "%6d", x_bytes);
	else
	    strcat(buf, "     .");
    if (x_time)
	sprintf(&buf[strlen(buf)], "%6.1f", x_bytes/(x_time*1024.0));
    else
	strcat(buf, "     .");
    if (r_connections)
	sprintf(&buf[strlen(buf)], "%4d", r_connections);
    else
	strcat(buf, "   .");
    if (r_articles)
	sprintf(&buf[strlen(buf)], "%6d", r_articles);
    else
	strcat(buf, "     .");
    if (r_posts)
	sprintf(&buf[strlen(buf)], "%4d", r_posts);
    else
	strcat(buf, "   .");
    if (r_failed)
	sprintf(&buf[strlen(buf)], "%5d", r_failed);
    else
	strcat(buf, "    .");
    if (r_reject)
	sprintf(&buf[strlen(buf)], "%5d", r_reject);
    else
	strcat(buf, "    .");
    if (r_xmit_articles)
	sprintf(&buf[strlen(buf)], "%4d", r_xmit_articles);
    else
	strcat(buf, "   .");
    if (r_xmit_newsgroups)
	sprintf(&buf[strlen(buf)], "%4d", r_xmit_newsgroups);
    else
	strcat(buf, "   .");
    kbytes = ((float)r_bytes)/1024.0;
    mbytes = ((float)r_bytes)/(1024.0*1024.0);
    if (kbytes > 1.0)
	if (mbytes > 1.0)
	    sprintf(&buf[strlen(buf)], "%5.1fM", mbytes);
	else
	    if (kbytes > 100.0)
		sprintf(&buf[strlen(buf)], "%5.0fK", kbytes);
	    else
		sprintf(&buf[strlen(buf)], "%5.1fK", kbytes);
    else
	if (r_bytes)
	    sprintf(&buf[strlen(buf)], "%6d", r_bytes);
	else
	    strcat(buf, "     .");
    if (r_time)
	sprintf(&buf[strlen(buf)], "%6.1f", r_bytes/(r_time*1024.0));
    else
	strcat(buf, "     .");
    printf("%s\n", buf);
    }

struct host_rec *get_host(char *hostname)
    {
    struct host_rec *hr;

    for (hr=hosts; hr; hr=hr->link)
	if (0 == strcmp(hostname, hr->hostname))
	    break;
    if (!hr)
	{
	++host_count;
	if (host_count == 1)
	    printorder = calloc(1, sizeof(printorder[0]));
	else
	    printorder = realloc(printorder, host_count*sizeof(printorder[0]));
	hr = calloc(1, sizeof(*hr));
	printorder[host_count-1] = hr;
	hr->link = hosts;
	hosts = hr;
	strcpy(hr->hostname, hostname);
	}
    return(hr);
    }

struct nntp_host_rec *get_nntp_host(char *hostname)
    {
    struct nntp_host_rec *hr;

    for (hr=nntp_hosts; hr; hr=hr->link)
	if ((NULL != strstr(hostname, hr->hostname)) || (NULL != strstr(hr->hostname, hostname)))
	    break;
    if (!hr)
	{
	++nntp_host_count;
	if (nntp_host_count == 1)
	    nntp_printorder = calloc(1, sizeof(nntp_printorder[0]));
	else
	    nntp_printorder = realloc(nntp_printorder, nntp_host_count*sizeof(nntp_printorder[0]));
	hr = calloc(1, sizeof(*hr));
	nntp_printorder[nntp_host_count-1] = hr;
	hr->link = nntp_hosts;
	nntp_hosts = hr;
	strcpy(hr->hostname, hostname);
	}
    else
	if (strlen(hostname) > strlen(hr->hostname))
	    strcpy(hr->hostname, hostname);
    return(hr);
    }

static int
compare(a, b)
struct host_rec **a, **b;
{
    return(strcmp((*a)->hostname, (*b)->hostname));
}

static int
compare_nntp(a, b)
struct nntp_host_rec **a, **b;
{
    return(strcmp((*a)->hostname, (*b)->hostname));
}

static int
compareg(a, b)
struct junk_rec **a, **b;
{
    return(strcmp((*a)->newsgroup, (*b)->newsgroup));
}

static char *
strtolower(str)
char *str;
   {
   char *s = str;

   while (*s)
	{
	if (isupper(*s))
	    *s = tolower(*s);
	++s;
	}
   return(str);
   }

int *c$_tmphead = (int *) 0;

/************************************************************************/

int *c$alloc_tmp(size)
  int size;
{
  int *c$_tmp;

  c$_tmp = (int *) malloc(size + 4);
  *c$_tmp = (int) c$_tmphead;
  c$_tmphead = c$_tmp;
  return(c$_tmphead + 1);
}

/************************************************************************/

struct dsc$descriptor *c$dsc(c$_string)
   char *c$_string;
{
   int *tmp;
   struct dsc$descriptor *c$_tmpdesc;

   while (c$_tmphead) {
      tmp = (int *)(*c$_tmphead);
      free(c$_tmphead);
      c$_tmphead = tmp;
      }                     
   c$_tmpdesc = (struct dsc$descriptor *) c$alloc_tmp(8);
   c$_tmpdesc->dsc$w_length = strlen(c$_string);
   c$_tmpdesc->dsc$b_dtype = DSC$K_DTYPE_T;
   c$_tmpdesc->dsc$b_class = DSC$K_CLASS_S;
   c$_tmpdesc->dsc$a_pointer = c$_string;
   return(c$_tmpdesc);
}

/************************************************************************/

struct dsc$descriptor *c$ldsc(c$_string,c$_length)
   char *c$_string;
   int c$_length;
{
   int *tmp;
   struct dsc$descriptor *c$_tmpdesc;

   while (c$_tmphead) {
      tmp = (int *)(*c$_tmphead);
      free(c$_tmphead); 
      c$_tmphead = tmp;
      }
   c$_tmpdesc = (struct dsc$descriptor *) c$alloc_tmp(8);
   c$_tmpdesc->dsc$w_length = c$_length;
   c$_tmpdesc->dsc$b_dtype = DSC$K_DTYPE_T;
   c$_tmpdesc->dsc$b_class = DSC$K_CLASS_S;
   c$_tmpdesc->dsc$a_pointer = c$_string;
   return(c$_tmpdesc);
}

/*
 *  c$rfi
 *
 *  Generate a reference to a temp integer value
 */

int *c$rfi(value)
  int value;
{
  int *tmp = c$alloc_tmp(4);

  *tmp = value;
  return(tmp);
}
/************************************************************************/

void cksts(status)
    int status;
{
    if (!(status & STS$M_SUCCESS))
	lib$signal(status);
}

void cli_qualifier_action(qualifier,action_routine)
   const char *qualifier;
   void (*action_routine)();
{
    int value;

    cli_get_bool_value(qualifier, &value);
    if (value)
	(*action_routine)();
}

void cli_get_bool_value(qualifier,value)
   const char *qualifier;
   int *value;
{
    int status;

    *value = 0;
    status = cli$present(c$dsc(qualifier));
    if (status == CLI$_PRESENT || status == CLI$_LOCPRES)
      *value = 1;
    else if (status == CLI$_ABSENT || status == CLI$_NEGATED ||
             status == CLI$_LOCNEG)
      *value = 0;
}

void cli_get_int_value(qualifier,value)
   const char *qualifier;
   int *value;
{
    unsigned long int status;
    $DESCRIPTOR_D(rdesc);
    extern int ots$cvt_tz_l();
    extern int ots$cvt_tu_l();
    extern int ots$cvt_to_l();
    int (*rtn)();
    long int num;

    if (cli$present(c$dsc(qualifier)) == CLI$_PRESENT)
	{
	cksts(status = cli$get_value(c$dsc(qualifier),&rdesc,NULL));
	rtn = ots$cvt_tu_l;
	if ((DLEN(rdesc) > 2) && (((char *)(rdesc.dsc$a_pointer))[0] == '%'))
	    {
	    if (((char *)(rdesc.dsc$a_pointer))[1] == 'X')
		rtn = ots$cvt_tz_l;
	    if (((char *)(rdesc.dsc$a_pointer))[1] == 'O')
		rtn = ots$cvt_to_l;
	    }
	cksts(status = (*rtn)(&rdesc, &num, 4, 0));
	*value = num;
	}
}

void cli_get_string_value(qualifier,value)
   const char *qualifier;
   char **value;
{
    unsigned long int status;
    $DESCRIPTOR_D(rdesc);
    $DESCRIPTOR_D(vdesc);
    long int num;

    if (cli$present(c$dsc(qualifier)) == CLI$_PRESENT)
	{
	cksts(status = cli$get_value(c$dsc(qualifier),&rdesc,NULL));
	num = 1 + DLEN(rdesc);
	str$copy_r(&vdesc, &num, DPTR(rdesc));
	*value = DPTR(vdesc);
	(*value)[DLEN(rdesc)] = '\0';
	str$free1_dx(&rdesc);
	}
}

void cli_get_time_value(qualifier,value)
   const char *qualifier;
   uquad *value;
{
    char tmp1[64], tmp2[64];
    unsigned long int status;
    $DESCRIPTOR_D(rdesc);
    uquad restim = { 0, 0 };
   
    if (cli$present(c$dsc(qualifier)) == CLI$_PRESENT)
	{
	status = cli$get_value(c$dsc(qualifier),&rdesc,NULL);
	if (status == CLI$_ABSENT)
	    cksts(sys$gettim(&restim));
	else
	    {
	    cksts(status);
	    strncpy(tmp1,DPTR(rdesc),DLEN(rdesc));
	    tmp1[DLEN(rdesc)] = '\0';
	    str$free1_dx(&rdesc);
	    if (!strcmp(tmp1,"TODAY"))
		{
		cksts(sys$asctim(0,c$ldsc(tmp2,sizeof(tmp2)),0,0));
		*(strchr(tmp2,':')-3) = '\0';
		sprintf(tmp1,"%s 00:00:00.00",tmp2);
		}
	    cksts(sys$bintim(c$dsc(tmp1),&restim));
	    }
	*value = restim;
	}
}

void init_cli(char *table, const char *name)
{
    $DESCRIPTOR_D(cmd);
    int i;
  
    cksts(cli$get_value(c$dsc("$VERB"),&cmd,NULL));

    if (memcmp(DPTR(cmd),name,DLEN(cmd)) == 0)
	return;    /* the command must have been properly defined! */
    /* this code assumes that the verb is shorter than the foreign
	symbol (which includes device:[dir], so, should be reasonable) */
    if ((DLEN(cmd) == 3) && 
	(0 == memcmp("RUN",DPTR(cmd),3)))
	{
	i = strlen(name);
	str$copy_r(&cmd, &i, name);
	}
    else
	{
	cksts(cli$get_value(c$dsc("$LINE"),&cmd,NULL));

	for (i = 0; (i < DLEN(cmd)) &&
		    (((char *)(cmd.dsc$a_pointer))[i] != ' ') &&
		    (((char *)(cmd.dsc$a_pointer))[i] != '/'); ++i)
	    ((char *)(cmd.dsc$a_pointer))[i] = (i < strlen(name))?name[i]:' ';
	}

    cksts(cli$dcl_parse(&cmd,table,lib$get_input,lib$get_input,NULL));
}

time_t vms_to_unix_time(time)
int time[2];
{
static int ubase[2] = {0x4beb4000,0x007c9567}; /* Unix Time Base 1/1/70 */
int     utime[2];
time_t  result;
int     divisor = 10000000;
int     remainder;

    lib$subx(time, ubase, utime);
    lib$ediv(&divisor, utime, &result, &remainder);
    return(result);
}
