/*
**
**++
**  FACILITY:
**      NNTP_XFER
** 
**  ABSTRACT:
**      Use the NNTP NEWNEWS commands to implement a feed initiated by the
**      receiver rather than the IHAVE/SENDME protocol which is sender
**      initiated.
** 
**  AUTHOR:
**      Geoff Huston
** 
**  COPYRIGHT:
**      Copyright © 1989,1990,199,1992
** 
**  CONTRIBUTIONS:
**      UCX Support botched in by D.J.Young (CNBR10@UK.AC.STRATH.VAXA) 16th August 1990
**      V6.0-1   6-Nov-1990 Edited in by GIH
**        - /GROUP stuff modified by R.B. Rodger (CNBS06@UK.AC.STRATH.VAXA)
**          1. Now accepts ^ as the 'no' symbol in the /GROUPS="*,!bananas"
**             list  -> /Groups="*,^bananas".
**          2. Lower cases group names
**          3. Uses the filename NNTP_nodename_SELECTIVE.BATCH instead of
**             NNTP_nodename_grouplist.BATCH.
**      V6.0-4  25-May-1991 volz@process.com
**        - Added TCPware support
**      V6.1    24-Jan-1992 gih
**        - reformat source
**      V6.1     9-Feb-1992     David E. Bellamy - bellamy@commerce.uq.oz.au
**        - Bug fix to UCX socket port assignments
**      V6.1b7	 8-Aug-1993	Charles Bailey  bailey@genetics.upenn.edu
**        - add mem_fail() routine as part of updated memory management
**	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  14-Feb-1995     Mark Martinec   mark.martinec@ijs.si
**	  - bumped the size of cmd in collect_newids() to minimize the
**	    chance of overflow in sprintf while compiling NEWNEWS command.
**	    This is just a quick fix. (suggested by wayne.westmoreland@srs.gov)
**	  - fix the year 2000 problem when generating log
**	V6.1b11	 3-Jun-1997	Mark Martinec   mark.martinec@ijs.si
**	  - replace routines nntp_read and nntp_read_line with newer
**	    from module NEWSREMCLIENT.C
**--
**/

#ifdef vaxc
#module NNTP_XFER "V6.2"
#endif

#define _NNTP_XFER_C
#define _NNTP_MAIN_PROGRAM

#include "nntpinclude.h"

#if NOGLOBALREF
extern void nntpxfercmd();
#else
globalref int nntpxfercmd;
#endif

#ifdef TCPWARE
#define TCPWARE 1
#define __TYPES_H_DEFINED
#endif

#if defined(TWG) || defined(MULTINET) || defined(TCPWARE)
struct hostent *dest_host;
struct sockaddr_in data_socket = {0};
#endif

#ifdef UCX
struct mysockaddr {
	short           inet_family;
	short           inet_port;
	int             inet_adrs;
	char            bklb[8];
};

struct itlist {
	int             lgth;
	struct mysockaddr *hst;
};

static short    sck_parm[2];
static struct mysockaddr local_host, remote_host;
struct itlist   lhst_adrs, rhst_adrs;
#endif

/* compile time constants */
#define NNTP_PORT	119
#define CLIENT_TIMER	250
#define RESP_TIMER	30
#define MAX_RESTART_ATTEMPT	20
#define MAX_BATCH_SIZE	250000


/* program constants */

#define X_BUF_SIZE	2048

char            net_open_chan[128] = {""},
                ibuf[X_BUF_SIZE],
                fnode[256],
                fgroup[256],
                scratch_area[256];

short           net_chan = 0;

int             cmd_code, offered = 0, accepted = 0, proto_num = 0,
		net_proto = 0, debugging = 0, logging = 0,
		days = 0, hours = 1, decnetstream = 0,
		ibuf_l = 0;  /* strlen(ibuf) */

struct iosb_block {
	unsigned short  iostatus;
	unsigned short  iosize;
	int             netinfo;
}               iosb,
                write_iosb,
                read_iosb;

char            node[128], group[4096], logfile[256], taskstr[132];

char           *s_to_upper();


/* Return pointer to a statically allocated string
   containing VMS error message associated with the given status.
   Stat1 and stat2 should belong to the facility SYSTEM.
   Usually stat1 is status returned by the system call (e.g. $QIOW)
   as the function value and stat2 is the IOSB status.
 */
char *getsyserrmsg(int stat1, int stat2)
{
  static char msg[128];
  static struct dsc$descriptor_s msg_dsc =
    { 128-1,DSC$K_DTYPE_T,DSC$K_CLASS_S,msg };
  unsigned short msg_l;
  int status;

  _c$cks(sys$getmsg((stat1&1)?stat2:stat1, &msg_l, &msg_dsc, 3, 0));
  msg[msg_l] = '\0';
  return msg;
}


void closefiles()
{
  return;
}

/*
 * nosysprv
 * 
 * Turn off installed sysprv - BUT if user already had sysprv from AUTHORIZE
 * then leave it on (as there is no change in user functionality)
 */

void nosysprv()
{
	unsigned int    authprivs[2], msysprv[2];
	int             item = JPI$_PROCPRIV;

	msysprv[0] = PRV$M_SYSPRV;
	msysprv[1] = 0;
	c$cks(lib$getjpi(&item, 0, 0, &authprivs, 0, 0));
	if (!(authprivs[0] & PRV$M_SYSPRV))
		c$cks(sys$setprv(0, msysprv, 0, 0));
}

/*
 * sysprv
 * 
 * Turn sysprv on as a temp priv - assumes image was installed with SYSPRV
 */

void sysprv()
{
	unsigned int    msysprv[2];

	msysprv[0] = PRV$M_SYSPRV;
	msysprv[1] = 0;
	c$cks(sys$setprv(1, msysprv, 0, 0));
}

/*
 * nntp_write
 * 
 * Synchronous write of a string to the net channel
 */

