#ifndef lint
static char     *rcsid = "@(#)$Id: nntpclnt.c,v 1.6 1992/08/03 04:54:23 sob Exp sob $";
#endif

/*
 * This software is Copyright 1992 by Stan Barber. 
 *
 * Permission is hereby granted to copy, reproduce, redistribute or otherwise
 * use this software as long as: there is no monetary profit gained
 * specifically from the use or reproduction or this software, it is not
 * sold, rented, traded or otherwise marketed, and this copyright notice is
 * included prominently in any copy made. 
 *
 * The author make no claims as to the fitness or correctness of this software
 * for any use whatsoever, and it is provided as is. Any use of this software
 * is at the user's own risk. 
 *
 */
/*
 * NNTP client routines.
 */

/*
 * Include configuration parameters only if we're made in the nntp tree.
 */

#ifdef NNTPSRC
#include "config.h"
#endif /* NNTPSRC */
#ifdef MSDOS
#include <stdlib.h>
#include <io.h>
#endif
#include <stdio.h>
#ifdef PCTCP
#include <pctcp/types.h>
#include <pctcp/pctcp.h>
#include <pctcp/ipconfig.h>
#include <pctcp/sockets.h>
#include <pctcp/options.h>
#include <pctcp/error.h>
#ifdef SOCK_TCP_NNTP
#undef SOCK_TCP_NNTP            /* defined wrong in the development kit! */
#endif
#define SOCK_TCP_NNTP 119
#endif
#ifdef PCNFS
#include <tklib.h>
#endif
#include <sys/types.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef PCNFS
#include <in_addr.h>
#endif
#ifdef LWP                      /* Lan Workplace for DOS from Novell */
#include <sys/ioctl.h>
#include <sys/filio.h>
#include <sys/sockio.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <sys/uio.h>
#define net_read(a,b,c,d,e)        soread(a,b,c)
#define net_write(a,b,c,d)	   sowrite(a,b,c)
#endif
#if defined(USG) || defined(MSDOS)
#include <string.h>
#else
#include <strings.h>
#endif
#include "nntpclnt.h"
#ifdef NONETDB
# define        IPPORT_NNTP     ((unsigned short) 119)
#else
# include       <netdb.h>       /* All TLI implementations may not have this */
#endif

#if defined(USG) || defined(MSDOS)
#ifndef index
# define        index   strchr
#endif
#ifndef bcopy
# define        bcopy(a,b,c)   memcpy(b,a,c)
#endif
#ifndef bzero
# define        bzero(a,b)     memset(a,'\0',b)
#endif
#endif /* USG or MSDOS */

#ifdef DATAKIT
#include <dk.h>
#include <errno.h>
#include <memory.h>
#define min(A, B)       ((A) < (B)? (A): (B))
#endif /* DATAKIT */

#ifdef EXCELAN
#if __STDC__
int connect(int, struct sockaddr *);
unsigned short htons(unsigned short);
unsigned long rhost(char **);
int rresvport( int );
int socket( int, struct sockproto *, struct sockaddr_in *, int );
#endif
#endif

#ifdef SGI4DDN
#include <fcntl.h>
#include <dn/defs.h>
#endif
#ifdef DECNET
#include <netdnet/dn.h>
#include <netdnet/dnetdb.h>
#endif /* DECNET */

#ifdef __STDC__
unsigned long inet_addr(char *x);
int get_tcp_socket(char *machine);
#else
unsigned long inet_addr();
#endif

#include "nntp.h"


#if defined(PCTCP) || defined(LWP)
int     nd = -1;                /* network descriptor */
#else
FILE    *ser_rd_fp = NULL;
FILE    *ser_wr_fp = NULL;
#endif
/*
 * getserverbyfile      Get the name of a server from a named file.
 *                      Handle white space and comments.
 *                      Use NNTPSERVER environment variable if set.
 *
 *      Parameters:     "file" is the name of the file to read.
 *
 *      Returns:        Pointer to static data area containing the
 *                      first non-ws/comment line in the file.
 *                      NULL on error (or lack of entry in file).
 *
 *      Side effects:   None.
 */

