/*	$NetBSD: scmio.c,v 1.19 2009/10/18 15:23:54 christos Exp $	*/

/*
 * Copyright (c) 1992 Carnegie Mellon University
 * All Rights Reserved.
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 * SUP Communication Module for 4.3 BSD
 *
 * SUP COMMUNICATION MODULE SPECIFICATIONS:
 *
 * IN THIS MODULE:
 *
 * OUTPUT TO NETWORK
 *
 *   MESSAGE START/END
 *	writemsg (msg)		start message
 *	  int msg;			message type
 *	writemend ()		end message and flush data to network
 *
 *   MESSAGE DATA
 *	writeint (i)		write int
 *	  int i;			integer to write
 *	writestring (p)		write string
 *	  char *p;			string pointer
 *	writefile (f)		write open file
 *	  int f;			open file descriptor
 *
 *   COMPLETE MESSAGE (start, one data block, end)
 *	writemnull (msg)	write message with no data
 *	  int msg;			message type
 *	writemint (msg,i)	write int message
 *	  int msg;			message type
 *	  int i;			integer to write
 *	writemstr (msg,p)	write string message
 *	  int msg;			message type
 *	  char *p;			string pointer
 *
 * INPUT FROM NETWORK
 *   MESSAGE START/END
 *	readflush ()		flush any unread data (close)
 *	readmsg (msg)		read specified message type
 *	  int msg;			message type
 *	readmend ()		read message end
 *
 *   MESSAGE DATA
 *	readskip ()		skip over one input data block
 *	readint (i)		read int
 *	  int *i;			pointer to integer
 *	readstring (p)		read string
 *	  char **p;			pointer to string pointer
 *	readfile (f)		read into open file
 *	  int f;			open file descriptor
 *
 *   COMPLETE MESSAGE (start, one data block, end)
 *	readmnull (msg)		read message with no data
 *	  int msg;			message type
 *	readmint (msg,i)	read int message
 *	  int msg;			message type
 *	  int *i;			pointer to integer
 *	readmstr (msg,p)	read string message
 *	  int msg;			message type
 *	  char **p;			pointer to string pointer
 *
 * RETURN CODES
 *	All routines normally return SCMOK.  SCMERR may be returned
 *	by any routine on abnormal (usually fatal) errors.  An
 *	unexpected MSGGOAWAY will result in SCMEOF being returned.
 *
 * COMMUNICATION PROTOCOL
 *	Messages always alternate, with the first message after
 *	connecting being sent by the client process.
 *
 *	At the end of the conversation, the client process will
 *	send a message to the server telling it to go away.  Then,
 *	both processes will close the network connection.
 *
 *	Any time a process receives a message it does not expect,
 *	the "readmsg" routine will send a MSGGOAWAY message and
 *	return SCMEOF.
 *
 *	Each message has this format:
 *	    ----------    ------------    ------------         ----------
 *	    |msg type|    |count|data|    |count|data|   ...   |ENDCOUNT|
 *	    ----------    ------------    ------------         ----------
 *	size:  int	    int  var.	    int  var.	   	  int
 *
 *	All ints are assumed to be 32-bit quantities.  A message
 *	with no data simply has a message type followed by ENDCOUNT.
 *
 **********************************************************************
 * HISTORY
 * Revision 1.7  92/09/09  22:04:41  mrt
 * 	Removed the data encryption routines from here to netcrypt.c
 * 	[92/09/09            mrt]
 *
 * Revision 1.6  92/08/11  12:05:57  mrt
 * 	Brad's changes: Delinted,Added forward declarations of
 * 	static functions. Added copyright.
 * 	[92/07/24            mrt]
 *
 * 27-Dec-87  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Added crosspatch support.
 *
 * 28-Jun-87  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Found error in debugging code for readint().
 *
 * 01-Apr-87  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Added code to readdata to "push" data back into the data buffer.
 *	Added prereadcount() to return the message count size after
 *	reading it and then pushing it back into the buffer.  Clear
 *	any encryption when a GOAWAY message is detected before reading
 *	the reason string. [V5.19]
 *
 * 02-Oct-86  Rudy Nedved (ern) at Carnegie-Mellon University
 *	Put a timeout on reading from the network.
 *
 * 25-May-86  Jonathan J. Chew (jjc) at Carnegie-Mellon University
 *	Renamed "howmany" parameter to routines "encode" and "decode" from
 *	to "count" to avoid conflict with 4.3BSD macro.
 *
 * 15-Feb-86  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Added readflush() to flush any unread data from the input
 *	buffer.  Called by requestend() in scm.c module.
 *
 * 19-Jan-86  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Added register variables to decode() for speedup.  Added I/O
 *	buffering to reduce the number or read/write system calls.
 *	Removed readmfil/writemfil routines which were not used and were
 *	not compatible with the other similarly defined routines anyway.
 *
 * 19-Dec-85  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Created from scm.c I/O and crypt routines.
 *
 **********************************************************************
 */

