
/*
 *  RNEWS.C
 *
 *  Copyright 1988 by William Loftus.  All rights reserved.
 *  (C) Copyright 1989-1990 by Matthew Dillon,  All Rights Reserved.
 *
 *  Version 0.60 Beta
 *
 *  This is rnews for compressed news.	News 2.11 will uux
 *  a file to this system that will be in compressed format.
 *  This program will strip off the "#! cunbatch", uncompress
 *  the news, and call unbatch.  If the news is not in compressed
 *  format it will just pass it to unbatch.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <log.h>
#include "/version.h"
#include <errno.h>
#include <ctype.h>

#define MAXGROUPS   1024

IDENT(".01");

void uncompress_news();
void unbatch_news();
void writeerr();
void CreateDirsFor();
char *TmpFileName();

int	Compressed;
char	*SeqFile = "UULIB:SeqNews";
char	*TmpFile;

void
main (argc, argv)
int argc;
char **argv;
{
    FILE *fp;
    char buf[32];
    int ch;
    int status = 0;

    LogProgram = "RNews";
    TmpFile = TmpFileName("T:mail");

    fclose(stderr);  /* Assume we are running in the background */

    fgets(buf, 13, stdin);

    if (!strncmp(buf,"#! cunbatch", 11)) {
	Compressed = 1;
    } else if (!strncmp(buf,"#! rnews", 8)) {
	fp = fopen(TmpFile, "w");
	if (fp == (char *)NULL) {
	    ulog(-1, "Unable to open %s for output", TmpFile);
	    exit(1);
	}
	fprintf(fp, "%s\n",  buf);
	while ((ch = getchar()) != EOF) {
	    fputc(ch, fp);
	}
	fclose(fp);
	Compressed = 0;
    } else {
	ulog(-1, "News in wrong format");
	remove(TmpFile);
	exit(1);
    }

    if (Compressed) {
	uncompress_news();
    }

    unbatch_news();

    status = remove(TmpFile);

    if (status != 0) {
	ulog(-1, "Unable to remove %s", TmpFile);
	exit(1);
    }
}

/*
 * Unbatch, an Amiga unbatcher.
 *
 *  Written by Fred Cassirer, 10/8/88.
 *  Some unbatch code originally taken from News2.11 sources.
 *
 *  Ported to Lattice 5.0
 *  Added config.h file
 *  Added use of ERRORFILE
 *  11/23/1988	Dan 'Sneakers' Schein
 *
 * This code (as well as the unbatch stuff) is free for anyone who thinks
 * they can get some use out of it.
 *
 *  Articles will only
 *  be placed in newsgroups as defined in the "UULIB:NewsGroups"
 *  control file.  Articles which are not listed in the control file
 *  are placed in the "Reject" directory.  Articles are sequenced by
 *  the sequencer in "UULIB:seqnews". This could possibly be updated
 *  to use a sequencer within each of the subdirectories, to more closely
 *  resemble the News system under Unix.
 *
 *  Unbatch will also take command line args of files to be unbatched.
 *  Files on the command line are not removed, they should be removed
 *  after running unbatch.
 */

static char *groups[MAXGROUPS];

void
unbatch_news()
{
    if (!freopen(TmpFile, "r", stdin)) {
	ulog(-1, "Can't unbatch, expected file %s to exist", TmpFile);
	exit(1);
    }
    if (unbatch())
	ulog(-1, "Unbatch failed");
    fclose(stdin);
}


void
initgroups(newsgroups)
char *newsgroups;
{
    static char buf[BUFSIZ];
    static int initflag = 0;
    int i, len;
    FILE *fp;

    if (initflag)
	return;

    initflag = 1;

    if ((fp = fopen(newsgroups, "r")) == NULL)  {
	ulog(-1, "Unable to open newsgroup file %s", newsgroups);
	remove(TmpFile);
	exit(2);
    }

    i = 0;
    while (fgets(buf, BUFSIZ, fp) && (i < MAXGROUPS)) {
	len = strlen(buf) - 1;
	buf[len] = '\0';
	if ((groups[i] = malloc(len+1)) == NULL) {
	    ulog(-1, "malloc failed");
	    remove(TmpFile);
	    exit(4);
	}
	strcpy(groups[i], buf);
	i++;
    }
    groups[i] = NULL;
    fclose(fp);
}