char *
getserverbyfile(file)
char    *file;
{
	register FILE   *fp;
	register char   *cp;
	static char     buf[256];
	char            *index();
	char            *getenv();
	char            *strcpy();

	if (cp = getenv("NNTPSERVER")) {
		(void) strcpy(buf, cp);
		return (buf);
	}

	if (file == NULL)
		return (NULL);

	fp = fopen(file, "r");
	if (fp == NULL)
		return (NULL);

	while (fgets(buf, sizeof (buf), fp) != NULL) {
		if (*buf == '\n' || *buf == '#')
			continue;
		cp = index(buf, '\n');
		if (cp)
			*cp = '\0';
		(void) fclose(fp);
		return (buf);
	}

	(void) fclose(fp);
	return (NULL);                   /* No entry */
}


/*
 * server_init  Get a connection to the remote news server.
 *
 *      Parameters:     "machine" is the machine to connect to.
 *
 *      Returns:        -1 on error
 *                      server's initial response code on success.
 *
 *      Side effects:   Connects to server.
 *                      "ser_rd_fp" and "ser_wr_fp" are fp's
 *                      for reading and writing to server.
 */

server_init(machine)
char    *machine;
{
	int     sockt_rd, sockt_wr;
	char    line[256];
	char    *index();
#if defined(DECNET) || defined(SGI4DDN)
	char    *cp;

	cp = index(machine, ':');

	if (cp && cp[1] == ':') {
		*cp = '\0';
		sockt_rd = get_dnet_socket(machine);
	} else
		sockt_rd = get_tcp_socket(machine);
#else
# ifdef DATAKIT
	sockt_rd = get_dk_socket(machine);
# else
	sockt_rd = get_tcp_socket(machine);
# endif
#endif
	if (sockt_rd < 0)
		return (-1);

#if defined(PCTCP) || defined(LWP)
	sockt_wr = nd = sockt_rd;          /* save the network descriptor */
#else
	sockt_wr = dup(sockt_rd);
	/*
	 * Now we'll make file pointers (i.e., buffered I/O) out of
	 * the socket file descriptor.  Note that we can't just
	 * open a fp for reading and writing -- we have to open
	 * up two separate fp's, one for reading, one for writing.
	 */

	if ((ser_rd_fp = fdopen(sockt_rd, "r")) == NULL) {
		perror("server_init: fdopen #1");
		return (-1);
	}

	if ((ser_wr_fp = fdopen(sockt_wr, "w")) == NULL) {
		perror("server_init: fdopen #2");
		ser_rd_fp = NULL;               /* from above */
		return (-1);
	}

#endif
	/* Now get the server's signon message */

	(void) get_server(line, sizeof(line));
	return (atoi(line));
}

#ifdef DATAKIT
/*
 * get_dk_socket -- get a socket (actually a file descriptor) connected
 *                  to the netnews server machine.
 *
 *       Parameters:    "machine" is the dial string to reach the service.
 *
 *       Returns:       file descriptor for the service, or a "-1" in
 *                      case of failure.
 *
 *       Side effects:  connects to service.
 */
 
get_dk_socket(machine)
char *machine;
{
	static short block[3] = {DKR_BLOCK};
	char *maphost(), *dialstring, *cuserid();
	int dkfd;
 
	if ( (dialstring = maphost(machine, 'n', "nntp", "", getlogin())) == (char *)NULL )
		return(-1);

	if ( (dkfd = dkdial(dialstring)) >= 0 )
		ioctl(dkfd, DIOCRMODE, block);
	else
		dkfd = -1;

	return(dkfd);
}
 
#else /* !DATAKIT */

/*
 * get_tcp_socket -- get us a socket connected to the news server.
 *
 *      Parameters:     "machine" is the machine the server is running on.
 *
 *      Returns:        Socket connected to the news server if
 *                      all is ok, else -1 on error.
 *
 *      Side effects:   Connects to server.
 *
 *      Errors:         Printed via perror.
 */

