/* pe7mai.c */
/*
 *	K e r m i t  File Transfer Utility
 *
 *	UNIX Kermit, Columbia University, 1981, 1982, 1983
 *	Bill Catchings, Bob Cattani, Chris Maio, Frank da Cruz, Alan Crosswell
 *
 *	Also:   Jim Guyton, Rand Corporation
 *		Walter Underwood, Ford Aerospace
 *
 * usage	kermit -s [-f -d -i -u -t -p -l -b] file ...	to send files
 *			kermit -r [-d -i -u -t -p -l -b]			to receive files
 *			kermit -g [-f -d -i -u -t -p -l -b] file ...	to get files
 *			kermit -f [-d -t -p -l]					to send finish command
 *			kermit -x [-d -v -i -u -t -p -l]			to set server mode
 *	where	s=send <flag>, r=receive <flag>, x=server <flag>,
 *			f=finish mode <flag>, g=get files mode <flag>,
 *			d=debug level <number>, v=verbose mode <flag>,
 *			i= image mode <flag>, u=no file name conversion <flag>
 *			l=tty line <device>, b=baud rate <speed>,
 *			t=turnaround mode <flag>, p=parity type <char>
 */

/*
 *	Modification History:
 *
 *  Version 1.1(0)
 *  December 4, 1986 - Add btobemp to decode other prefixxed packets.
 *                   - Split program into parts to allow compiling on
 *                     machine with minimum memory configuration.
 *                     put in version number (use -v flag)
 *                        Chris Lent (ihnp4!philabs!phri!cooper!chris)
 *  March 85	- Change to use the IDRIS operating system

 *  Oct. 17 Included fixes from Alan Crosswell (CUCCA) for IBM_UTS:
 *	  - Changed MYEOL character from \n to \r.
 *	  - Change char to int in bufill so getc would return -1 on
 *		EOF instead of 255 (-1 truncated to 8 bits)
 *	  - Added read() in rpack to eat the EOL character
 *	  - Added fflush() call in printmsg to force the output
 *	  NOTE: The last three changes are not conditionally compiled
 *		since they should work equally well on any system.
 *
 *	  Changed Berkeley 4.x conditional compilation flag from
 *	  UNIX4X to UCB4X.
 *	  Added support for error packets and cleaned up the printing
 *	  routines.
 */
#define KVERSION "Perkin-Elmer 7000 Series 1.1(0) 5 Dec 86"
#define UTEXT	0		/* Set for 8 bit text strings */

#include <std.h>		/* Standard UNIX definitions */
#include <sys.h>		/* Idris system subroutines */
/*#include "sgtty.h"		/* Special tty defs */
#include "pe7tty.h"		/* Special tty defs */

/* Symbol Definitions */

#define BUFSIZE 80		/* A buffer size for I/O */
#define MAXTRY	8		/* Times to retry a packet */
#define SOH		1		/* Start of header */
#define CR		13		/* ASCII Carriage Return */
#define XON		17		/* ASCII xon character */
#define SP		32		/* ASCII space */
#define DEL		127		/* Delete (rubout) */

#define MAXPACKSIZ	94	/* Maximum packet size */
#define DEFPACKSIZ	80	/* The default packet size */
#define MAXFNAME 50 /* My maximum for filename length */
#define SPOVER	14		/* The closest I may come to the end of the output
							buffer */
#define MYTIME	10		/* Seconds after which I should be timed out */
#define MYPAD	0		/* Number of padding characters I will need */
#define MYPCHAR 0		/* Padding character I need (NULL) */
#define MYEOL	'\r'	/* End-Of-Line character I need */
#define MYQUOTE '#'		/* Quote character I will use */
#define DEFQBIN 'Y'		/* I will do binary quoting */
#define MYQBIN	'&'		/* Binary quoting character */
#define DEFCHKT	'1'		/* Default Check sum type */
#define MYCHKT	'1'		/* Check sum type */
#define DEFREPT '~'		/* Repeat character I will use */
#define MYREPT	'~'		/* Repeat character that I need */
#define TRUE	-1		/* Boolean constants */
#define FALSE	0


/* Macro Definitions */