#include "libc.h"
#include <errno.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/poll.h>
#include "supcdefs.h"
#include "supextern.h"
#include "supmsg.h"
#ifndef INFTIM
#define INFTIM -1
#endif

/*************************
 ***    M A C R O S    ***
 *************************/

/* end of message */
#define ENDCOUNT (-1)		/* end of message marker */
#define NULLCOUNT (-2)		/* used for sending NULL pointer */

#define RETRIES 15		/* # of times to retry io */
#define FILEXFER 2048		/* block transfer size */
#define XFERSIZE(count) ((count > FILEXFER) ? FILEXFER : count)

/*********************************************
 ***    G L O B A L   V A R I A B L E S    ***
 *********************************************/

extern int netfile;		/* network file descriptor */

int scmdebug;			/* scm debug flag */

int cryptflag;			/* whether to encrypt/decrypt data */
char *cryptbuf;			/* buffer for data encryption/decryption */

extern char *goawayreason;	/* reason for goaway message */

struct buf {
	char b_data[FILEXFER];	/* buffered data */
	char *b_ptr;		/* pointer to end of buffer */
	int b_cnt;		/* number of bytes in buffer */
}   buffers[2];
struct buf *gblbufptr;		/* buffer pointer */

static int writedata(size_t, void *);
static int writeblock(size_t, void *);
static int readdata(size_t, void *, bool);
static int readcount(int *);


/***********************************************
 ***    O U T P U T   T O   N E T W O R K    ***
 ***********************************************/

static int
writedata(size_t count, void *data)
{				/* write raw data to network */
	int x, tries;
	struct buf *bp;

	if (gblbufptr) {
		if (gblbufptr->b_cnt + count <= FILEXFER) {
			memcpy(gblbufptr->b_ptr, data, count);
			gblbufptr->b_cnt += count;
			gblbufptr->b_ptr += count;
			return (SCMOK);
		}
		bp = (gblbufptr == buffers) ? &buffers[1] : buffers;
		memcpy(bp->b_data, data, count);
		bp->b_cnt = count;
		bp->b_ptr = bp->b_data + count;
		data = gblbufptr->b_data;
		count = gblbufptr->b_cnt;
		gblbufptr->b_cnt = 0;
		gblbufptr->b_ptr = gblbufptr->b_data;
		gblbufptr = bp;
	}
	tries = 0;
	for (;;) {
		errno = 0;
		x = write(netfile, data, count);
		if (x > 0)
			break;
		if (errno)
			break;
		if (++tries > RETRIES)
			break;
		if (scmdebug > 0)
			logerr("SCM Retrying failed network write");
	}
	if (x <= 0) {
		if (errno == EPIPE)
			return (scmerr(-1, "Network write timed out"));
		if (errno)
			return (scmerr(errno, "Write error on network"));
		return (scmerr(-1, "Write retries failed"));
	}
	if (x != count)
		return (scmerr(-1, "Write error on network returned %d "
		    "on write of %zu", x, count));
	return (SCMOK);
}

static int
writeblock(size_t count, void *data)
{				/* write data block */
	int x;
	int y = byteswap((int)count);

	x = writedata(sizeof(int), &y);
	if (x == SCMOK)
		x = writedata(count, data);
	return (x);
}