int
get_tcp_socket(machine)
char    *machine;       /* remote host */
{
	int     s;
#ifdef PCTCP
	in_name fhost;
	struct  addr a;
	fhost = nm_res_name(machine,(char *) NULL,0);
	if (fhost == 0){
		pneterror(machine);
		return(-1);
	}
	/*
	 * setup address structure 
	 */
	 a.lsocket = 0;
	 a.fsocket = SOCK_TCP_NNTP;
	 a.fhost = fhost;
	 /* make connection */ 
	 s = net_connect(-1,STREAM,&a);
	 if (s == -1) {
		pneterror("net_connect");
		return(-1);
	}
#else
	struct  sockaddr_in sin;
#ifdef NONETDB
	bzero((char *) &sin, sizeof(sin));
	sin.sin_family = AF_INET;
#else
	struct  servent *getservbyname(), *sp;
	struct  hostent *gethostbyname(), *hp;
#ifdef h_addr
	int     x = 0;
	register char **cp;
	static char *alist[1];
#endif /* h_addr */
	unsigned long inet_addr();
	static struct hostent def;
	static struct in_addr defaddr;
	static char namebuf[ 256 ];

	if ((sp = getservbyname("nntp", "tcp")) ==  NULL) {
		fprintf(stderr, "nntp/tcp: Unknown service.\n");
		return (-1);
	}
	/* If not a raw ip address, try nameserver */
	if (!isdigit(*machine) ||
	    (long)(defaddr.s_addr = inet_addr(machine)) == -1)
		hp = gethostbyname(machine);
	else {
		/* Raw ip address, fake  */
		(void) strcpy(namebuf, machine);
		def.h_name = namebuf;
#ifdef h_addr
		def.h_addr_list = alist;
#endif
		def.h_addr = (char *)&defaddr;
		def.h_length = sizeof(struct in_addr);
		def.h_addrtype = AF_INET;
		def.h_aliases = 0;
		hp = &def;
	}
	if (hp == NULL) {
		fprintf(stderr, "%s: Unknown host.\n", machine);
		return (-1);
	}
	bzero((char *) &sin, sizeof(sin));
	sin.sin_family = hp->h_addrtype;
	sin.sin_port = sp->s_port;
#endif /* NONETDB */

	/*
	 * The following is kinda gross.  The name server under 4.3
	 * returns a list of addresses, each of which should be tried
	 * in turn if the previous one fails.  However, 4.2 hostent
	 * structure doesn't have this list of addresses.
	 * Under 4.3, h_addr is a #define to h_addr_list[0].
	 * We use this to figure out whether to include the NS specific
	 * code...
	 */

#ifdef  h_addr

	/* get a socket and initiate connection -- use multiple addresses */

	for (cp = hp->h_addr_list; cp && *cp; cp++) {
		s = socket(hp->h_addrtype, SOCK_STREAM, 0);
		if (s < 0) {
			perror("socket");
			return (-1);
		}
		bcopy(*cp, (char *)&sin.sin_addr, hp->h_length);
		
		if (x < 0)
			fprintf(stderr, "trying %s\n", inet_ntoa(sin.sin_addr));
		x = connect(s, (struct sockaddr *)&sin, sizeof (sin));
		if (x == 0)
			break;
		fprintf(stderr, "connection to %s: ", inet_ntoa(sin.sin_addr));
		perror("");
		(void) close(s);
	}
	if (x < 0) {
		fprintf(stderr, "giving up...\n");
		return (-1);
	}
#else   /* no name server */
#ifdef EXCELAN
	if ((s = socket(SOCK_STREAM,(struct sockproto *)NULL,&sin,SO_KEEPALIVE)) < 0)
	{
		/* Get the socket */
		perror("socket");
		return (-1);
	}
	bzero((char *) &sin, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(IPPORT_NNTP);
	/* set up addr for the connect */

	if ((sin.sin_addr.s_addr = rhost(&machine)) == -1) {
		fprintf(stderr, "%s: Unknown host.\n", machine);
		return (-1);
	}
	/* And then connect */

	if (connect(s, (struct sockaddr *)&sin) < 0) {
		perror("connect");
		(void) close(s);
		return (-1);
	}
#else /* not EXCELAN */
	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		return (-1);
	}

	/* And then connect */

	bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
	if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		perror("connect");
		(void) close(s);
		return (-1);
	}