int nntp_write(b)
	char           *b;
{
	static char     obuf[512];
	int             sts;


        if (!net_chan) {
		if (debugging) {
			strcpy(obuf, b);
			strcat(obuf, "\r\n");
			printf("nntp_write_call (X chan closed): %s", obuf);
		}
		return(0);
	}
	strcpy(obuf, b);
	strcat(obuf, "\r\n");
	if (debugging)
		printf("nntp_write: %s", obuf);
	sts = sys$qiow(0, net_chan, IO$_WRITEVBLK, &write_iosb, 0, 0,
		       obuf, strlen(obuf), 0, (net_proto == CMUTCP), 0, 0);
	if (!(sts & 1) || !(write_iosb.iostatus & 1)) {
		printf("nntp_write: QIO fail: sts=%X iosts = %X\n",sts,write_iosb.iostatus);
		close_net();
		return (0);
	}
	return (1);
}

/*
 * nntp_read
 * 
 * Timed read of the net channel for a full line
 */

jmp_buf nntp_env;

void cancel_nntp_read(sig)
	int sig;
{
	close_net();
	printf("cancel_nntp_read: ReadTimeout - Lost connection\n");
	longjmp(nntp_env, 1);
}

static int nntp_read(b,b_m,b_len,timer)
  char *b;
  int b_m;    /* sizeof(b) */
  int *b_len; /* returns strlen(b) */
  int timer;
{
  struct iosb {
	unsigned short iostatus;
	unsigned short iosize;
	int netinfo;
	} r_iosb;
  int sts;
  int b_l; /* strlen(b) */

  if (b_len) *b_len = 0;
  *b = '\0'; b_l = 0;

#ifdef UCXSOCKETS
  if ((r_iosb.iosize = recv(socket_1,b,b_m-1,0)) <= 0) {
    printf("nntp_read: recv error - %s\n",strerror(errno));
    close_net();
    return(0);
    }
  else {
    alarm(0);
    sts = 1; r_iosb.iostatus = 1;
    }
#else
  if (setjmp(nntp_env)) return(0);
  signal(SIGALRM,cancel_nntp_read);
  alarm(timer);
  sts = sys$qiow(0,net_chan,
#if defined(DECNETSTREAM)
                 IO$_READVBLK | IO$M_MULTIPLE,
#elif defined(EXOS)
                 EX__READ,
#else
                 IO$_READVBLK,
#endif
                 &r_iosb, 0,0, b,b_m-1, 0,0,0,0);
  alarm(0);
  if (!(sts & 1) || !(r_iosb.iostatus & 1)) {
    printf("nntp_read: %s\n", getsyserrmsg(sts, r_iosb.iostatus) );
    close_net();
    return(0);
    }
  if (!r_iosb.iosize) {
    printf("nntp_read: null response, connection dropped\n");
    close_net();
    return(0);
    }
#endif
  b_l = r_iosb.iosize; b[b_l] = '\0';

  { char *from, *cp;
    int from_l, ll;

 /* replace all <CR><LF> pairs with <LF> */
    for (from=b, from_l=b_l, b_l=0;  from_l>0; ) {
      cp = memchr(from,'\n',from_l);  ll = !cp ? from_l : cp-from+1;
      if (cp && ll>1 && *(cp-1)=='\r') { *(cp-1) = '\n'; ll--; }
      if (b+b_l < from) memmove(b+b_l,from,ll);
      b_l += ll;
      if (!cp) { from_l = 0; from = NULL; }
      else { from_l -= (cp-from)+1; from = cp+1; }
      }
 /* cheating a bit: if the last char in the buffer is <CR> discard it
    in the hope that <LF> will follow at the start of the next read   */
    if (b_l>0 && b[b_l-1]=='\r') b_l--;

    b[b_l] = '\0';
    if (b_len) *b_len = b_l;
    return(1);
  }
}

/*
 * nntp_read_line
 *
 * Read one line (to '\n') from the net channel
 */

static char lbuf[2048] = {""};  /* works with any size, bigger for efficiency */
static int  lbuf_l = 0;     /* actual length of string in lbuf */
static int  lbuf_ind = 0;   /* index of the next character in lbuf to be read */

static int nntp_read_line(b,b_m,b_len,timer)
  char *b;
  int b_m;    /* sizeof(b) */
  int *b_len; /* returns strlen(b) */
  int timer;
{
  char *cp;
  int b_l,ll;

  *b = '\0';
  for (b_l = 0;  b_l < b_m-1; ) {
    if (lbuf_l-lbuf_ind <= 0) {
      lbuf_ind = 0;
      if (!nntp_read(lbuf,sizeof(lbuf), &lbuf_l, timer)) {
        if (b_len) *b_len = b_l;
        return(0);
        }
      }
    cp = memchr(&lbuf[lbuf_ind], '\n',
                ll = min(lbuf_l-lbuf_ind, b_m-b_l-1) );
    if (cp) ll = cp - &lbuf[lbuf_ind] + 1;   /* take only up to and incl '\n' */
    memcpy(&b[b_l],&lbuf[lbuf_ind],ll); b_l += ll; b[b_l] = '\0';
    lbuf_ind += ll;
    if (cp) {                            /* return if we have a complete line */
      if (b_len) *b_len = b_l;
      return(1);
      }
    }
  if (b_len) *b_len = b_l;
  return(1);/* return full buffer even if it does not contain a complete line */
}