char *
finddir(dir)
char *dir;
{
    static char work[40];
    char *p;
    int i;

    while (1) {
	p = work;
	while ((*dir != ' ') && (*dir != ',') && *dir)
	    *p++ = *dir++;

	*p = '\0';
	for (i = 0; groups[i] != NULL; ++i) {
	    if (strcmpi(groups[i], work) == 0)
		return(work);
	}

	if (!*dir++)
	    break;
    }
    return(NULL);
}

/*
 * unbatchnews: extract news in batched format and process it one article
 * at a time.  The format looks like
 *	#! rnews 1234
 *	article containing 1234 characters
 *	#! rnews 4321
 *	article containing 4321 characters
 *
 *	or
 *
 *	#! command [args]
 *	calls LIBDIR/command [args] to process the news
 */

#define MAXARGS 	20
#define FILES_PER_LINE	5

char buf[BUFSIZ];
char homedir[128];

unbatch()
{
    int c;
    FILE *pfn;
    long size;
    FILE *seq;
    char filename[128], tmpname[30];
    int  seqno, fcount;
    long atol();
    char *dir;

    initgroups("UULIB:Newsgroups");

    if ((seq = fopen(SeqFile, "r")) != NULL) {
	fscanf(seq,"%d",&seqno);
	fclose(seq);
    } else {
	ulog(-1, "Sequence file %s not found, creating", SeqFile);
	seqno = 0;
    }

    if (gets(buf) == NULL) {
	ulog(-1, "Empty file!");
	return(2);
    }

    if (strncmp(buf, "#! rnews ", 9) != 0) {
	ulog(-1, "unbatch: rnews cmd not supported '%s'!", buf);
	return(1);
    }

    fcount = 0;
    do {
	if (!fcount) {
	    fflush(stdout);
	    fcount = FILES_PER_LINE;
	}
	while (strncmp(buf, "#! rnews ", 9) && strncmp(buf, "! rnews ", 8)) { /* kludge for bug */
	    char *cp;
	    for (cp = buf; *cp != '\0'; ++cp) {
		if (!isascii(*cp) || (!isprint(*cp) && !isspace(*cp)))
		    *cp = '?';
	    }
	    ulog(-1, "out of sync, skipping %s", buf);
	    if (gets(buf) == NULL)
		return(0);
	}

	size = atol(buf + (buf[0] == '#' ? 9 : 8));
	if (size <= 0) {
	    ulog(-1, "nonsense size %ld", size);
	    continue;
	}

	sprintf(tmpname,"UUNEWS:Tmp%d", ++seqno);
	fcount--;
	fflush(stdout);
	if ((pfn = fopen(tmpname, "w")) == NULL) {
	    ulog(-1, "Can't create %s", tmpname);
	    exit(3);
	}

	while (gets(buf)) {
	    fprintf(pfn, "%s\n", buf);
	    size -= strlen(buf) + 1;
	    if (strncmp(buf, "Newsgroup", 9) == 0) {
		if (!(dir = finddir(buf+12))) {
		    ulog(-1, "Articles for %s placed in Rejects", buf+12);
		    dir = "Rejects";
		}
		sprintf(filename, "UUNEWS:%s/%d", dir, seqno);
		break; /* Get out of the while */
	    }
	}

	if (size > 0) {
	    while(--size >= 0 && (c = getc(stdin)) != EOF)
		putc(c, pfn);
	    if (ferror(pfn) || fclose(pfn)) {   /* disk full? */
		ulog(-1, "Error writing to temp file");
		break;
	    }
	    if (rename(tmpname, filename) < 0) {
		CreateDirsFor(filename);
		rename(tmpname, filename);
	    }
	}

       /*
	* If we got a truncated batch, don't process the
	* last article; it will probably be received again.
	*/

	if (size > 0) {
	    ulog(-1, "truncated batch");
	    break;
	}
    } while (gets(buf) != NULL);

    if ((seq = fopen(SeqFile, "w")) == NULL) {
	ulog(-1, "Unable to create %s", SeqFile);
    }
    fprintf(seq, "%d\n", seqno);
    fclose(seq);
    return(0);
}