/*
 * tochar: convert a control character to a printable one by adding a space.
 *
 * unchar: undoes tochar.
 *
 * ctl:		converts between control characters and printable characters by
 *			toggling the control bit (ie. ^A becomes A and A becomes ^A).
 */

#define tochar(ch)	((ch) + ' ')
#define unchar(ch)	((ch) - ' ')
#define ctl(ch)		((ch) ^ 64)

/* Global Variables */

/* This is the format for the return packet from the other user */
int
	spsiz {MAXPACKSIZ},	/* Maximum send packet size */
	timint {MYTIME},	/* Timeout for foreign host on sends */
	pad {0};			/* How much padding to send */
TEXT
	padchar {0},		/* Padding character to send */
	eol {MYEOL},		/* End-Of-Line character to send */
	quote {MYQUOTE},	/* Quote character in incoming data */
	qbin {0},			/* binary conversion character */
	chkt {MYCHKT},		/* Checksum type '1','2', or '3' */
	rept {0};			/* Repeat character if repeat supported */

int
	size[2] {0},		/* Size of present data in packets */
	empty[2] {0},		/* Packets ok to fill if zero */
	pknum {0},			/* Current packet buffer index */
	n {0},				/* Packet number */
	nxo {0},			/* Output packet counter for display */
	nxi {0},			/* Nak packet counter for display */
	nxs {0},			/* Saved nak counter */
	repeat_count {0},	/* Repeat counter for repeated characters */
	numtry {0},			/* Times this packet retried */
	oldtry {0},			/* Times previous packet retried */
	iflg {0},			/* Indicates to turn on the image mode */
	image {0},			/* -1 means 8-bit mode (driven by iflg) */
	speed {0},			/* baud rate of the link */
	fflg {0},			/* Indicates to send finish command */
	rflg {0},			/* Indicates receive mode */
	sflg {0},			/* Indicates send mode */
	xflg {0},			/* Indicates server mode */
	gflg {0},			/* Indicates get file mode */
	vflg {0},			/* Verbose messages in server mode */
	tflg {0},			/* Turn around flag */
	pflg {0},			/* What kind of parity are we using? */
	spflg {0},			/* This is our start-up parity flag */
	debug {0},			/* indicates level of debugging output (0=none) */
	filnamcnv {0},		/* -1 means do file name case conversions */
	filecount {0};		/* Number of files left to send */

TEXT
	state {0},			/* Present state of the automaton */
	rqbin {0},			/* 8 bit quote character saved in rpar */
	sqbin {0},			/* 8 bit quote character used in spar */
	rrept {0},			/* Repeat character used in spar */
	**filelist {0},		/* List of files to be sent */
	*filnam {0},		/* Current file name */
	*files[10] {0},		/* Pointers for the server file names */
	filename_buffer[MAXPACKSIZ] {0}; /* Buffer for the server file name */
TEXT
	recpkt[MAXPACKSIZ] {0}, /* Receive packet buffer */
	*packet[2] {0},		/* Packet pointers */
	packet0[MAXPACKSIZ] {0}, /* Send packet zero buffer */
	packet1[MAXPACKSIZ] {0}; /* Send packet one buffer */

FILE	ttyfd {0};		/* File descriptor of tty for I/O, 0 if remote */

FIO *fd {0};			/* pfio pointer */
FIO	pfio {0};			/* Buffer structure for the files */

struct tty
		ttymode {0},	/* tty raw mode */
		savemode {0};	/* tty cooked mode */

struct {
	int files;			/* Number of files */
	int fc;				/* Number of characters in files */
	int pli;			/* Number of packets received */
	int plo;			/* Number of packets sent */
	int cli;			/* Number of characters received */
	int clo;			/* Number of characters sent */
	ULONG time;			/* Total time for transaction */
  } total {0}, file {0};

/*
 *	m a i n
 *
 *	Main routine - parse command and options, set up the
 *	tty lines, and dispatch to the appropriate routine.
 *
 */

main(argc,argv)
int argc;				/* Character pointers to and count of */
char **argv;			/* command line arguments */

