
/*
 *  RNEWS.C
 *
 *  Copyright 1988 by William Loftus.  All rights reserved.
 *
 *  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 "news.h"
#include <errno.h>
#include <ctype.h>
#include <sys/stat.h>	    /* outrageous place for the mkdir() proto! */
#include <log.h>

extern char *TmpFileName();

IDENT(".02");

static int dofile(void);
static void unbatch(void);
static long store_art(long size);
static void uncompress_news(void);
static void news_exit(int stat);

#define exit news_exit

char homedir[128];

char *NewsDir;
char *wkname;

#define PROG_NAME "Rnews"

int
main(int argc, char **argv)
{
	register int ret = 0;

	LogProgram = PROG_NAME;
	fclose(stderr); /* Assume we are running in the background */
	while (argc > 1 && argv[1][0] == '=') {
		switch (argv[1][1]) {
		default:
			printf("unknown flag '%s' ignored\n", argv[1]);
			break;
		case 'x':
			LogLevel = atoi(&argv[1][2]);
			break;
		}
		--argc, ++argv;
	}

	getcwd(homedir, sizeof(homedir));
	NewsDir = GetConfigDir(UUNEWS);
	if (chdir(NewsDir) != 0) {
	    ulog(0, "NewsDir doesn't exist; quitting");
	    exit(1);
	}
	wkname = strdup(TmpFileName("news-work"));

	if (argc == 2 && strcmp(argv[1], "UseNet") == 0)        /* HACK!!! */
		--argc;
	if (argc < 2) {
		ulog(1, "Starting standard input");
		ret += dofile();
	} else while (*++argv != NULL) {
		chdir(homedir);         /* so relative file names will work */
		ulog(1, "Starting file %s", *argv);
		if (freopen(*argv, "r", stdin) == NULL)
			ulog(0, "Cannot open input news file %s", *argv);
		else
			ret += dofile();
	}

	exit(ret);
	/* NOTREACHED */
	return -1;
}

static void
rewind_input(char *line)
{
	register int ch;
	register FILE *fp;

	if (rewind(stdin) == 0)
	    return;

	ulog(-1, "rewinding the hard way...");
	if ((fp = fopen(wkname, "w")) == NULL) {
		ulog(0, "Can't open temporary file");
		exit(1);
	}

	fprintf(fp, "%s", line);
	while ((ch = getc(stdin)) != EOF)
		fputc(ch, fp);
	fclose(fp);

	if (freopen(wkname, "r", stdin) == NULL) {
		ulog(0, "Can't reopen temporary file.");
		exit(1);
	}
}

static int
dofile(void)
{
	char buf[16];

	chdir(NewsDir);

	fgets(buf, 13, stdin);
	if (strncmp(buf, "#! cunbatch", 11) == 0) {
		uncompress_news();
	} else {
		rewind_input(buf);
		unbatch();
	}

	fclose(stdin);

	remove(wkname);
	return 0;
}

/*
 * 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
 *	"LIB/NewsGroups" control file.  Articles which are not listed
 *	in the control file are placed in the "junk" directory.  Articles
 *	are sequenced by the sequencer in "LIB/seqnews". This could
 *	possibly be updated to use a sequencer within each of the news
 *	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 struct group {
	struct group *next;
	char name[1];
} groups;

static void
initgroups(void)
{
	register struct group *gp;
	register long len;
	register FILE *fp;
	char buf[50];

	if (groups.next != NULL)
		return;

	if ((fp = openlib("newsgroups")) == NULL)       {
		ulog(0, "Couldn't open LIB/newsgroups file");
		exit(2);
	}

	gp = &groups;
	while (fgets(buf, sizeof(buf), fp) != NULL) {
		len = strlen(buf) - 1;
		buf[len] = '\0';
		if ((gp->next = (struct group *)malloc(4+len+1)) == NULL) {
			ulog(0, "Malloc failed!");
			exit(4);
		}
		gp = gp->next;
		strcpy(gp->name, buf);
	}
	gp->next = NULL;

	fclose(fp);
}

static char *
finddir(register char *dir)
{
	register struct group *gp, *dp = NULL;
	register char *p;
	register int c;

	do {
		p = dir;
		do	c = *++dir;
		while (c != ' ' && c != ',' && c != '\n' && c != '\0');
		*dir++ = '\0';

		for (gp = groups.next; gp != NULL; gp = gp->next) {
			if (gp == dp) break;
			if (strcmp(gp->name, p) == 0) {
				dp = gp;
				break;
			}
		}
	} while (c != '\n' && c != '\0');

	if (dp != NULL)
		return dp->name;
	return "junk";          /* Oops, not a known newsgroup */
}

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