int
writemsg(int msg)
{				/* write start of message */
	int x;

	if (scmdebug > 1)
		loginfo("SCM Writing message %d", msg);
	if (gblbufptr)
		return (scmerr(-1, "Buffering already enabled"));
	gblbufptr = buffers;
	gblbufptr->b_ptr = gblbufptr->b_data;
	gblbufptr->b_cnt = 0;
	x = byteswap(msg);
	return (writedata(sizeof(int), &x));
}

int
writemend(void)
{				/* write end of message */
	size_t count;
	char *data;
	int x;

	x = byteswap(ENDCOUNT);
	x = writedata(sizeof(int), &x);
	if (x != SCMOK)
		return (x);
	if (gblbufptr == NULL)
		return (scmerr(-1, "Buffering already disabled"));
	if (gblbufptr->b_cnt == 0) {
		gblbufptr = NULL;
		return (SCMOK);
	}
	data = gblbufptr->b_data;
	count = gblbufptr->b_cnt;
	gblbufptr = NULL;
	return (writedata(count, data));
}

int
writeint(int i)
{				/* write int as data block */
	int x;
	if (scmdebug > 2)
		loginfo("SCM Writing integer %d", i);
	x = byteswap(i);
	return (writeblock(sizeof(int), &x));
}

int
writestring(char *p)
{				/* write string as data block */
	int len;
	int x;
	if (p == NULL) {
		int y = byteswap(NULLCOUNT);
		if (scmdebug > 2)
			loginfo("SCM Writing string NULL");
		return (writedata(sizeof(int), &y));
	}
	if (scmdebug > 2)
		loginfo("SCM Writing string %s", p);
	len = strlen(p);
	if (cryptflag) {
		x = getcryptbuf(len + 1);
		if (x != SCMOK)
			return (x);
		encode(p, cryptbuf, len);
		p = cryptbuf;
	}
	return (writeblock((size_t)len, p));
}

int
writefile(int f)
{				/* write open file as a data block */
	char buf[FILEXFER];
	int number = 0, sum = 0, filesize, x;
	int y;
	struct stat statbuf;

	if (fstat(f, &statbuf) < 0)
		return (scmerr(errno, "Can't access open file for message"));
	filesize = (int)statbuf.st_size;
	y = byteswap(filesize);
	x = writedata(sizeof(int), &y);

	if (cryptflag)
		x = getcryptbuf(FILEXFER);

	if (x == SCMOK) {
		sum = 0;
		do {
			number = read(f, buf, FILEXFER);
			if (number > 0) {
				if (cryptflag) {
					encode(buf, cryptbuf, number);
					x = writedata((size_t)number, cryptbuf);
				} else {
					x = writedata((size_t)number, buf);
				}
				sum += number;
			}
		} while (x == SCMOK && number > 0);
	}
	if (sum != filesize)
		return (scmerr(-1, "File size error on output message"));
	if (number < 0)
		return (scmerr(errno, "Read error on file output message"));
	return (x);
}

int
writemnull(int msg)
{				/* write message with no data */
	int x;
	x = writemsg(msg);
	if (x == SCMOK)
		x = writemend();
	return (x);
}

int
writemint(int msg, int i)
{				/* write message of one int */
	int x;
	x = writemsg(msg);
	if (x == SCMOK)
		x = writeint(i);
	if (x == SCMOK)
		x = writemend();
	return (x);
}

int
writemstr(int msg, char *p)
{				/* write message of one string */
	int x;
	x = writemsg(msg);
	if (x == SCMOK)
		x = writestring(p);
	if (x == SCMOK)
		x = writemend();
	return (x);
}
/*************************************************
 ***    I N P U T   F R O M   N E T W O R K    ***
 *************************************************/