int
System(cmd)
char *cmd;
{
    long nl;
    int return_value;

    if ((nl = (long)Open("null:wpl", 1006)) != NULL) {
	return_value = (Execute(cmd, nl, nl) == -1) ? 0 : -1;
	Close(nl);
    } else {
	ulog(-1, "Unable to open NULL: device, did you mount it?");
	return_value = -1;
    }
    return return_value;
}


/*
 * Set USERMEM to the maximum amount of physical user memory available
 * in bytes.  USERMEM is used to determine the maximum BITS that can be used
 * for compression.
 *
 * SACREDMEM is the amount of physical memory saved for others; compress
 * will hog the rest.
 */
#ifndef SACREDMEM
#define SACREDMEM	0
#endif

#ifndef USERMEM
/*# ifdef AMIGA
#   define USERMEM	200000
# else*/
#   define USERMEM	450000	/* default user memory */
/* # endif*/
#endif

#ifdef interdata		/* (Perkin-Elmer) */
#define SIGNED_COMPARE_SLOW	/* signed compare is slower than unsigned */
#endif

#ifdef pdp11
# define BITS	12	/* max bits/code for 16-bit machine */
# define NO_UCHAR	/* also if "unsigned char" functions as signed char */
# undef USERMEM
#endif /* pdp11 */	/* don't forget to compile with -i */

#ifdef z8000
# define BITS	12
# undef vax		/* weird preprocessor */
# undef USERMEM
#endif /* z8000 */

#ifdef pcxt
# define BITS	12
# undef USERMEM
#endif /* pcxt */

#ifdef USERMEM
# if USERMEM >= (433484+SACREDMEM)
#  define PBITS 16
# else
#  if USERMEM >= (229600+SACREDMEM)
#   define PBITS	15
#  else
#   if USERMEM >= (127536+SACREDMEM)
#    define PBITS	14
#   else
#    if USERMEM >= (73464+SACREDMEM)
#     define PBITS	13
#    else
#     define PBITS	12
#    endif
#   endif
#  endif
# endif
# undef USERMEM
#endif /* USERMEM */

#ifdef PBITS		/* Preferred BITS for this memory size */
# ifndef BITS
#  define BITS PBITS
# endif BITS
#endif /* PBITS */

#if BITS == 16
# define HSIZE	69001		/* 95% occupancy */
#endif
#if BITS == 15
# define HSIZE	35023		/* 94% occupancy */
#endif
#if BITS == 14
# define HSIZE	18013		/* 91% occupancy */
#endif
#if BITS == 13
# define HSIZE	9001		/* 91% occupancy */
#endif
#if BITS <= 12
# define HSIZE	5003		/* 80% occupancy */
#endif

#ifdef M_XENIX			/* Stupid compiler can't handle arrays with */
# if BITS == 16 		/* more than 65535 bytes - so we fake it */
#  define XENIX_16
# else
#  if BITS > 13 		/* Code only handles BITS = 12, 13, or 16 */
#   define BITS 13
#  endif
# endif
#endif

/*
 * a code_int must be able to hold 2**BITS values of type int, and also -1
 */
#if BITS > 15
typedef long int	code_int;
#else
typedef int		code_int;
#endif

#ifdef SIGNED_COMPARE_SLOW
typedef unsigned long int count_int;
typedef unsigned short int count_short;
#else
typedef long int	  count_int;
#endif

#ifdef NO_UCHAR
 typedef char	char_type;
#else
 typedef	unsigned char	char_type;
#endif /* UCHAR */
char_type magic_header[] = { "\037\235" };      /* 1F 9D */

/* Defines for third byte of header */
#define BIT_MASK	0x1f
#define BLOCK_MASK	0x80
/* Masks 0x40 and 0x20 are free.  I think 0x20 should mean that there is
   a fourth header byte (for expansion).
*/
#define INIT_BITS 9			/* initial number of bits/code */

#include <stdio.h>
#include <ctype.h>

int n_bits;				/* number of bits/code */
int maxbits = BITS;			/* user settable max # bits/code */
code_int maxcode;			/* maximum code, given n_bits */
code_int maxmaxcode = 1 << BITS;	/* should NEVER generate this code */
#define MAXCODE(n_bits)        ((1 << (n_bits)) - 1)