static void
unbatch(void)
{
	register long size;
	char buf[20];

	if (fgets(buf, sizeof(buf), stdin) == NULL) {
		ulog(0, "Empty file!");
		return;
	}

	if (strncmp(buf, "#! rnews ", 9) != 0) {
		rewind_input(buf);
		(void)store_art(10000000L);
		return;
	}

	do {
		if (strncmp(buf, "#! rnews ", 9) != 0 &&
		    strncmp(buf, "! rnews ", 8) != 0) { /* kludge for bug */
			register char *cp;
			for (cp = buf; *cp != '\0'; ++cp)
				if (!isascii(*cp) ||
				   (!isprint(*cp) && !isspace(*cp)))
					*cp = '?';
			*--cp = '\0';
			ulog(0, "out of sync, skipping <%s>", buf);
			continue;
		}

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

		if (store_art(size) > 0) {
			/*
			 * If we got a truncated batch, don't process the
			 * last article; it will probably be received again.
			 */
			ulog(0, "truncated batch");
			break;
		}

	} while (fgets(buf, sizeof(buf), stdin) != NULL);
}

static char buf[1024];	/* this should be dynamic? */

static long
store_art(long size)
{
	register int c;
	register FILE *pfn;
	FILE *seq;
	unsigned long seqno;
	char *dir;

	initgroups();

	if ((pfn = fopen("ArticleTemp", "w")) == NULL) {
		ulog(0, "Could not creat article temp file");
		exit(3);
	}

	dir = "Rejects";
	while (fgets(buf, sizeof(buf), stdin)) {
		fprintf(pfn, "%s", buf);
		size -= strlen(buf);
		if (strncmp(buf, "Newsgroups: ", 11) == 0) {
			dir = finddir(buf+12);
			break;	/* Get out of the while */
		}
	}

	while(size > 0 && (c = getc(stdin)) != EOF)
		--size, putc(c, pfn);

	fflush(pfn);
	if (ferror(pfn)) {              /* disk full? */
		fclose(pfn);
		remove("ArticleTemp");
		ulog(0, "error writing temporary file");
		return -1;
	}
	fclose(pfn);

	sprintf(buf, "%s/.next", dir);
	if (IsDir(dir)) {
		if ((seq = fopen(buf, "r")) == NULL) {
			ulog(0, "sequencer file '%s' not found, will create",
					buf);
			seqno = 1000;
		} else {
			fscanf(seq, "%ld", &seqno);
			fclose(seq);
		}
	} else if (mkdir(dir) != 0) {
		ulog(0, "Cannot create directory for '%s'; article dropped",
						dir);
		return -1;
	} else {
		seqno = 1;
	}
	if ((seq = fopen(buf, "w")) == NULL) {
		ulog(0, "Couldn't create sequencer for '%s'; article dropped",
						dir);
		return -1;
	} else {
		fprintf(seq, "%ld\n", seqno + 1);
		fclose(seq);
	}

	sprintf(buf, "%s/%ld", dir, seqno);
	if (rename("ArticleTemp", buf) != 0) {
		remove("ArticleTemp");
		ulog(0, "Could not rename article to %s", buf);
	}

	return size;
}

/*
 * 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
#  define USERMEM	450000	/* default user memory */
#endif

#ifdef AMIGA			/* Commodore Amiga */
# define BITS		16
# define M_XENIX	/* so we can use small model (faster) */
# undef USERMEM
#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;
#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 */

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)         ((((code_int)1) << (n_bits)) - 1)

#ifdef XENIX_16
count_int * htab[9] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
unsigned short * codetab[5] = { NULL, NULL, NULL, NULL, NULL };
#else	/* Normal machine */
count_int	htab[HSIZE];
unsigned short	codetab[HSIZE];
#endif /* XENIX_16 */
code_int hsize = HSIZE; 	/* for dynamic table sizing */

/*
 * 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).
 */

#ifdef XENIX_16
# define tab_prefixof(i)        (codetab[(i)>>14][(i) & 0x3FFF])
# define tab_suffixof(i)        ((char_type *)htab[(i)>>15])[(i) & 0x7FFF]
# define de_stack		((char_type *)(htab[2]))
#else	/* Normal machine */
# define tab_prefixof(i)        codetab[i]
# define tab_suffixof(i)        (((char_type *)htab)[i])
/* FIXME??? This next line is suspect..... */
# define de_stack		((char_type *)(((long)htab)+(((code_int)1)<<BITS)))
#endif /* XENIX_16 */

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

static code_int getcode(void);
static void writeerr(void);

/*
 * block compression parameters -- after all codes are used up,
 * and compression rate changes, start over.
 */
int block_compress = BLOCK_MASK;
int clear_flg = 0;

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

/*
 * 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.
 */
#ifdef XENIX_16
static void *
safealloc(long size)
{
	register retry = 30;
	register void *p;

	while ((p = malloc(size)) == NULL) {
		if (--retry < 0) {
			ulog(0,
"Can't get memory to decompress news batch -- terminating");
			exit(20);
		}
		ulog(0,
"Can't get memory to decompress news batch -- will retry");
		sleep(30);
	}
	return p;
}
#endif /* XENIX_16 */

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

	maxcode = MAXCODE(n_bits = INIT_BITS);
	free_ent = ((block_compress) ? FIRST : 256);
	finchar = oldcode = getcode();
	if(oldcode == -1)       /* EOF already? */
		return; 	/* Get out of here */