static int
readdata(size_t count, void *vdata, bool push)
{				/* read raw data from network */
	char *p;
	int c, n, m, x;
	static int bufcnt = 0;
	static char *bufptr;
	static char buffer[FILEXFER];
	struct pollfd set[1];
	char *data = vdata;

	if (push) {
		if (bufptr + count < buffer)
			return (scmerr(-1, "No space in buffer %zu", count));
		bufptr -= count;
		bufcnt += count;
		memcpy(bufptr, data, -count);
		return (SCMOK);
	}
	if (count == 0 && data == NULL) {
		bufcnt = 0;
		return (SCMOK);
	}
	if (count <= bufcnt) {
		memcpy(data, bufptr, count);
		bufptr += count;
		bufcnt -= count;
		return (SCMOK);
	}
	if (bufcnt > 0) {
		memcpy(data, bufptr, (size_t)bufcnt);
		data += bufcnt;
		count -= bufcnt;
	}
	bufptr = buffer;
	bufcnt = 0;
	set[0].fd = netfile;
	set[0].events = POLLIN;
	p = buffer;
	n = FILEXFER;
	m = count;
	while (m > 0) {
		while ((c = poll(set, 1, 2 * 60 * 60 * 1000)) < 1) {
			if (c == 0)
				return (scmerr(-1, "Timeout on network input"));
			if (errno != EINTR)
				sleep(5);
		}
		x = read(netfile, p, (size_t)n);
		if (x == 0)
			return (scmerr(-1, "Premature EOF on network input"));
		if (x < 0)
			return (scmerr(errno, "Read error on network"));
		p += x;
		n -= x;
		m -= x;
		bufcnt += x;
	}
	memcpy(data, bufptr, (size_t)count);
	bufptr += count;
	bufcnt -= count;
	return (SCMOK);
}

static int 
readcount(int *count)
{				/* read count of data block */
	int x;
	int y;
	x = readdata(sizeof(int), &y, false);
	if (x != SCMOK)
		return (x);
	*count = byteswap(y);
	return (SCMOK);
}

int 
prereadcount(int *count)
{				/* preread count of data block */
	int x;
	int y;
	x = readdata(sizeof(int), &y, false);
	if (x != SCMOK)
		return (x);
	x = readdata(sizeof(int), &y, true);
	if (x != SCMOK)
		return (x);
	*count = byteswap(y);
	return (SCMOK);
}

int
readflush(void)
{
	return readdata(0, NULL, false);
}

int
readmsg(int msg)
{				/* read header for expected message */
	/* if message is unexpected, send back SCMHUH */
	int x;
	int m;
	if (scmdebug > 1)
		loginfo("SCM Reading message %d", msg);
	x = readdata(sizeof(int), &m, false);	/* msg type */
	if (x != SCMOK)
		return (x);
	m = byteswap(m);
	if (m == msg)
		return (x);

	/* check for MSGGOAWAY in case he noticed problems first */
	if (m != MSGGOAWAY)
		return (scmerr(-1, "Received unexpected message %d", m));
	(void) netcrypt((char *) NULL);
	(void) readstring(&goawayreason);
	(void) readmend();
	if (goawayreason == NULL)
		return (SCMEOF);
	logerr("SCM GOAWAY %s", goawayreason);
	return (SCMEOF);
}

int
readmend(void)
{
	int x;
	int y;
	x = readdata(sizeof(int), &y, false);
	y = byteswap(y);
	if (x == SCMOK && y != ENDCOUNT)
		return (scmerr(-1, "Error reading end of message"));
	return (x);
}

int
readskip(void)
{				/* skip over one input block */
	int x;
	int n;
	char buf[FILEXFER];
	x = readcount(&n);
	if (x != SCMOK)
		return (x);
	if (n < 0)
		return (scmerr(-1, "Invalid message count %d", n));
	while (x == SCMOK && n > 0) {
		x = readdata((size_t)XFERSIZE(n), buf, false);
		n -= XFERSIZE(n);
	}
	return (x);
}

int 
readint(int *buf)
{				/* read int data block */
	int x;
	int y;
	x = readcount(&y);
	if (x != SCMOK)
		return (x);
	if (y < 0)
		return (scmerr(-1, "Invalid message count %d", y));
	if (y != sizeof(int))
		return (scmerr(-1, "Size error for int message is %d", y));
	x = readdata(sizeof(int), &y, false);
	(*buf) = byteswap(y);
	if (scmdebug > 2)
		loginfo("SCM Reading integer %d", *buf);
	return (x);
}