{
	TEXT *ttyname,		/* tty name for LINE argument */
	*cp;				/* char pointer */

	printf("IDRIS Kermit - %s",KVERSION);
	if (argc < 2)		/* Make sure there's a command line */
		Usage("Invalid command line - Not enough arguments.");
	packet[0] = &packet0;	/* Set up the packet pointers */
	packet[1] = &packet1;

	/* Turn off the parse flags */
	sflg = FALSE;
	rflg = FALSE;
	xflg = FALSE;
	gflg = FALSE;
	fflg = FALSE;
	image = iflg = FALSE;
	filnamcnv = TRUE;	/* conversion for UNIX systems */
	vflg = FALSE;		/* Turn off verbose flags */
	debug = FALSE;		/* Turn off the debug mode */
	ttyname = 0;		/* We did not get a line assignment (pointer) */
	speed = FALSE;		/* Speed not indicated */
	spflg = pflg = FALSE;		/* Parity is not indicated */
	tflg = FALSE;		/* No turn around character */

	sqbin = DEFQBIN;	/* Set up default qbin character */
	rqbin = sqbin;	
	qbin = FALSE;

	/* Reset the display counters */
	nxo = 0;
	nxi = 0;
	nxs = -1;

	getflags(&argc,&argv,\
		"s,r,x,g,f,i,u,v,d#,l*,b#,p?,t:F <file>",\
		&sflg, &rflg, &xflg, &gflg, &fflg, &iflg, &filnamcnv, &vflg,\
		&debug, &ttyname, &speed, &pflg, &tflg);

	if (!ttyname)			/* If LINE was not specified, use default */
		ttyname = "/dev/lnk0";
	ttyfd = open(ttyname, UPDATE, 0);/* Open the tty line */
	if (ttyfd <= 0)
		Usage("Cannot open %s.",ttyname);
	
	/* Put the proper tty into the correct mode */
	egtty(ttyfd,&savemode); /* save for later use */
	egtty(ttyfd,&ttymode);	/* set for changing the setup */

		printf("IDRIS Kermit - %s",KVERSION);
	vflg = vflg ? vflg : fflg || gflg || rflg || sflg || debug ?
		 TRUE : FALSE;

	if ((gflg + xflg + rflg + sflg) != 1)	/* Only one command allowed */
		if (!fflg)
			Usage("Only one command allowed - g ! r ! s ! x.");

	if (fflg && (xflg || rflg))
		Usage("Finish with server or receive?");

	if (!pflg)				/* Do we set up the parity? */
	{
		/* Check for raw mode and if true then force no parity */
		if (ttymode.t_mode & M_RAW) pflg = FALSE;
		else
		{
			/* What has the caller set into the hardware */
			switch (ttymode.t_mode & (M_EVEN | M_ODD))
			{
			case M_ODD:
				pflg = 'o';
				break;
			case M_EVEN:
				pflg = 'e';
				break;
			case M_EVEN | M_ODD:
				pflg = 'm';
				break;
			case 0:
				pflg = 's';
			}
		}
	}
	else
	{
		if (scnbuf("neoms",5,pflg) == 5)
			Usage("Improper parity call out.");
		if (pflg == 'n') pflg = FALSE;	/* If no parity then set it */
	}
	spflg = pflg;		/* Set up start-up parity */

	/* Put the hardware into raw mode */
	ttymode.t_mode = (M_RAW | M_ALL | MR_XON);

	if (speed)			/* Do we set up the speed? */
	{
		switch (speed)
		{
			case 110:
				speed = B110;
				ttymode.t_mode |= M_2STOP;
				break;
			case 150:
				speed = B150;
				break;
			case 300:
				speed = B300;
				break;
			case 1200:
				speed = B1200;
				break;
			case 2400:
				speed = B2400;
				break;
			case 4800:
				speed = B4800;
				break;
			case 9600:
				speed = B9600;
				break;
			default:
				Usage("Bad line speed.");
		}
		ttymode.t_ispeed = speed;
		ttymode.t_ospeed = speed;
	}
	/* Set up the time-out and buffer size */
	ttymode.t_min = 128;
	ttymode.t_time = MYTIME * 10;
	if (tflg)				/* Turn around flag on? */
		ttymode.t_cgo = NULL;	/* Turn off XON in XON/XOFF */

	estty(ttyfd,&ttymode);	/* Put asg'd tty in requested mode */

	/* All set up, now execute the command that was given. */

	if (filnamcnv > 0)	/* Do we want to do file name conversions? */
		filnamcnv = FALSE;

	if (iflg)			/* Turn on the image mode? */
	{
		image = TRUE;
		/* Test the parity and make a determination of the qbin
			mode info */
		if (pflg)			/* Pflg contains the parity bit if set */
			sqbin = MYQBIN;		/* Set 8-bit quoting */
		else
			sqbin = DEFQBIN;	/* We can do 8 bit transfers */
	}

	if (debug)
	{
		printf("Main 1: S=%d, R=%d, X=%d, G=%d,", sflg, rflg, xflg, gflg);
		printf(" F=%d, I=%d, U=%d, V=%d,", fflg, iflg, filnamcnv, vflg);
		printf(" D=%d\n", debug);
		printf("        L=%s, B=%d, P=%c,", ttyname, speed, pflg);
		printf(" T=%d\n", tflg);
		printf("Main 2: Bits for ttyfd %x\n",ttymode.t_mode);
	}
	if (gflg || sflg)
	{
		/* Anything to get or send? */
		if (argc--)
			filnam = *argv++;	/* Get file to send */
		else
		{
			savemode.t_time = 0;
			estty(ttyfd,&savemode); /* restore the old mode */
			Usage("File name required.");
								/* and give error */
		}
		fd = NULL;		/* Indicate no file open yet */
		filelist = argv;	/* Set up the rest of the file list */
		filecount = argc;	/* Number of files left to get or send */
	}

	dostat(1);

	if (gflg)		/* Get command */
	{
		if (debug) printf("Main 3: Get command\n");
		if (getsw() == FALSE)
			Usage("Get failed.");
		else
			printmsg("Get done.");
	}

	if (sflg)		/* Send command */ 
	{
		if (debug) printf("Main 4: Send command\n");
		if (sendsw() == FALSE)	/* Send the file(s) */
			Usage("Send failed.");	/* Report failure */
		else			/*	or */
			printmsg("Send done.");		/* success */
	}
	/* end of sflg */

	if (rflg)			/* Receive command */
	{
		if (debug) printf("Main 5: Receive command\n");
		if (debug < 3)
		{
			if (recsw() == FALSE)	/* Receive the file(s) */
				Usage("Receive failed.");
			else			/* Report failure */
				printmsg("Receive done.");	/* or success */
		}
		/* End of test for no receive of data (debug < 3) */
	}
	/* End of (rflg) test */

	if (xflg) 
		while (server() == TRUE);

	if (fflg) 
	{
		if (debug) printf("Main 6: Finish command\n");
			if (finishsw() == FALSE)
				Usage("Finish failed.");
			else
				printmsg("Finish done.");
	}

	if (vflg)
		if(total.files > 1)
		{
			printf("%d Files\n", total.files);
			printf("%d Bytes   %d Seconds   %d/%d Packets",
				total.fc, total.time, total.plo, total.pli);
			printf("   %d/%d Characters\n", total.clo, total.cli);
		}

	/* Restore the tty (reset the timeout to infinite) */
	savemode.t_time = 0;
	estty(ttyfd,&savemode);
	exit(YES);
}