#ifdef XENIX_16
	htab[0] = (code_int *)safealloc(8192*sizeof(code_int));
	htab[1] = (code_int *)safealloc(8192*sizeof(code_int));
	htab[2] = (code_int *)safealloc(8192*sizeof(char));
	for (code = 0; code < 5; ++code)
		codetab[code] = (unsigned short *)
				safealloc(16384*sizeof(unsigned short));
#endif /* XENIX_16 */

	/*
	 * As above, initialize the first 256 entries in the table.
	 */
	for (code = 255; code >= 0; code--) {
		tab_prefixof(code) = 0;
		tab_suffixof(code) = (char_type)code;
	}

	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 {	register int c = *--stackp;
			putc(c, stdout);        /* putc is often a macro! */
		} 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();

#ifdef XENIX_16
	free(htab[0]), htab[0] = NULL;
	free(htab[1]), htab[1] = NULL;
	free(htab[2]), htab[2] = NULL;
	for (code = 0; code < 5; ++code)
		free(codetab[code]), codetab[code] = NULL;
#endif /* XENIX_16 */
}

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

#ifndef vax
char_type rmask[9] = {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};
#endif /* vax */

static code_int
getcode(void)
{
	/*
	 * 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)
				/* won't get any bigger now */
				maxcode = maxmaxcode;
			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;
#ifdef vax
	asm("extzv      r10,r9,(r8),r11");
#else /* not a vax */
	/*
	 * 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;
#endif /* vax */
	offset += n_bits;

	return code;
}

#ifdef DEBUG

static void _fprintf(FILE *fi, const char *ctl, ...) { }

static /*volatile*/ void
printcodes(void)
{
	/*
	 * Just print out codes from input file.  For debugging.
	 */
	code_int code;
	int col = 0, bits;

	bits = n_bits = INIT_BITS;
	maxcode = MAXCODE(n_bits);
	free_ent = ((block_compress) ? FIRST : 256);
	while ((code = getcode()) >= 0) {
		if ((code == CLEAR) && block_compress) {
			free_ent = FIRST - 1;
			clear_flg = 1;
		} else if (free_ent < maxmaxcode)
			free_ent++;
		if (bits != n_bits) {
			_fprintf(stderr, "\nChange to %d bits\n", n_bits);
			bits = n_bits;
			col = 0;
		}
		_fprintf(stderr, "%5d%c", code,
			(col+=6) >= 74 ? (col = 0, '\n') : ' ');
	}
	_fprintf(stderr, "\n");
	exit(0);
}

static int
in_stack(register int c, register int stack_top)
{
	if ((isascii(c) && isprint(c) && c != '\\') || c == ' ') {
		de_stack[--stack_top] = c;
	} else {
		switch(c) {
		case '\n': de_stack[--stack_top] = 'n'; break;
		case '\t': de_stack[--stack_top] = 't'; break;
		case '\b': de_stack[--stack_top] = 'b'; break;
		case '\f': de_stack[--stack_top] = 'f'; break;
		case '\r': de_stack[--stack_top] = 'r'; break;
		case '\\': de_stack[--stack_top] = '\\'; break;
		default:
			de_stack[--stack_top] = '0' + c % 8;
			de_stack[--stack_top] = '0' + (c / 8) % 8;
			de_stack[--stack_top] = '0' + c / 64;
			break;
		}
		de_stack[--stack_top] = '\\';
	}
	return stack_top;
}
#endif /* DEBUG */

static void
writeerr(void)
{
	ulog(0, "Error while writing to '%s'", wkname);
	remove(wkname);
	exit(1);
}

static void
uncompress_news(void)
{
	if (freopen(wkname, "w", stdout) == NULL) {
		ulog(0, "Can't open uncompressed file for output");
		return;
	}

	/* Check the magic number */
	if ((getc(stdin)!=(magic_header[0] & 0xFF))
	  || (getc(stdin)!=(magic_header[1] & 0xFF))) {
		ulog(0, "input file not in compressed format");
		exit(1);
	}
	maxbits = getc(stdin);  /* set -b from file */
	block_compress = maxbits & BLOCK_MASK;
	maxbits &= BIT_MASK;
	maxmaxcode = ((code_int)1) << maxbits;

	decompress();

	fclose(stdout);
	if (freopen(wkname, "r", stdin) == NULL) {
		ulog(0, "Can't reopen uncompressed file for input");
		return;
	}
	unbatch();
}

#undef exit
static /*volatile*/ void
news_exit(int stat)
{
	chdir(homedir);
	exit(stat);
}