int 
readstring(char **buf)
{				/* read string data block */
	int x;
	int count;
	char *p;

	x = readcount(&count);
	if (x != SCMOK)
		return (x);
	if (count == NULLCOUNT) {
		if (scmdebug > 2)
			loginfo("SCM Reading string NULL");
		*buf = NULL;
		return (SCMOK);
	}
	if (count < 0)
		return (scmerr(-1, "Invalid message count %d", count));
	if (scmdebug > 3)
		loginfo("SCM Reading string count %d", count);
	if ((p = (char *) malloc((unsigned) count + 1)) == NULL)
		return (scmerr(-1, "Can't malloc %d bytes for string", count));
	if (cryptflag) {
		x = getcryptbuf(count + 1);
		if (x == SCMOK)
			x = readdata((size_t)count, cryptbuf, false);
		if (x != SCMOK) {
			free(p);
			return (x);
		}
		if (scmdebug > 3)
			printf("SCM Reading encrypted string %s\n", cryptbuf);
		decode(cryptbuf, p, count);
	} else {
		x = readdata((size_t)count, p, false);
		if (x != SCMOK) {
			free(p);
			return (x);
		}
	}
	p[count] = 0;		/* NULL at end of string */
	*buf = p;
	if (scmdebug > 2)
		loginfo("SCM Reading string %s", *buf);
	return (SCMOK);
}

int
readfile(int f)
{				/* read data block into open file */
	int x;
	int count;
	char buf[FILEXFER];

	if (cryptflag) {
		x = getcryptbuf(FILEXFER);
		if (x != SCMOK)
			return (x);
	}
	x = readcount(&count);
	if (x != SCMOK)
		return (x);
	if (count < 0)
		return (scmerr(-1, "Invalid message count %d", count));
	while (x == SCMOK && count > 0) {
		if (cryptflag) {
			x = readdata((size_t)XFERSIZE(count), cryptbuf, false);
			if (x == SCMOK)
				decode(cryptbuf, buf, XFERSIZE(count));
		} else
			x = readdata((size_t)XFERSIZE(count), buf, false);
		if (x == SCMOK) {
			(void) write(f, buf, (size_t)XFERSIZE(count));
			count -= XFERSIZE(count);
		}
	}
	return (x);
}

int
readmnull(int msg)
{				/* read null message */
	int x;
	x = readmsg(msg);
	if (x == SCMOK)
		x = readmend();
	return (x);
}

int
readmint(int msg, int *buf)
{				/* read int message */
	int x;
	x = readmsg(msg);
	if (x == SCMOK)
		x = readint(buf);
	if (x == SCMOK)
		x = readmend();
	return (x);
}

int 
readmstr(int msg, char **buf)
{				/* read string message */
	int x;
	x = readmsg(msg);
	if (x == SCMOK)
		x = readstring(buf);
	if (x == SCMOK)
		x = readmend();
	return (x);
}
/**********************************
 ***    C R O S S P A T C H     ***
 **********************************/

void
crosspatch(void)
{
	struct pollfd set[2];
	int c;
	char buf[STRINGLENGTH];

	set[0].fd = STDIN_FILENO;
	set[0].events = POLLIN;
	set[1].fd = netfile;
	set[1].events = POLLIN;
	for (;;) {
		if ((c = poll(set, 2, INFTIM)) < 1) {
			if (c == -1) {
				if (errno == EINTR) {
					continue;
				}
			}
			sleep(5);
			continue;
		}
		if (set[1].revents & POLLIN) {
			c = read(netfile, buf, sizeof(buf));
			if (c < 0 && errno == EWOULDBLOCK)
				c = 0;
			else {
				if (c <= 0) {
					break;
				}
				(void) write(1, buf, (size_t)c);
			}
		}
		if (set[0].revents & POLLIN) {
			c = read(0, buf, sizeof(buf));
			if (c < 0 && errno == EWOULDBLOCK)
				c = 0;
			else {
				if (c <= 0)
					break;
				(void) write(netfile, buf, (size_t)c);
			}
		}
	}
}