#ifdef UCX
static unsigned int get_local_adrs(void)
{
	/*
	 * debugging code - use 130.56.5.21 (sao.aarnet.edu.au) as the
	 * localhost value static unsigned int localhost = (21 << 24) +  (5
	 * << 16) + (56 << 8) +  130;
	 */

	static unsigned int localhost = 0;

	if (!localhost) {
		const char      *gotenv;
		short           nam_chan, retlen;
		int             comm = INETACP$C_TRANS * 256 + INETACP_FUNC$C_GETHOSTBYNAME;
		struct dsc$descriptor command = {4, DSC$K_CLASS_S, DSC$K_DTYPE_T, (char *)&comm};
		struct dsc$descriptor host_ad = {4, DSC$K_CLASS_S, DSC$K_DTYPE_T, (char *)&localhost};
		struct iosb_block nam_iosb;

		if (!(gotenv = news_getenv("UCX$INET_HOST",1)))
			gotenv = "localhost";
		if (sys$assign(c$dsc("BG0:"), &nam_chan, 0, 0) & 1) {
			if (!((sys$qiow(0, nam_chan, IO$_ACPCONTROL, &nam_iosb, 0, 0, &command, c$dsc(gotenv), &retlen, &host_ad, 0, 0) & 1)
			      && (nam_iosb.iostatus & 1)))
				localhost = (1 << 24) + 127;
			sys$dassgn(nam_chan);
		}
	}
	return (localhost);
}

static unsigned int 
get_host_adrs(host_name)
	char           *host_name;
{
	unsigned int    hostadrs = 0;
	short           nam_chan, retlen;
	int             comm = INETACP$C_TRANS * 256 + INETACP_FUNC$C_GETHOSTBYNAME;
	struct dsc$descriptor command = {4, DSC$K_CLASS_S, DSC$K_DTYPE_T, (char *)&comm};
	struct dsc$descriptor host_ad = {4, DSC$K_CLASS_S, DSC$K_DTYPE_T, (char *)&hostadrs};
	struct iosb_block nam_iosb;

	if (sys$assign(c$dsc("BG0:"), &nam_chan, 0, 0) & 1) {
		if (!((sys$qiow(0, nam_chan, IO$_ACPCONTROL, &nam_iosb, 0, 0, &command, c$dsc(host_name), &retlen, &host_ad, 0, 0) & 1)
		      && (nam_iosb.iostatus & 1)))
			hostadrs = 0;
		sys$dassgn(nam_chan);
	} else
		printf("Couldn't open BG0:\n");
	return (hostadrs);
}
#endif

/*
 * wait_net_response
 * 
 * Wait for a response from remote system - cmd indicates that all text should
 * be skipped until NNTP command response is obtained
 */

int wait_net_response(secs, cmd)
	int             secs, cmd;
{
	if (nntp_read_line(ibuf,sizeof(ibuf),&ibuf_l,secs)) {
		if (debugging)
			printf("nntp_read: %s", ibuf);
		if (!cmd)
			return (1);
		if (sscanf(ibuf, "%d", &cmd_code) == 1) {
			if ((cmd_code == 400) || (cmd_code == 205)) {
				close_net();
				if (debugging)
					printf("nntp_read: connection shutdown response\n");
				return (0);
			} else
				return (cmd_code);
		}
	} else {
		if (debugging)
			printf("nntp_read: connection lost\n");
		return (0);
	}
	return (0);
}

/*
 * open_net
 * 
 * Open network link
 */

int open_net(node, proto)
	const char      *node;
	int             proto;
{
	char            netobj[128];

	strcpy(netobj, node);
	if (proto == CMUTCP) {
		if (!(sys$assign(c$dsc("IP:"), &net_chan, 0, 0) & 1))
			return (0);
		if ((sys$qiow(0, net_chan, IO$_CREATE, &iosb, 0, 0, netobj, 119, 0, 1, 0, 300) & 1)
		    && (iosb.iostatus & 1))
			return (1);
		sys$dassgn(net_chan);
		return (0);
	}
#ifdef UCX
	else if (proto == UCXTCP) {
		if (sys$assign(c$dsc("BG:"), &net_chan, 0, 0) & 1) {
			sck_parm[0] = INET$C_TCP;
			sck_parm[1] = INET_PROTYP$C_STREAM;
			local_host.inet_family = INET$C_AF_INET;
			local_host.inet_port = 0;	/* dynamic port assignment */
			local_host.inet_adrs = get_local_adrs();
			lhst_adrs.lgth = sizeof local_host;
			lhst_adrs.hst = &local_host;
			if ((sys$qiow(0, net_chan, IO$_SETMODE, &read_iosb, 0, 0, &sck_parm, 0, &lhst_adrs, 0, 0, 0) & 1)
			    && (read_iosb.iostatus & 1)) {
				remote_host.inet_family = INET$C_AF_INET;
				remote_host.inet_port = htons(NNTP_PORT);
				if (!(remote_host.inet_adrs = get_host_adrs(netobj))) {
					char           *c, *cd, resnum[4];
					unsigned int    indx = 0, *i;

					c = netobj;
					while (c && (indx <= 3)) {
						if ( (cd = strchr(c, '.')) )
							*cd = '\0';
						resnum[indx++] = atoi(c);
						if (cd) {
							*cd++ = '.';
							c = cd;
						} else
							c = 0;
					}
					if (c || indx != 4) {
						sys$qiow(0, net_chan, IO$_DEACCESS | IO$M_SHUTDOWN, read_iosb, 0, 0, 0, 0, 0, INET$C_DSC_ALL, 0, 0);
						sys$dassgn(net_chan);
						return (0);
					}
					i = (unsigned int *) &resnum[0];
					remote_host.inet_adrs = *i;
				}
				rhst_adrs.lgth = sizeof remote_host;
				rhst_adrs.hst = &remote_host;

				/*
				 * printf("Calling
				 * %08x\n",remote_host.inet_adrs);
				 */

				if ((sys$qiow(0, net_chan, IO$_ACCESS, &read_iosb, 0, 0, 0, 0, &rhst_adrs, 0, 0, 0) & 1)
				    && (read_iosb.iostatus & 1))
					return (1);
				printf("iosb.iostatus=%08x\n", read_iosb.iostatus);
				printf("Failed open_net\n");
				sys$qiow(0, net_chan, IO$_DEACCESS | IO$M_SHUTDOWN, read_iosb, 0, 0, 0, 0, 0, INET$C_DSC_ALL, 0, 0);
			}
			sys$dassgn(net_chan);
		}
		return (0);
	}
#endif
#if defined(TWG) || defined(MULTINET) || defined(TCPWARE)
	else if ((proto == WINTCP) || (proto == MULTINETTCP) || (proto == TCPWARETCP)) {
		if (((proto == WINTCP) && (sys$assign(c$dsc("INET:"), &net_chan, 0, 0) & 1))
		    || ((proto == MULTINETTCP) && (sys$assign(c$dsc("INET0:"), &net_chan, 0, 0) & 1))
		    || ((proto == TCPWARETCP) && (sys$assign(c$dsc("INET0:"), &net_chan, 0, 0) & 1))) {
			if ((sys$qiow(0, net_chan, IO$_SOCKET, &iosb, 0, 0, AF_INET, SOCK_STREAM, 0, 0, 0, 0) & 1)
			    && (iosb.iostatus & 1)) {
				if (!(dest_host = gethostbyname(netobj))) {
					int             h[4], i;

					if (sscanf(netobj, "%d.%d.%d.%d", &h[0], &h[1], &h[2], &h[3]) != 4) {
						if (debugging)
							printf("open_net: gethostbyname returned null\n");
						sys$dassgn(net_chan);
						return (0);
					}
					for (i = 0; i < 4; ++i)
						if (h[i] < 0 || h[i] > 255) {
							if (debugging)
								printf("Incorrect format of internet address\n");
							return (0);
						}
					data_socket.sin_addr.s_addr = (h[3] << 24) + (h[2] << 16) + (h[1] << 8) + h[0];
				} else
					data_socket.sin_addr.s_addr = *((u_long *) (dest_host->h_addr));
				data_socket.sin_family = AF_INET;
				data_socket.sin_port = htons(NNTP_PORT);
				if ((sys$qiow(0, net_chan, IO$_CONNECT, &iosb, 0, 0, &data_socket, sizeof(data_socket), 0, 0, 0, 0) & 1)
				    && (iosb.iostatus & 1))
					return (1);
			}
		}
		if (debugging)
			printf("open_net: io$_connect or io$_Socket error\n");
		if (debugging)
			printf("open_net: iosb.iostatus %x \n", iosb.iostatus);
		sys$dassgn(net_chan);
		return (0);
	}
#endif
	else {
		sprintf(netobj, "%s::\"%s\"", node, taskstr);
		return (sys$assign(c$dsc(netobj), &net_chan, 0, 0) & 1);
	}
	return (0); /* should not happen */
}