#endif /* !EXCELAN */
#endif /* !h_addr */
#endif /* !PCTCP */
	return (s);
}

#endif /* !DATAKIT */

#ifdef DECNET
/*
 * get_dnet_socket -- get us a socket connected to the news server.
 *
 *      Parameters:     "machine" is the machine the server is running on.
 *
 *      Returns:        Socket connected to the news server if
 *                      all is ok, else -1 on error.
 *
 *      Side effects:   Connects to server.
 *
 *      Errors:         Printed via nerror.
 */

get_dnet_socket(machine)
char    *machine;
{
	int     s, area, node;
	struct  sockaddr_dn sdn;
	struct  nodeent *getnodebyname(), *np;

	bzero((char *) &sdn, sizeof(sdn));

	switch (s = sscanf( machine, "%d%*[.]%d", &area, &node )) {
		case 1: 
			node = area;
			area = 0;
		case 2: 
			node += area*1024;
			sdn.sdn_add.a_len = 2;
			sdn.sdn_family = AF_DECnet;
			sdn.sdn_add.a_addr[0] = node % 256;
			sdn.sdn_add.a_addr[1] = node / 256;
			break;
		default:
			if ((np = getnodebyname(machine)) == NULL) {
				fprintf(stderr, 
					"%s: Unknown host.\n", machine);
				return (-1);
			} else {
				bcopy(np->n_addr, 
					(char *) sdn.sdn_add.a_addr, 
					np->n_length);
				sdn.sdn_add.a_len = np->n_length;
				sdn.sdn_family = np->n_addrtype;
			}
			break;
	}
	sdn.sdn_objnum = 0;
	sdn.sdn_flags = 0;
	sdn.sdn_objnamel = strlen("NNTP");
	bcopy("NNTP", &sdn.sdn_objname[0], sdn.sdn_objnamel);

	if ((s = socket(AF_DECnet, SOCK_STREAM, 0)) < 0) {
		nerror("socket");
		return (-1);
	}

	/* And then connect */

	if (connect(s, (struct sockaddr *) &sdn, sizeof(sdn)) < 0) {
		nerror("connect");
		close(s);
		return (-1);
	}

	return (s);
}
#endif
#ifdef SGI4DDN
/*
 * get_dnet_socket -- get us a socket connected to the news server.
 * (Silicon Graphics Version)
 *      Parameters:     "machine" is the machine the server is running on.
 *
 *      Returns:        link ("socket") connected to the news server if
 *                      all is ok, else -1 on error.
 *
 *      Side effects:   Connects to server.
 *
 *      Errors:         Printed via dn_perror.
 */

get_dnet_socket(machine)
char    *machine;
{
	int     s, area, node;
	OpenBlock sdn;

	bzero((char *) &sdn, sizeof(sdn));
	strcpy(sdn.op_node_name,machine);
	strcpy(sdn.op_task_name,"NNTP");

	if ((s = open(DN_LINK,O_RDWR)) < 0) {
		dn_perror("Open Fail: ");
		return (-1);
	}

	if (ioctl(s, SES_LINK_ACCESS, &sdn) < 0) {
		dn_perror("connect");
		close(s);
		return (-1);
	}

	return (s);
}
#endif



/*
 * handle_server_response
 *
 *      Print some informative messages based on the server's initial
 *      response code.  This is here so inews, rn, etc. can share
 *      the code.
 *
 *      Parameters:     "response" is the response code which the
 *                      server sent us, presumably from "server_init",
 *                      above.
 *                      "nntpserver" is the news server we got the
 *                      response code from.
 *
 *      Returns:        -1 if the error is fatal (and we should exit).
 *                      0 otherwise.
 *
 *      Side effects:   None.
 */

handle_server_response(response, nntpserver)
int     response;
char    *nntpserver;
{
    switch (response) {
	case OK_NOPOST:         /* fall through */
		printf(
	"NOTE: This machine does not have permission to post articles.\n");
		printf(
	"      Please don't waste your time trying.\n\n");

	case OK_CANPOST:
		return (0);
		break;

	case ERR_ACCESS:
		printf(
   "This machine does not have permission to use the %s news server.\n",
		nntpserver);
		return (-1);
		break;

	default:
		printf("Unexpected response code from %s news server: %d\n",
			nntpserver, response);
		return (-1);
		break;
    }
	/*NOTREACHED*/
}