/*
 *	s e n d s w
 *
 *	Sendsw is the state table switcher for sending files.  It loops until
 *	either it finishes, or an error is encountered.	The routines called
 *	by sendsw are responsible for changing the state.
 *
 */

sendsw()
{
	TEXT sinit(), sfile(), sdata(), seof(), sbreak();

	n = 0;					/* Initialize message number */
	numtry = 0;				/* Say no tries yet */
	state = 'S';			/* Send initiate is the start state */
	while(TRUE)				/* Do this as long as necessary */
	{
		if (debug) printf("Sendsw 1: State: %c\n",state);
		switch(state)
		{
		case 'S':	state = sinit();	break; /* Send-Init */
		case 'T':	state = sfile();	break; /* Send-File */
		case 'E':	state = sdata();	break; /* Send-Data */
		case 'Z':	state = seof();	break; /* Send-End-of-File */
		case 'B':	state = sbreak(); break; /* Send-Break */
		case 'C':	return (TRUE);		/* Complete */
		case 'A': 						/* "Abort" */
		default:	return (FALSE);	/* Unknown, fail */
		}
	}
}

/*
 *	r e c s w
 *
 *	This is the state table switcher for receiving files.
 *
 */

recsw()
{
	TEXT rinit(), rfile(), rdata(); /* Use these procedures */

	n = 0;					/* Initialize message number */
	numtry = 0;				/* Say no tries yet */
	state = 'R';			/* Receive-Init is the start state */
	while(TRUE)
	{
		if (debug) printf("Recsw 1: state: %c\n",state);
		switch(state)			/* Do until done */
		{
		case 'R':	state = rinit(); break; /* Receive-Init */
		case 'F':	state = rfile(); break; /* Receive-File */
		case 'D':	state = rdata(); break; /* Receive-Data */
		case 'C':	return (TRUE);		/* Complete state */
		case 'A':
		default:	return (FALSE);		/* "Abort" state */
		}
	}
}