/*
 * close_net
 * 
 * Close network link
 */

void close_net()
{
	if (net_proto) {
		sys$cancel(net_chan);
		sys$qiow(0, net_chan, IO$_DELETE, 0, 0, 0, 0, 0, 0, 0, 0, 0);
	}
	sys$dassgn(net_chan);
	net_chan = 0;
}


/*
 * toggle_link
 * 
 * Write a non-command to the net chan and wait for the command-not-recognised
 * response from the server.
 */

int toggle_link()
{
	if (!nntp_write("STAT") || (!(wait_net_response(RESP_TIMER, 1)))) {
		if (*net_open_chan) {
			close_net();
			printf("Lost connection to NEWS SERVER ([%c] %s)", (net_proto ? 'T' : 'D'), net_open_chan);
			*net_open_chan = 0;
		}
		return (0);
	}
	return (1);
}


/*
 * s_to_upper
 * 
 */

char           *
s_to_upper(s)
	char           *s;
{
	char           *save = s;

	while (*s) {
		*s = toupper(*s);
		s++;
	}
	return (save);
}


/*
 * open_rem_chan
 * 
 * Open channel to remote NNTP server on host name node, using protocol
 * proto_num (6 == TCPWARETCP, 3 == MULTINETTCP, 2 == WINTCP,1 == CMUTCP, 0 =
 * DECNET)
 */

char            restart_node[256];
int             restart_proto_num, restarts = 0;

int restart_nntp(void)
{
	if (++restarts > MAX_RESTART_ATTEMPT)
		return (0);
	printf("(Lost NNTP connection - restart attempted)\n");
	return (open_rem_chan(restart_node, restart_proto_num));
}

int open_rem_chan(node, proto_num)
	const char      *node;
	int             proto_num;
{
	int             reply;

	strcpy(restart_node, node); lower_case(restart_node);
	restart_proto_num = proto_num;
	if (*net_open_chan) {
		if ((proto_num != net_proto) || (strcmp(restart_node, net_open_chan))) {
			close_net();
			*net_open_chan = 0;
		}
		/*
		 * else toggle_link();
		 */
	}
	if (!*net_open_chan) {
		strcpy(net_open_chan, restart_node);
		if (!open_net(net_open_chan, proto_num)) {
			*net_open_chan = 0;
			if (debugging)
				printf("open_chan: Could not open network channel\n");
			return (0);
		} else
			net_proto = proto_num;
		if (!(reply = wait_net_response(CLIENT_TIMER, 1))) {
			close_net();
			*net_open_chan = 0;
			if (debugging)
				printf("open_chan: No reply received in %d secs\n", CLIENT_TIMER);
			return (0);
		}
		if ((reply != 200) && (reply != 201)) {
			printf("Connection refused to NEWS SERVER ([%c] %s)", (net_proto ? 'T' : 'D'), net_open_chan);
			return (0);
		}
	}
	return (1);
}


/*
 * ITM routines
 * 
 */

int open_itm_file(void)
{
	itmfab = cc$rms_fab;
	itmfab.fab$b_fac = FAB$M_GET;
	itmfab.fab$l_fna = (char *) ITM_FILENAME;
	itmfab.fab$b_fns = strlen(itmfab.fab$l_fna);
	itmfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

	itmrab = cc$rms_rab;
	itmrab.rab$l_fab = &itmfab;
	itmrab.rab$l_rbf = itmrab.rab$l_ubf = (char *) &newsitm;
	itmrab.rab$w_rsz = itmrab.rab$w_usz = sizeof newsitm;

	if (!(sys$open(&itmfab) & 1))
		return (0);
	if (!(sys$connect(&itmrab) & 1)) {
		sys$close(&itmfab);
		return (0);
	}
	return (1);
}