/*
 * put_server -- send a line of text to the server, terminating it
 * with CR and LF, as per ARPA standard.
 *
 *      Parameters:     "string" is the string to be sent to the
 *                      server.
 *
 *      Returns:        Nothing.
 *
 *      Side effects:   Talks to the server.
 *
 *      Note:           This routine flushes the buffer each time
 *                      it is called.  For large transmissions
 *                      (i.e., posting news) don't use it.  Instead,
 *                      do the fprintf's yourself, and then a final
 *                      fflush.
 */

void
put_server(string)
char *string;
{
#ifdef DEBUG
	fprintf(stderr, ">>> %s\n", string);
#endif
#if defined(PCTCP) || defined(LWP)
	net_write(nd,string,strlen(string),0);
	net_write(nd,"\r\n",2,0);
/*      net_flush(nd); */
#else
	fprintf(ser_wr_fp, "%s\r\n", string);
	(void) fflush(ser_wr_fp);
#endif
	return;
}

#ifdef DATAKIT
/*
 * dkfgets - DATAKIT substitute for fgets
 *
 * The basic problem is that fgets is incompatible with alarm().  When
 * the file stream used by fgets() (or any stdio) function is a "slow"
 * device, i.e. not disk, signals generated by the alarm clock will
 * interrupt a read and cause data to be thrown away.  This means that
 * get_server will return an error.  The return code is not checked by
 * the callers of get_server and the ensuing ARTICLE loop ends up leaving
 * the server flow-controlled and both sides hung.  In these additions
 * we use this local routine which disables the alarm before doing the read, 
 * and restore them after the read.
 */
char *
dkfgets(string, size, fp)
char    *string;
int     size;
FILE    *fp;
{
	static struct dk_io {
		char *ptr;
		char *buffr;
		int avail;
	} io_p = { 0, 0, 0};
	register struct dk_io *io = &io_p;
	register char *cp;
	register to_move;
	char *save_string = string;
 
	if (!io->buffr) {
		/* get initial buffer */
		io->buffr = (char *) malloc(BUFSIZ);
		if (io->buffr == NULL)
			return(NULL);
	}
	while (size) {
		if (io->avail == 0) {
			unsigned alarm_time;
			/* try to fill buffer */
			alarm_time = (unsigned) alarm(0);
			if ((io->avail = read(fileno(fp), io->buffr, BUFSIZ)) <
0) {
				fprintf(stderr, "Read Failure on NNTP\n");
				return(NULL);
			}
			if (alarm_time)
				(void) alarm(alarm_time);
			io->ptr = io->buffr;
		}
		cp = memchr(io->ptr, '\n', min(size, io->avail));
		if (cp) {
			/* this buffer contains a new line */
			to_move = cp - io->ptr + 1;
			size = 0;
		}
		else {
			to_move = min(size, io->avail);
			size -= to_move;
		}
		(void) memcpy(string, io->ptr, to_move);
 
		io->avail -= to_move;
		io->ptr += to_move;
		string += to_move;
	}
	return(save_string);
}
#endif /* DATAKIT */

/*
 * get_server -- get a line of text from the server.  Strips
 * CR's and LF's.
 *
 *      Parameters:     "string" has the buffer space for the
 *                      line received.
 *                      "size" is the size of the buffer.
 *
 *      Returns:        -1 on error, 0 otherwise.
 *
 *      Side effects:   Talks to server, changes contents of "string".
 */