count_int htab [HSIZE];
unsigned short codetab [HSIZE];
#define htabof(i)       htab[i]
#define codetabof(i)    codetab[i]
code_int hsize = HSIZE; 		/* for dynamic table sizing */
count_int fsize;

/*
 * To save much memory, we overlay the table used by compress() with those
 * used by decompress().  The tab_prefix table is the same size and type
 * as the codetab.  The tab_suffix table needs 2**BITS characters.  We
 * get this from the beginning of htab.  The output stack uses the rest
 * of htab, and contains characters.  There is plenty of room for any
 * possible stack (stack used to be 8000 characters).
 */

#define tab_prefixof(i) codetabof(i)
#define tab_suffixof(i)        (((char_type *)htab)[i])
#define tab_suffixof1(i)        (((long)htab)+(long)(i))
#define de_stack	       (char_type *)(tab_suffixof1(1<<BITS))

code_int free_ent = 0;			/* first unused entry */
int exit_stat = 0;

code_int getcode();

void _fprintf() { }

int nomagic = 0;	/* Use a 3-byte magic number header, unless old file */

/*
 * block compression parameters -- after all codes are used up,
 * and compression rate changes, start over.
 */
int block_compress = BLOCK_MASK;
int clear_flg = 0;
long int ratio = 0;
#define CHECK_GAP 10000 /* ratio check interval */
count_int checkpoint = CHECK_GAP;
/*
 * the next two codes should not be changed lightly, as they must not
 * lie within the contiguous general code space.
 */
#define FIRST	257	/* first free entry */
#define CLEAR	256	/* table clear output code */

int (*bgnd_flag)();

static void decompress ();
static void compress ();

void
uncompress_news()
{
    if (freopen(TmpFile, "w", stdout) == NULL) {
       ulog(-1, "Can't open uncompressed file %s", TmpFile);
       exit(1);
    }

    maxbits = BITS;
    maxmaxcode = 1 << maxbits;

    /* Check the magic number */
    if (nomagic == 0) {
       if ((getchar()!=(magic_header[0] & 0xFF))
	  || (getchar()!=(magic_header[1] & 0xFF))) {
		ulog(-1, "stdin: not in compressed format");
		exit(1);
	}
	maxbits = getchar();    /* set -b from file */
	block_compress = maxbits & BLOCK_MASK;
	maxbits &= BIT_MASK;
	maxmaxcode = 1 << maxbits;
	fsize = 100000; 	/* assume stdin large for USERMEM */
    }
  decompress();

  fclose(stdout);
}

static int offset;
long int in_count = 1;			/* length of input */
long int bytes_out;			/* length of compressed output */
long int out_count = 0; 		/* # of codes output (for debugging) */


/*
 * Decompress stdin to stdout.	This routine adapts to the codes in the
 * file building the "string" table on-the-fly; requiring no table to
 * be stored in the compressed file.  The tables used herein are shared
 * with those of the compress() routine.  See the definitions above.
 */