void close_itm_file(void)
{
	sys$close(&itmfab);
}

int itm_check(id)
	char           *id;
{
	itmrab.rab$l_kbf = id;
	itmrab.rab$b_ksz = IDLEN + 4;
	itmrab.rab$b_krf = 1;
	itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
	itmrab.rab$b_rac = RAB$C_KEY;

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


/*
 * HISTORY routines
 * 
 */

int             hist_file_open = 0, hist_off = 0;

#ifdef __DECC
#pragma member_alignment save
#pragma nomember_alignment
#endif

struct FAB      histfab;
struct RAB      histrab;

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

#ifdef __DECC
#pragma member_alignment restore
#endif

int open_hist_file()
{
	FILE           *fpr;

	if ( (fpr = fopen(HIST_OFF, "r")) ) {
		fclose(fpr);
		hist_off = 1;
		return (0);
	}
	histfab = cc$rms_fab;
	histfab.fab$b_fac = FAB$M_GET;
	histfab.fab$l_fna = (char *) HIST_FILE;
	histfab.fab$b_fns = strlen(histfab.fab$l_fna);
	histfab.fab$b_shr = FAB$M_SHRDEL | FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;

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

	if (!(sys$open(&histfab) & 1))
		return (0);
	if (!(sys$connect(&histrab) & 1)) {
		sys$close(&histfab);
		return (0);
	}
	return (hist_file_open = 1);
}

void close_hist_file()
{
	if (hist_file_open)
		sys$close(&histfab);
}

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

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


/*
 * open_server_files
 * 
 * Open the item and newsgroup files for sharing
 */

int open_server_files(void)
{
	if (init_lock()) {
		printf("NEWS database currently locked - transfer not performed\n");
		exit(1);
	}
	if (!open_itm_file())
		return (0);
	open_hist_file();
	return (1);
}

void close_server_files(void)
{
	close_itm_file();
	close_hist_file();
}

int id_check(id)
	char           *id;
{
	char            l_id[IDLEN + 4];
	int             i;

	for (i = 0; i < (IDLEN + 4); ++i)
		l_id[i] = '\0';
	strncpy(l_id, id, IDLEN);
	if (itm_check(l_id) || hist_check(l_id))
		return (1);
	return (0);
}


void collect_newids(stmfile, idfile)
	char           *stmfile, *idfile;
{
	char            cmd[2048], *cp;
	time_t		start_time;
	time_t          last_time = 0;
	struct tm      *stm;
	FILE           *fpr, *fpw;

	time(&start_time);
	if ( (fpr = fopen(stmfile, "r")) ) {
		if (fscanf(fpr, "%lX", &last_time) != 1)
			last_time = 0;
		fclose(fpr);
	}
      /* since last_time is unsigned, we have to do it cautiously: */
	last_time -= min(last_time, hours * 60 * 60);
	last_time -= min(last_time, days * 60 * 60 * 24);
	if (last_time <= 0) last_time = 1;

	stm = localtime(&last_time);

	if (!(fpw = fopen(idfile, "a"))) {
		printf("Collect_ids: Cannot append to id file %s\n", idfile);
		return;
	}
	printf("Collect_ids: Connect to NNTP server %s at %s", node, ctime(&start_time));
	if (!open_rem_chan(node, proto_num)) {
		printf("  Connection refused\n");
		fclose(fpw);
		return;
	}
	printf("Request item-id list: since %s", ctime(&last_time));
	sprintf(cmd, "NEWNEWS %.512s %02d%02d%02d %02d%02d%02d", group,
		stm->tm_year % 100, stm->tm_mon + 1, stm->tm_mday,
		stm->tm_hour, stm->tm_min, stm->tm_sec);
	printf("NNTP> %s\n", cmd);
	if (!nntp_write(cmd) || (wait_net_response(CLIENT_TIMER, 1) != 230)) {
		fclose(fpw);
		close_net();
		printf("  0 item-ids returned - Comm error\n");
		return;
	}
	while (wait_net_response(RESP_TIMER, 0) == 1) {
		if (!strcmp(ibuf, ".\n"))
			break;
		if (*ibuf == '.')
			memmove(ibuf, ibuf + 1, strlen(ibuf));
		if (strlen(ibuf) > 1) {
			++offered;
			fputs(ibuf, fpw); _ck_put(fpw);
		}
	}
	printf("  %d item-ids returned", offered);
	fclose(fpw);
	if (!strcmp(ibuf, ".\n")) {
		printf("\n");
		if ( (fpr = fopen(stmfile, "w")) ) {
			fprintf(fpr, "%lX\n", start_time);
			fclose(fpr);
			strcat(stmfile, ";-1");
			while (!delete(stmfile));
			cp = strrchr(stmfile, ';');
			*++cp = '\0';
			strcpy(cmd, stmfile);
			strcat(cmd, "1");
			rename(stmfile, cmd);
		}
	} else
		printf(" - Transmission aborted - comm errors\n");
}

struct tree {
	char           *str;
	struct tree    *left, *right;
}              *idtree = 0;

int tsearch(t, s)
	struct tree   **t;
	char           *s;
{
	int             cmp;

	if (!*t) {
		*t = (struct tree *) news_malloc(sizeof **t);
		strcpy((*t)->str = (char *) news_malloc(strlen(s) + 1), s);
		(*t)->left = (*t)->right = 0;
		return (0);
	}
	if (!(cmp = strcmp(s, (*t)->str)))
		return (1);
	if (cmp < 0)
		return (tsearch(&((*t)->left), s));
	return (tsearch(&((*t)->right), s));
}

void cleart(t)
	struct tree    *t;
{
	if (t->right)
		cleart(t->right);
	if (t->left)
		cleart(t->left);
	news_free(t->str);
	news_free(t);
}


int process_idfile(idfilename, batchfile)
	char           *idfilename, *batchfile;
{
	FILE           *fpr, *fpw = 0;
	char            inl[512], nfn[256], cmd[256], xfn[256], npart[20], *cp;
	int             ids = 0, resp = 0, art_count = 0, fsize = 0;
        time_t		start_time;
	static int      part = 0;

	if (!(fpr = fopen(idfilename, "r"))
	    || !(fpw = fopen(idfilename, "w")))
		return (0);
	while (fgets(inl, sizeof(inl), fpr)) {
		if ( (cp = strchr(inl, '\n')) )
			*cp = '\0';
		if (!strlen(inl) || (cp = strchr(inl, ' ')) || id_check(inl))
			continue;
		if (!tsearch(&idtree, inl)) {
			++ids;
			fputs(inl, fpw); _ck_put(fpw);
			fputs("\n", fpw); _ck_put(fpw);
		}
	}
	if (idtree)
		cleart(idtree);
	idtree = 0;
	fclose(fpr);
	fclose(fpw);
	if (!ids)
		while (!delete(idfilename));
	else {
		strcpy(nfn, idfilename);
		strcat(nfn, ";-1");
		while (!delete(nfn));
		strcat(idfilename, ";");
		strcpy(nfn, idfilename);
		strcat(nfn, "1");
		rename(idfilename, nfn);
		if ( (cp = strchr(idfilename,';')) )
			*cp = '\0';
		fpw = 0;
	}

	if (!ids || !(fpr = fopen(idfilename, "r")))
		return (0);
	time(&start_time);
	printf("Process_ids: Connect to NNTP server %s at %s",
          node, ctime(&start_time));
	if (!open_rem_chan(node, proto_num)) {
		printf("  Connection refused\n");
		fclose(fpr);
		return (0);
	}
	strcpy(nfn, batchfile);
	cp = strrchr(nfn, '.');
	strcpy(cp, ".OPEN");
	while (fgets(inl, sizeof(inl), fpr)) {
		if ( (cp = strchr(inl,'\n')) )
			*cp = '\0';
		sprintf(cmd, "ARTICLE %s", inl);
		if (!nntp_write(cmd)
		  || ((resp = wait_net_response(CLIENT_TIMER, 1)) != 220)) {
			if (resp == 430)
				continue;
			if (!restart_nntp()) {
				if (fpw) {
					fclose(fpw);
					rename(nfn, batchfile);
				}
				fclose(fpr);
				printf("  %d items received  - Transmission aborted - comm errors\n", art_count);
				return (art_count);
			}
			continue;
		}
		if (!fpw) {
			strcpy(xfn, batchfile);	/* [BT] */
			cp = strrchr(xfn, '.');	/* [BT] */
			sprintf(npart, "_%03d.batch", ++part);	/* [BT] */
			strcpy(cp, npart);	/* [BT] */
			fpw = fopen(nfn, "w");
		}
		fprintf(fpw, "#! rnews 1\n");
		while (wait_net_response(RESP_TIMER, 0) == 1) {
			if (!strcmp(ibuf, ".\n"))
				break;
			if (*ibuf == '.') {
				--fsize;
				fputs(ibuf + 1, fpw); _ck_put(fpw);
			} else {
				fputs(ibuf, fpw); _ck_put(fpw);
			}
			fsize += strlen(ibuf);
		}
		if (strcmp(ibuf, ".\n")) {
			fclose(fpw);
			while (!delete(nfn));
			fpw = 0;
			if (!restart_nntp()) {
				fclose(fpr);
				printf("  %d items received  - Transmission aborted - comm errors\n", art_count);
				return (art_count);
			}
			continue;
		}
		++art_count;
		if (fsize > MAX_BATCH_SIZE) {
			fclose(fpw);
			rename(nfn, xfn);
			fpw = 0;
			fsize = 0;
		}
	}
	close_net();
	printf("  %d items received\n", art_count);
	if (fpw) {
		fclose(fpw);
		rename(nfn, xfn);
	}
	fclose(fpr);
	while (!delete(idfilename));
	return (art_count);
}

void usage(void)
{
  printf("Usage: NNTP_XFER -n node -p proto [-l log] [-d days] [-h hours] [-t task] [-g group] [-s] [-o]\n");
  printf("  -or-\n");
  printf("Usage: NNTP_XFER/NODE=node/PROTOCOL=proto\n");
  printf("           [/LOG=log/DAY=days/HOUR=hours/TASK=task/GROUPS=groups/STREAM/DEBUG]\n\n");

  printf("  -n node - name of node to connect to\n");
  printf("  -p proto - protocol to use: \"tcp\" for tcp/ip, \"decnet\" for DECnet\n");
  printf("  -l log (optional) - name of log file to record activity of the run\n");
  printf("  -d days (optional) - number of previous days traffic to request - default is 0\n");
  printf("  -h hours (optional) - number of previous hours traffic to request - default is 1\n");
  printf("  -t decnet-task (optional) - the default task connect string is TASK=NNTP\n");
  printf("     other strings may be specified instead\n");
  printf("  -g group (optional) - the specifid newsgroup to retrieve - default is all groups\n");
  printf("  -s (optional) - used with DECnet protocol to use stream Input Qio calls\n");
  printf("     (used to connect to Ultrix NNTP over DECnet)\n");
  printf("     default is ok for remote VMS systems\n");
  printf("  -o (optional) - used to send all I/O to the screen (used for debugging)\n");
  exit(1);
}

/*
 * main
 */

int main(argc, argv)
	int             argc;
	char           *argv[];
{
	char            proto[32], batch_stamp_file[256], out_all[256],
			idfile[256], qual[1024], *np;
	int             usg_error = 0, i;
	unsigned short  qual_len;
	$DESCRIPTOR(qual_dsc, qual);

	/* startup code - initialize environment */
	init_mem();
	sysprv();
	if (!open_server_files()) {
		printf("FileError: Cannot open NEWS datafiles\n");
		exit(1);
	}
	if ( (np = (char *) news_getenv("NNTP_SCRATCH",0)) )
		strcpy(scratch_area, "NNTP_SCRATCH");
	else if (!news_getenv("SYS$SCRATCH",0))
		strcpy(scratch_area, "NEWS_MANAGER");
	else
		strcpy(scratch_area, "SYS$SCRATCH");
	*taskstr = '\0';
	*node = '\0';
	strcpy(group, "*");

	/* interpret program arguments */
	if (argc < 2) usage();
	if (*argv[1] == '/') {
		char            mcommand[1024];
		register int	argindex;

		strcpy(mcommand, "INVOKENNTP");
		for (argindex = 1; argindex < argc; argindex++) {
			strcat(mcommand, " ");
			strcat(mcommand, argv[argindex]);
		}
		if (!(cli$dcl_parse(c$dsc(mcommand), (int *)&nntpxfercmd, 0, 0, 0) & 1))
			exit(1);

		decnetstream = (cli$present(c$dsc("STREAM")) & 1);
		debugging = (cli$present(c$dsc("DEBUG")) & 1);
		if (cli$get_value(c$dsc("NODE"), &qual_dsc, &qual_len) & 1) {
			qual[qual_len] = '\0';
			strcpy(node, qual);
		}
		if (cli$get_value(c$dsc("PROTOCOL"), &qual_dsc, &qual_len) & 1) {
			qual[qual_len] = '\0';
			strcpy(proto, qual);
		}
		if (cli$get_value(c$dsc("FILE"), &qual_dsc, &qual_len) & 1) {
			FILE           *fpg;
			*group = '\0';
			qual[qual_len] = '\0';
			if ( (fpg = fopen(qual, "r")) ) {
				while (fgets(qual, 512, fpg)) {
					if (*qual == '#')
						continue;
					if ( (np = strchr(qual,'\n')) )
						*np = '\0';
					if (*group)
						strcat(group,",");
					lower_case(qual);
					strcat(group, qual);
				}
				fclose(fpg);
			}
		}
		if (cli$present(c$dsc("GROUPS")) & 1) {
			*group = '\0';
			while (cli$get_value(c$dsc("GROUPS"), &qual_dsc, &qual_len) & 1) {
				qual[qual_len] = '\0';
				if (qual_len) {
					if (*qual == '@') {
						FILE           *fpg;
						if ( (fpg = fopen(qual + 1, "r")) ) {
							while (fgets(qual, 512, fpg)) {
								if (*qual == '#')
									continue;
								if ( (np = strchr(qual, '\n')) )
									*np = '\0';
								if (*group)
									strcat(group, ",");
								lower_case(qual);
								strcat(group, qual);
							}
							fclose(fpg);
						}
					} else {
						if (*group)
							strcat(group, ",");
						lower_case(qual);	/* lower case group name
									 * - RBR */
						strcat(group, qual);
					}
				}
			}
		}
		if (cli$get_value(c$dsc("LOGFILE"), &qual_dsc, &qual_len) & 1) {
			qual[qual_len] = '\0';
			strcpy(logfile, qual);
			logging = 1;
		}
		if (cli$get_value(c$dsc("DAYOFFSET"), &qual_dsc, &qual_len) & 1) {
			qual[qual_len] = '\0';
			if (sscanf(qual, "%d", &days) != 1)
				days = 0;
		}
		if (cli$get_value(c$dsc("HOUROFFSET"), &qual_dsc, &qual_len) & 1) {
			qual[qual_len] = '\0';
			if (sscanf(qual, "%d", &hours) != 1)
				hours = 1;
		}
		if (cli$get_value(c$dsc("TASK"), &qual_dsc, &qual_len) & 1) {
			qual[qual_len] = '\0';
			if (qual_len) {
				if (!strchr(qual, '='))
					strcpy(taskstr, "TASK=");
				else
					*taskstr = '\0';
				strcat(taskstr, qual);
				np = taskstr;
				--np;
				while (*++np)
					*np = toupper(*np);
			}
		}
	} else {
		if (*argv[1] != '-') {
			strcpy(node, argv[1]);
			if (argc > 2)
				strcpy(proto, argv[2]);
			if (argc > 3) {
				strcpy(logfile, argv[3]);
				logging = 1;
			}
			if (argc == 5)
				debugging = 1;
		} else {
			i = 1;
			while (i < argc) {
				np = argv[i];
				if (*np == '-') {
					++np;
					if (tolower(*np) == 'n') {
						if (++i < argc)
							strcpy(node, argv[i]);
						else
							++usg_error;
					}
					if (tolower(*np) == 'p') {
						if (++i < argc)
							strcpy(proto, argv[i]);
						else
							++usg_error;
					} else if (tolower(*np) == 'g') {
						if (++i < argc) {
							strcpy(group, argv[i]);
							lower_case(group);
						} else
							++usg_error;
					} else if (tolower(*np) == 'f') {
						if (++i < argc) {
							FILE           *fpg;
							if ( (fpg = fopen(argv[i], "r")) ) {
								*group = '\0';
								while (fgets(qual, 512, fpg)) {
									if (*qual == '#')
										continue;
									if ( (np = strchr(qual, '\n')) )
										*np = '\0';
									if (*group)
										strcat(group, ",");
									lower_case(qual);
									strcat(group, qual);
								}
								fclose(fpg);
							}
						} else
							++usg_error;
					} else if (tolower(*np) == 'l') {
						if (++i < argc) {
							strcpy(logfile, argv[i]);
							logging = 1;
						} else
							++usg_error;
					} else if (tolower(*np) == 'd') {
						if (++i < argc) {
							if (sscanf(argv[i], "%d", &days) != 1)
								++usg_error;
						} else
							++usg_error;
					} else if ((tolower(*np) == 'h') && (tolower(*np + 1) != 'e')) {
						if (++i < argc) {
							if (sscanf(argv[i], "%d", &hours) != 1)
								++usg_error;
						} else
							++usg_error;
					} else if (tolower(*np) == 't') {
						if (++i < argc) {
							strcpy(taskstr, argv[i]);
							np = taskstr;
							--np;
							while (*++np)
								*np = toupper(*np);
						} else
							++usg_error;
					} else if (tolower(*np) == 'h') { usage();
					} else {
						while (*np) {
							if (tolower(*np) == 'o')
								debugging = 1;
							if (tolower(*np) == 's')
								decnetstream = 1;
							++np;
						}
					}
					if (usg_error) usage();
				}
				++i;
			}
		}
		if (!*node) usage();
	}

	if (!*group)
		strcpy(group, "*");
	/*
	 * strncpy(fgroup,group,80); fgroup[80] = '\0'; while (np =
	 * strchr(fgroup,'.')) *np = '-'; while (np = strchr(fgroup,',')) *np
	 * = '_'; while (np = strchr(fgroup,'*')) *np = 'A'; while (np =
	 * strchr(fgroup,'!')) *np = 'N';
	 */
	strcpy(fgroup, "selective");
	while ( (np = strchr(group, '^')) )
		*np = '!';	/* use ^ as the 'no' symbol RBR */

	strcpy(fnode, node);
	while ( (np = strchr(fnode, '.')) )
		*np = '-';

#ifdef CMU
	if (tolower(*proto) == 't')
		proto_num = CMUTCP;
	if (tolower(*proto) == 'c')
		proto_num = CMUTCP;
#endif
#ifdef UCX
	if (tolower(*proto) == 't')
		proto_num = UCXTCP;
	if (tolower(*proto) == 'u')
		proto_num = UCXTCP;
#endif
#ifdef TWG
	if (tolower(*proto) == 't')
		proto_num = WINTCP;
	if (tolower(*proto) == 'w')
		proto_num = WINTCP;
#endif
#ifdef MULTINET
	if (tolower(*proto) == 't')
		proto_num = MULTINETTCP;
	if (tolower(*proto) == 'm')
		proto_num = MULTINETTCP;
	if (tolower(*proto) == 's')
		proto_num = MULTINETTCP;
#endif
#ifdef TCPWARE
	if (tolower(*proto) == 't')
		proto_num = TCPWARETCP;
	if (tolower(*proto) == 'p')
		proto_num = TCPWARETCP;
#endif

	if (proto_num != DECNET)
		decnetstream = 0;
	else if (!*taskstr) {
		char            logname[132], *gotenv, *cp;

		sprintf(logname, "NEWS_%s_TASK", node);
		cp = logname;
		--cp;
		while (*++cp)
			*cp = toupper(*cp);
		if ( (gotenv = (char *) news_getenv(logname,0)) )
			strcpy(taskstr, gotenv);
		else
			strcpy(taskstr, "TASK=NNTP");
		cp = taskstr;
		--cp;
		while (*++cp)
			*cp = toupper(*cp);
	}
	if (!strcmp(group, "*"))
		sprintf(idfile, "NEWS_MANAGER:NNTP_%s.IDS", fnode);
	else
		sprintf(idfile, "NEWS_MANAGER:NNTP_%s_%s.IDS", fnode, fgroup);
	if (!strcmp(group, "*"))
		sprintf(out_all, "NEWS_MANAGER:NNTP_%s.BATCH", fnode);
	else
		sprintf(out_all, "NEWS_MANAGER:NNTP_%s_%s.BATCH", fnode, fgroup);

	accepted += process_idfile(idfile, out_all);

	if (news_lock_alarm) {
		printf("NNTP_XFER - transfer aborted (news sync lock detected.\n");
		exit(1);
	}
	if (!strcmp(group, "*"))
		sprintf(batch_stamp_file, "NEWS_MANAGER:NNTP_%s.LASTCALL", fnode);
	else
		sprintf(batch_stamp_file, "NEWS_MANAGER:NNTP_%s_%s.LASTCALL", fnode, fgroup);
	if (!strcmp(group, "*"))
		sprintf(idfile, "NEWS_MANAGER:NNTP_%s.IDS", fnode);
	else
		sprintf(idfile, "NEWS_MANAGER:NNTP_%s_%s.IDS", fnode, fgroup);

	collect_newids(batch_stamp_file, idfile);

	if (news_lock_alarm) {
		printf("NNTP_XFER - transfer aborted (news sync lock detected.\n");
		exit(1);
	}
	if (!strcmp(group, "*"))
		sprintf(idfile, "NEWS_MANAGER:NNTP_%s.IDS", fnode);
	else
		sprintf(idfile, "NEWS_MANAGER:NNTP_%s_%s.IDS", fnode, fgroup);

	accepted += process_idfile(idfile, out_all);
	close_server_files();

	if (logging) {
		struct tm      *stm;
		time_t         cur_time;
		char           *p, datum[32];
		FILE           *fpl;

		time(&cur_time);
		p = ctime(&cur_time);
		p += 4;
		stm = localtime(&cur_time);
		sprintf(datum, "%d %.3s %d %02d:%02d:%02d ",
			stm->tm_mday, p, (stm->tm_year)+1900,
                        stm->tm_hour, stm->tm_min, stm->tm_sec);
		fpl = fopen(logfile, "a");
		fprintf(fpl, "%s nntpxfer: %s (%s) - %d items listed, %d transferred\n",
			datum, node, group, offered, accepted);
		fclose(fpl);
	}
}

/*
 * mem_fail - called by memory allocation routines in NewsRTL when allocation
 * request fails.  Free memory reserve to get a little scratch space, and
 * try to exit gracefully.
 */

void *mem_fail(bytesize)
  size_t bytesize;
{  

  if (mem_reserve) {
    mem_reserve = 0;
    if (debugging) printf("mem_fail: Out of memory - exiting\n");
    nntp_write("QUIT");
    close_net();
    }
  exit(LIB$_INSVIRMEM);
  return((void *) 0);  /* dead code to keep DECC happy */
}