get_server(string, size)
char    *string;
int     size;
{
	register char *cp;
	char *index();

#if defined(PCTCP) || defined(LWP)
	int i = 0;
	bzero(string,size);
	cp = string;
	while(i < size){
		if (net_read(nd,cp,1,(struct addr *) NULL,0) < 0){
			if (i > 0)
				break;
			else 
				return(-1);
		}
		if (*cp == '\r' || *cp == '\n'){
			*cp = '\0';
			if (i>0)
				break;
			else
				continue;
		}
		cp++;
		i++;
	}
#else
# ifdef DATAKIT
	if (dkfgets(string, size, ser_rd_fp) == NULL)
# else /* !DATAKIT */
	if (fgets(string, size, ser_rd_fp) == NULL)
# endif /* !DATAKIT */
		return (-1);
	if ((cp = index(string, '\r')) != NULL)
		*cp = '\0';
	else if ((cp = index(string, '\n')) != NULL)
		*cp = '\0';
#endif
#ifdef DEBUG
	fprintf(stderr, "<<< %s\n", string);
#endif

	return (0);
}


/*
 * close_server -- close the connection to the server, after sending
 *              the "quit" command.
 *
 *      Parameters:     None.
 *
 *      Returns:        Nothing.
 *
 *      Side effects:   Closes the connection with the server.
 *                      You can't use "put_server" or "get_server"
 *                      after this routine is called.
 */

void
close_server()
{
	char    ser_line[256];

#if !defined(PCTCP) && !defined(LWP)
	if (ser_wr_fp == NULL || ser_rd_fp == NULL)
		return;
#endif

	put_server("QUIT");
#ifndef DATAKIT
	(void) get_server(ser_line, sizeof(ser_line));
#endif
#ifdef LWP
	(void) soclose(nd);
#else
#ifdef PCTCP
	(void) net_eof(nd);
	(void) net_releaseall();
#else
#ifndef EXCELAN
	shutdown(fileno(ser_rd_fp),2);
#endif
	(void) fclose(ser_rd_fp);
	(void) fclose(ser_wr_fp);
#endif
#endif
	return;
}

#ifdef NONETDB
/*
 * inet_addr for EXCELAN (which does not have it!)
 *
 */
unsigned long
inet_addr(cp)
register char   *cp;
{
	unsigned long val, base, n;
	register char c;
	unsigned long octet[4], *octetptr = octet;
#ifndef htonl
	extern  unsigned long   htonl();
#endif  /* htonl */
again:
	/*
	 * Collect number up to ``.''.
	 * Values are specified as for C:
	 * 0x=hex, 0=octal, other=decimal.
	 */
	val = 0; base = 10;
	if (*cp == '0')
		base = 8, cp++;
	if (*cp == 'x' || *cp == 'X')
		base = 16, cp++;
	while (c = *cp) {
		if (isdigit(c)) {
			val = (val * base) + (c - '0');
			cp++;
			continue;
		}
		if (base == 16 && isxdigit(c)) {
			val = (val << 4) + (c + 10 - (islower(c) ? 'a' : 'A'));
			cp++;
			continue;
		}
		break;
	}
	if (*cp == '.') {
		/*
		 * Internet format:
		 *      a.b.c.d
		 *      a.b.c   (with c treated as 16-bits)
		 *      a.b     (with b treated as 24 bits)
		 */
		if (octetptr >= octet + 4)
			return (-1);
		*octetptr++ = val, cp++;
		goto again;
	}
	/*
	 * Check for trailing characters.
	 */
	if (*cp && !isspace(*cp))
		return (-1);
	*octetptr++ = val;
	/*
	 * Concoct the address according to
	 * the number of octet specified.
	 */
	n = octetptr - octet;
	switch (n) {

	case 1:                         /* a -- 32 bits */
		val = octet[0];
		break;

	case 2:                         /* a.b -- 8.24 bits */
		val = (octet[0] << 24) | (octet[1] & 0xffffff);
		break;

	case 3:                         /* a.b.c -- 8.8.16 bits */
		val = (octet[0] << 24) | ((octet[1] & 0xff) << 16) |
			(octet[2] & 0xffff);
		break;

	case 4:                         /* a.b.c.d -- 8.8.8.8 bits */
		val = (octet[0] << 24) | ((octet[1] & 0xff) << 16) |
		      ((octet[2] & 0xff) << 8) | (octet[3] & 0xff);
		break;

	default:
		return (-1);
	}
	val = htonl(val);
	return (val);
}
#endif /* NONETDB */