/*
 * g e t s w
 *
 * Get a file from the other end
 *
 */

getsw()
{
	TEXT ginit(), iinit(), rfile(), rdata(); /* Use these procedures */
	TEXT filnam1[MAXFNAME],
	*newfilnam,
	*cp;
	int num, len;

	cpystr(filnam1, filnam, NULL);	/* Copy file name */
	newfilnam = cp = filnam1;
	while (*cp != '\0')
		if (*cp++ == '/')
			newfilnam = cp;
	if (filnamcnv)			/* Convert lower case to upper */
		for (cp = newfilnam; *cp != '\0'; cp++)
			*cp = toupper(*cp);
	len = cp - newfilnam;
	printmsg("Requesting %s as %s",filnam, newfilnam);
	n = 0;					/* Initialize message number */
	numtry = 0;				/* Say no tries yet */
	state = 'S';
	while (TRUE)
	{
		if (debug) printf("Getsw 1: State: %c\n",state);
		switch (state)
		{
		case 'S':	state = iinit(); break; /* Try this */
		/* Then this */
		case 'R':	state = ginit(len,newfilnam); break;
		case 'F':	state = rfile(); break;	/* Fetch file name */
		case 'D':	state = rdata(); break;	/* Fetch the data */
		case 'C':	return (TRUE);	/* End it all goodly */
		case 'A':
		default:	return (FALSE);	/* End it all badly */
		}
	}
}

/*
 *	s e r v e r
 *
 *	This is the state table switcher for the server mode
 *
 */

server()
{
	TEXT xinit(), rfile(), rdata(); /* Use these procedures */
	TEXT sinit(), xfile(), sdata(), seof(), sbreak(); /* and these */

	image=iflg;
	pflg=spflg;
	n = 0;					/* Initialize message number */
	numtry = 0;				/* Say no tries yet */
	state = 'X';			/* Begin is the start state */
	while(TRUE)
	{
		if (debug) printf("Server 1: state: %c\n",state);
		switch(state)			/* Do until done */
		{
		case 'X':	state = xinit(); break;	/* Fetch what to do */
		case 'S':	state = sinit(); break;	/* Send init packets */
		case 'T':	state = xfile(); break;	/* maybe open file */
		case 'E':	state = sdata(); break;	/* Send the data */
		case 'Z':	state = seof(); break;
		case 'B':	state = sbreak(); break;
		case 'F':	state = rfile(); break; /* Receive-File */
		case 'D':	state = rdata(); break; /* Receive-Data */
		case 'C':	return (TRUE);			/* Complete state */
		case 'G':	return (FALSE);			/* Finish command */
		case 'A':
		default:	return (debug ? FALSE : TRUE);
		}
	}
}

/*
 *	f i n i s h s w
 *
 *	finishsw is the state table switcher for sending general commands
 * It loops until
 *	either it finishes, or an error is encountered.	The routines called
 *	by finishsw are responsible for changing the state.
 *
 */