static void decompress() {
    register char_type *stackp;
    register int finchar;
    register code_int code, oldcode, incode;

    /*
     * As above, initialize the first 256 entries in the table.
     */
    maxcode = MAXCODE(n_bits = INIT_BITS);
    for ( code = 255; code >= 0; code-- ) {
	tab_prefixof(code) = 0;
	tab_suffixof(code) = (char_type)code;
    }
    free_ent = ((block_compress) ? FIRST : 256 );

    finchar = oldcode = getcode();
    if(oldcode == -1)   /* EOF already? */
	return; 		/* Get out of here */
    putchar( (char)finchar );   /* first code must be 8 bits = char */
    if(ferror(stdout))          /* Crash if can't write */
	writeerr();
    stackp = de_stack;

    while ( (code = getcode()) > -1 ) {

	if ( (code == CLEAR) && block_compress ) {
	    for ( code = 255; code >= 0; code-- )
		tab_prefixof(code) = 0;
	    clear_flg = 1;
	    free_ent = FIRST - 1;
	    if ( (code = getcode ()) == -1 )    /* O, untimely death! */
		break;
	}
	incode = code;
	/*
	 * Special case for KwKwK string.
	 */
	if ( code >= free_ent ) {
	    *stackp++ = finchar;
	    code = oldcode;
	}

	/*
	 * Generate output characters in reverse order
	 */
#ifdef SIGNED_COMPARE_SLOW
	while ( ((unsigned long)code) >= ((unsigned long)256) ) {
#else
	while ( code >= 256 ) {
#endif
	    *stackp++ = tab_suffixof(code);
	    code = tab_prefixof(code);
	}
	*stackp++ = finchar = tab_suffixof(code);

	/*
	 * And put them out in forward order
	 */
	do
	    putchar ( *--stackp );
	while ( stackp > de_stack );

	/*
	 * Generate the new entry.
	 */
	if ( (code=free_ent) < maxmaxcode ) {
	    tab_prefixof(code) = (unsigned short)oldcode;
	    tab_suffixof(code) = finchar;
	    free_ent = code+1;
	}
	/*
	 * Remember previous code.
	 */
	oldcode = incode;
    }

    fflush( stdout );
    if(ferror(stdout))
	writeerr();
}

char_type lmask[9] = {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00};
char_type rmask[9] = {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};

/*****************************************************************
 * TAG( getcode )
 *
 * Read one code from the standard input.  If EOF, return -1.
 * Inputs:
 *	stdin
 * Outputs:
 *	code or -1 is returned.
 */

code_int
getcode() {
    /*
     * On the VAX, it is important to have the register declarations
     * in exactly the order given, or the asm will break.
     */
    register code_int code;
    static int offset = 0, size = 0;
    static char_type buf[BITS];
    register int r_off, bits;
    register char_type *bp = buf;

    if ( clear_flg > 0 || offset >= size || free_ent > maxcode ) {
	/*
	 * If the next entry will be too big for the current code
	 * size, then we must increase the size.  This implies reading
	 * a new buffer full, too.
	 */
	if ( free_ent > maxcode ) {
	    n_bits++;
	    if ( n_bits == maxbits )
		maxcode = maxmaxcode;	/* won't get any bigger now */
	    else
		maxcode = MAXCODE(n_bits);
	}
	if ( clear_flg > 0) {
	    maxcode = MAXCODE (n_bits = INIT_BITS);
	    clear_flg = 0;
	}
	size = fread( buf, 1, n_bits, stdin );
	if ( size <= 0 )
	    return -1;			/* end of file */
	offset = 0;
	/* Round size down to integral number of codes */
	size = (size << 3) - (n_bits - 1);
    }
    r_off = offset;
    bits = n_bits;
	/*
	 * Get to the first byte.
	 */
	bp += (r_off >> 3);
	r_off &= 7;
	/* Get first part (low order bits) */
#ifdef NO_UCHAR
	code = ((*bp++ >> r_off) & rmask[8 - r_off]) & 0xff;
#else
	code = (*bp++ >> r_off);
#endif /* NO_UCHAR */
	bits -= (8 - r_off);
	r_off = 8 - r_off;		/* now, offset into code word */
	/* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
	if ( bits >= 8 ) {
#ifdef NO_UCHAR
	    code |= (*bp++ & 0xff) << r_off;
#else
	    code |= *bp++ << r_off;
#endif /* NO_UCHAR */
	    r_off += 8;
	    bits -= 8;
	}
	/* high order bits. */
	code |= (*bp & rmask[bits]) << r_off;
    offset += n_bits;

    return code;
}

char *
rindex(s, c)            /* For those who don't have it in libc.a */
char *s, c;
{
    char *p;
    for (p = NULL; *s; s++)
	if (*s == c)
	    p = s;
    return(p);
}

void
writeerr()
{
    perror ( TmpFile );
    unlink ( TmpFile );
    exit ( 1 );
}

/*
 *  assign:path/path/path.../filename
 *
 *  creates path directories
 */

void
CreateDirsFor(filename)
char *filename;
{
    short i;
    short j;

    for (i = 0; ; ++i) {
	for (j = i; filename[j] && filename[j] != ':' && filename[j] != '/'; ++j);
	if (filename[j] == 0)
	    break;
	if (filename[j] == ':') {
	    i = j;
	    continue;
	}
	filename[j] = 0;
	mkdir(filename);        /*  may fail if parents already exist */
	filename[j] = '/';
    }
}