finishsw()
{
	TEXT sfinish();
	n = 0;					/* Initialize message number */
	numtry = 0;				/* Say no tries yet */
	state = 'F';			/* Send finish is the start state */
	while(TRUE)				/* Do this as long as necessary */
	{
		if (debug) printf("Finishsw 1: State: %c\n",state);
		switch(state)
		{
		case 'F':	state = sfinish();	break; /* Send-Finish command */
		case 'C':	return (TRUE);		/* Complete */
		case 'A':						/* "Abort" */
		default:	return (FALSE);		/* Unknown, fail */
		}
	}
}

/*
 *	i i n i t
 *
 *	Initiate: send this host's parameters and get other side's back.
 *
 */

TEXT iinit()
{
	int num, len, slen;			/* Packet number, length */

	if (numtry++ > 2) return ('R'); /* If too many tries, give up */
	slen = spar(packet0);		/* Fill up init info packet */

	flushinput();			/* Flush pending input */

	eol = MYEOL;			/* Preset to my eol character */
	spack('I',n,slen,packet0); /* Send an I packet */
	switch(rpack(&len,&num,recpkt)) /* What was the reply? */
	{

	case 'Y':				/* ACK */
		if (n != num)		/* If wrong ACK, stay in S state */
		{					/* Wrong packet number */
	case 'N':				/* NAK, Try it again */
	case FALSE:
			nxi++;
			return (state);
		}
		if (!rpar(recpkt,&len))	/* Get the other side's init data */
			return ('A');		/* error with the packet parameters */


		nxtpkt();
		n = 0;				/* Reset packet number to zero */
		return ('R');		/* OK, switch state to R */

	case 'E':				/* Error packet received */
		prerrpkt(recpkt);	/* Print it out and */
	default:
		 return ('A');		/* Anything else, just "abort" */
	}
 }

/*
 *	s i n i t
 *
 *	Send Initiate: send this host's parameters and get other side's back.
 */

TEXT sinit()
{
	int num, len, slen;			/* Packet number, length */

	if (numtry++ > MAXTRY) return ('A'); /* If too many tries, give up */
	slen = spar(packet0);		/* Fill up init info packet */

	flushinput();			/* Flush pending input */

	eol = MYEOL;			/* Preset to my eol character */
	spack('S',n,slen,packet0); /* Send an S packet */
	switch(rpack(&len,&num,recpkt)) /* What was the reply? */
	{

	case 'Y':				/* ACK */
		if (n != num)		/* If wrong ACK, stay in S state */
		{					/* Wrong packet number */
	case 'N':				/* NAK, Try it again */
	case FALSE:
			nxi++;
			return (state);
		}
		if (!rpar(recpkt,&len))	/* Get the other side's init data */
			return ('A');		/* error with the packet parameters */


		nxtpkt();
		return ('T');		/* OK, switch state to T */

	case 'E':				/* Error packet received */
		prerrpkt(recpkt);	/* Print it out and */
	default:
		 return ('A');		/* Anything else, just "abort" */
	}
 }

/*
 *	r i n i t
 *
 *	Receive Initialization
 */
	
TEXT rinit()
{
	int len, num, slen;			/* Packet length, number */

	if (numtry++ > MAXTRY) return ('A'); /* If too many tries, "abort" */

	switch(rpack(&len,&num,recpkt)) /* Get a packet */
	{
	case 'S':			/* Send-Init packet */
		if (!rpar(recpkt,&len))	/* Get the other side's init data */
			return ('A');		/* error with the packet parameters */
		slen = spar(packet0);	/* Fill up packet with my init info */
		spack('Y',n,slen,packet0); /* ACK with my parameters */
		oldtry = numtry;	/* Save old try count */
		nxtpkt();
		return ('F');		/* Enter File-Receive state */

	case FALSE:			/* Didn't get packet */
		nxi++;
		spack('N',n,0,0);	/* Return a NAK */
		return (state);		/* Keep trying */

	case 'E':			/* Error packet received */
		prerrpkt(recpkt);	/* Print it out and */
	default:
		return ('A');		/* Some other packet type, "abort" */
	}
}
/* pe7mai.c End-of-file */
