/*
 *  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
 *	    David Ragozin, Univ. Washinton
 *	    John Sambrook, Univ. Washington, Bioengineering.
 *
 *  usage:  kermit c [lbe line baud escapechar]		to connect
 *	    kermit s [d..iflb line baud] file ...	to send files
 *	    kermit r [d..iflb line baud]		to receive files
 *
 *  where   c=connect, s=send, r=receive,
 *	    d=debug, i=image mode, f=no filename conversion, l=tty line,
 *	    b=baud rate, e=escape char.
 *
 *  If UCB4X then 
 *
 *  After first c, s, or r enters command mode with  'Kermit: ' prompt
 *
 *	Commands:	c[lbe line baud esc.char] 	to (re)connect
 *	  		s[diflb line baud] file ...	to send file(s)
 *	  		r[diflb line baud]		to receive
 *	  		![cmd]			        for shell escape
 *	  		q				to quit
 *			? or h				to print this list
 *     NOTE:
 *	    In command mode l(ine) or b(aud) parameters for c,s or r 
 *	    need not be specified if unchanged from previous command(s).
 *
 *  endif UCB4X  may work on others but tested only on 4.1 and 4.1c
 */
 
/*
 *  Modification History:
 *  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.
 *
 *  Feb. '84 Multiple command execution for UCB4X by David Ragozin (UW)
 *
 *  Jul. '85 Ported to Data General MV/UX (hosted on AOS/VS) UNIX.
 *
 */

#include <stdio.h>	    /* Standard UNIX definitions */
#include <errno.h>	    /* error definitions */

/* Conditional compilation for different machines/operating systems */
/* One and only one of the following lines should be 1 */

#define UCB4X	    0	    /* Berkeley 4.x UNIX */
#define TOPS_20	    0	    /* TOPS-20 */
#define VAX_VMS	    0	    /* VAX/VMS (not yet impletmented) */
#define IBM_UTS	    0	    /* Amdahl UTS on IBM systems */
#define MVUX	    1	    /* Data General MV/UX */

/* Conditional compilation for the different Unix variants */
/* 0 means don't compile it, nonzero means do */

#if UCB4X
#define V6_LIBS	    0	    /* Dont't use retrofit libraries */
#define NO_FIONREAD 0	    /* We have ioctl(FIONREAD,...) for flushinput() */
#define NO_TANDEM   0	    /* We have TANDEM line discipline (xon/xoff) */
#endif

#if IBM_UTS
#define V6_LIBS	    0	    /* Don't use retrofit libraries */
#define NO_FIONREAD 1	    /* No ioctl(FIONREAD,...) for flushinput() */
#define NO_TANDEM   1	    /* No TANDEM line discipline (xon/xoff) */
#endif

#if MVUX
#define	V6_LIBS	    0	    /* Don't use retrofit libraries */
#define NO_FIONREAD 1	    /* No ioctl(FIONREAD,...) for flushinput() */
#define NO_TANDEM   1       /* No TANDEM line discipline (xon/xoff) */
#define CRMOD	    0	    /* No CRMOD on MV/UX, so ... */
#endif

#if V6_LIBS
#include <retrofit/sgtty.h>
#include <retrofit/signal.h>
#include <retrofit/setjmp.h>
#else
#include <sgtty.h>
#include <signal.h>
#include <setjmp.h>
#endif /* V6_LIBS */

#if MVUX
#include <multitask.h>
#include <termio.h>
#endif

#if NO_TANDEM
#define TANDEM		0	 
#endif

/*
**  Manifest constants.
*/

#define MAXPACK		94		/* maximum packet size */
#define SOH		1		/* start of header */
#define BELL		7		/* ASCII bell */
#define CR		13		/* ASCII carriage return */
#define SP		32		/* ASCII space */
#define DEL		127		/* delete (rubout) */
#define ESCCHR		'^'		/* default escape character */
#define MAXTRY		10		/* times to retry a packet */
#define MYQUOTE		'#'		/* quote character */
#define MYPAD		0		/* number of pad characters needed */
#define MYPCHAR		0		/* padding character */
#define SHELL		"/bin/sh"	/* default shell to use */
#define	TTY		"/dev/tty"
 
#if IBM_UTS
#define MYEOL		'\r'    	/* EOL character for UTS */
#else
#define MYEOL		'\n'    	/* EOL character for others */
#endif

#define MYTIME		10		/* timeout interval */
#define MAXTIM		60		/* maximum timeout interval */
#define MINTIM		2		/* minimum timeout interval */
#define CMDLINE		BUFSIZ		/* maximum command line size */

#define TRUE		-1		/* boolean constants */
#define FALSE	 	0

#if MVUX
#define	 LISTENID	16		/* task id of listener task */
#define  TALKID		17		/* task id of talker task */
#define  LISTENPRI	1		/* priority of listener */
#define  TALKPRI	2		/* priority of talker */
#define  STACK		1024		/* default stack size */
#endif

/*
**  Macro definitions.
*/

#define tochar(ch)	((ch) + ' ')	/* control character to printable */
#define unchar(ch)	((ch) - ' ')	/* printable to control character */
#define ctl(ch)		((ch) ^ 64 )	/* toggle control bit */
#define strip(ch)	((ch) & 0x7f)	/* strip parity (eigth) bit */
#define stty(fd, p)	ioctl(fd, TIOCSETP, p)
#define gtty(fd, p)	ioctl(fd, TIOCGETP, p)

/*
**  Type and structure definitions.
*/

typedef enum _cmdtype
{
	cmd_connect,			/* connect to remote */
	cmd_help,			/* print Kermit help */
	cmd_none,			/* no valid command */
	cmd_quit,			/* exit Kermit */
	cmd_receive,			/* receive files */
	cmd_send,			/* send files */
	cmd_shell			/* UNIX shell command */
} cmdtype;

/*
**  Imported functions.
*/

extern int		fclose();	/* close stream */
extern char *		fgets();	/* read string from stream */
extern FILE *		fopen();	/* open stream */
extern char *		getenv();	/* get environment variable */
extern int		pclose();	/* close pipe from process */
extern FILE *		popen();	/* open pipe to process */
extern char *		strcpy();	/* copy string */

/*
**  Local functions.
*/

static void		bufemp();	/* empty buffer of data */
static int		bufill();	/* get buffer of data */
static int		clkint();	/* clock handler */
static int		closeline();	/* close communication line */
static void		connect();	/* connect function */
static void		defaults();	/* establish default characteristics */
static void		doconnect();	/* execute a connect command */
static void		dohelp();	/* print help menu */
static void		doreceive();	/* execute a receive command */
static void		dosend();	/* execute a send command */
static void		doshell();	/* execute a shell command */
static int		error();	/* print error message */
static void		flushinput();	/* flush input queue */
static int		getcmdline();	/* get next command line */
static int		mapspeed();	/* map baud rate */
static int		openline();	/* open communications line */
static cmdtype		parsecmd();	/* parse command line */
static void		prerrpkt();	/* print error packet */
static void		printmsg();	/* print message */
static int		recsw();	/* receive file state machine */
static int		rdata();	/* receive file data */
static int		rfile();	/* receive file header */
static int		rinit();	/* receive initialization */
static int		rpack();	/* receive a packet */
static void		rpar();		/* get others send-init parameters */
static int		sbreak();	/* send a break */
static int		sdata();	/* send file data */
static int		sendsw();	/* send file state machine */
static int		seof();		/* send eof */
static int		sfile();	/* send file header */
static int		sinit();	/* send initiate */
static int		spack();	/* send packet */
static void		spar();		/* load send-init parameters */
static void		usage();	/* print usage and quit */

#if MVUX
static void		listener();	/* listener sub-task */
static void		talker();	/* talker sub-task */
#endif

/*
**  Exported functions.
*/

int			main();		/* program entry point */

/*
**  Imported data structures.
*/
/* none */

/*
**  Local data structures
*/

static FILE *		crfp;		/* communications read file pointer */
static FILE *		cwfp;		/* communications write file pointer */

static int		lflag;		/* true if line option given */
static int		lopen;		/* true if line is open */
static char		llast[BUFSIZ];	/* last line name */
static char		lname[BUFSIZ];	/* line name */
static FILE *		lrfp;		/* line read file pointer */
static FILE *		lwfp;		/* line write file pointer */

static int		topen;		/* true if terminal is open */
static char		tname[BUFSIZ];	/* terminal name */
static FILE *		trfp;		/* terminal read file pointer */
static FILE *		twfp;		/* terminal write file pointer */

static int		speed;		/* baud rate of communications line */

static int		size;		/* size of present data */
static int		rpsiz;		/* maximum receive packet size */
static int		spsiz;		/* maximum send packet size */
static int		pad;		/* how much padding to send */
static int		timint;		/* timeout for foreign host on sends */
static int		n;		/* packet number */
static int		numtry;		/* number of retries for this packet */
static int		oldtry;		/* times previous packet retried */
static int		terminate;	/* termination flag for connect */
static int		remote;		/* -1 means we're a remote kermit */
static int		image;		/* -1 means 8 bit mode */
static int		debug;		/* debugging level */
static int		convert;	/* do file name case conversion */

static int		file;		/* current file number */
static int		files;		/* number of files to send */
static char *		filename;	/* current file name */
static char *		filenames[256];	/* pointers to file names */

static char		state;		/* present state of the automaton */
static char		padchar;	/* padding character */
static char		eol;		/* end of line character to send */
static char		escchr;		/* connect command escape char */
static char		quote;		/* quote character in incoming data */
static char		recpkt[MAXPACK];/* receive packet buffer */
static char		packet[MAXPACK];/* packet buffer */
static char		shargs[BUFSIZ];	/* arguments to a shell command */

static FILE *		fp;		/* file pointer for current disk file */
static jmp_buf		env;		/* jump buffer for timeouts */

/*
 *  m a i n
 *
 *  Main routine - Initialize and enter command loop: parse command and options,
 *  set up the tty lines, dispatch to the appropriate routine and get next
 *  command.
 */

int main(argc, argv)
int argc;					/* argument count */
char * argv[];					/* argument vector */
{
	int		c;			/* current character */
	int		ac;			/* argument count */
	char *		av[256];		/* argument vector */
	char **		avp;			/* argument vector pointer */
	cmdtype		cmd;			/* command */
	char		cmdline[BUFSIZ];	/* command line */

/*
**	Set up so that first iteration uses the command line as
**	the line to parse.  Establish the default values for 
**	certain ``global'' variables.
*/
	ac = argc - 1;
	avp = ++argv;
	defaults();
/*
**	Top level loop.  This loop obtains until the quit command is
**	received.
*/
	cmd = cmd_none;
	while (cmd != cmd_quit)
	{
/*
**	    Parse and execute the current command line.
*/
	    cmd = parsecmd(ac, avp);
/*
**	    In order to support the transfer of files, the communications
**	    lines must be opened and closed in a certain order.  The code
**	    that follows is an attempt to do just that.
*/
	    if (cmd == cmd_connect)
	    {
		remote = 0;			/* connect is always local */
		if (lflag)			/* if line specified */
		{
		    if (lopen)			/* if a line is open */
		    {
			if (strcmp(lname, llast) != 0)
			{
			    closeline(lrfp, lwfp);
			    lopen = 0;
			    if (openline(lname, &lrfp, &lwfp) == -1)
				cmd = cmd_none;
			    else
			    {
				strcpy(lname, llast);
				lopen = 1;
			    }
			}
		    }
		    else
		    {
			if (openline(lname, &lrfp, &lwfp) == -1)
			{
			    lopen = 0;
			    cmd = cmd_none;
			}
			else
			{
			    strcpy(lname, llast);
			    lopen = 1;
			}
		    }
		}
		else if (lopen)			/* if line already open */
		    printmsg("Connection continued on line %s.\n", lname);
		else				/* no line open; none given */
		{
		    error("No line specified for connect command.\n");
		    cmd = cmd_none;
		}
	    }
	    else if (cmd == cmd_send || cmd == cmd_receive)
	    {
		if (lflag)			/* if line specified */
		{
		    remote = 0;			/* local */
		    if (lopen)			/* if a line is open */
		    {
			if (strcmp(lname, llast) != 0)
			{
			    closeline(lrfp, lwfp);
			    lopen = 0;
			    if (openline(lname, &lrfp, &lwfp) == -1)
				cmd = cmd_none;
			    else
			    {
				strcpy(lname, llast);
				lopen = 1;
			    }
			}
		    }
		    else
		    {
			if (openline(lname, &lrfp, &lwfp) == -1)
			{
			    lopen = 0;
			    cmd = cmd_none;
			}
			else
			{
			    strcpy(lname, llast);
			    lopen = 1;
			}
		    }
		    crfp = lrfp;
		    cwfp = lwfp;
		}
		else if (lopen)			/* open line; use it */
		{
		    remote = 0;
		    crfp = lrfp;
		    cwfp = cwfp;
		}
		else		/* no open line, no line given; use tty */
		{
		    remote = 1;			/* remote */
		    if (topen == 0)		/* if tty not open */
		    {
			if (openline(tname, &trfp, &twfp) == -1)
			    cmd = cmd_none;
		    }
		    crfp = trfp;
		    cwfp = twfp;
		}
	    }
/*
**	    Dispatch to the appropriate execution function depending
**	    on the value of cmd.
*/
	    switch (cmd)
	    {
		case cmd_connect:
		    doconnect();
		    break;
		
		case cmd_receive:
		    doreceive();
		    break;

		case cmd_send:
		    dosend();
		    break;

		case cmd_shell:
		    doshell();
		    break;
		 
		case cmd_help:
		    dohelp();
		    break;
		
		case cmd_quit:
		    break;
	    }
/*
**	    If the last command was not a quit command then get the next
**	    command line from the user.
*/
	    if (cmd != cmd_quit)
		ac = getcmdline(avp = av, cmdline);
	}
}


/*
 *	b u f e m p
 *
 *  Put data from an incoming packet into a file.
 */

static void bufemp(buffer,len)
char  buffer[];				/* Buffer */
int   len;				/* Length */
{
    int i;				/* Counter */
    char t;				/* Character holder */

    for (i=0; i<len; i++)		/* Loop thru the data field */
    {
	t = buffer[i];			/* Get character */
	if (t == MYQUOTE)		/* Control quote? */
	{				/* Yes */
	    t = buffer[++i];		/* Get the quoted character */
	    if ((t & 0177) != MYQUOTE)	/* Low order bits match quote char? */
		t = ctl(t);		/* No, uncontrollify it */
	}
	if (t==CR && !image)		/* Don't pass CR if in image mode */
	    continue;

	putc(t,fp);
    }
}


/*
 *  b u f i l l
 *
 *  Get a bufferful of data from the file that's being sent.
 *  Only control-quoting is done; 8-bit & repeat count prefixes are
 *  not handled.
 */

static int bufill(buffer)
char buffer[];				/* Buffer */
{
    int i,				/* Loop index */
    	t;				/* Char read from file */
    char t7;				/* 7-bit version of above */

    i = 0;				/* Init data buffer pointer */
    while((t = getc(fp)) != EOF)	/* Get the next character */
    {
	t7 = t & 0177;			/* Get low order 7 bits */

	if (t7 < SP || t7==DEL || t7==quote) /* Does this char require */
	{				    /* special handling? */
	    if (t=='\n' && !image)
	    {				/* Do LF->CRLF mapping if !image */
		buffer[i++] = quote;
		buffer[i++] = ctl('\r');
	    }
	    buffer[i++] = quote;	/* Quote the character */
	    if (t7 != quote)
	    {
		t = ctl(t);		/* and uncontrolify */
		t7 = ctl(t7);
	    }
	}
	if (image)
	    buffer[i++] = t;		/* Deposit the character itself */
	else
	    buffer[i++] = t7;

	if (i >= spsiz-8) return(i);	/* Check length */
    }
    if (i==0) return(EOF);		/* Wind up here only on EOF */
    return(i);				/* Handle partial buffer */
}


/*
 *  c l k i n t
 *
 *  Clock interrupt handler.
 */

static int clkint()			/* Timer interrupt handler */
{
    longjmp(env,TRUE);			/* Tell rpack to give up */
}


/*
 *  c l o s e l i n e
 *  
 *  Function closeline() closes the given file pointers.  If an 
 *  error is detected then a diagnostic is written and -1 is returned.
 *  If no error is detected then 0 is returned.
 */

static int closeline(rfp, wfp)
FILE * rfp;					/* read file pointer */
FILE * wfp;					/* write file pointer */
{

	if (fclose(rfp) == -1)
	{
	    error("Can't close read file pointer.");
	    error("Error code: %d.", errno);
	    return -1;
	}
	if (fclose(wfp) == -1)
	{
	    error("Can't close write file pointer.");
	    error("Error code: %d.", errno);
	    return -1;
	}
	return 0;
}


/*
 *  c o n n e c t
 *
 *  Establish a virtual terminal connection with the remote host.
 */

static void connect()
{

/*
**	Under MV/UX a true fork() call does not exist; however, we can
**	create multiple asynchronous tasks with a program.  We create
**	two sub-tasks, ``listener'' and ``talker'' to handle the data
**	communication.  For the main task to regain control one or both
**	of the tasks must set the terminate flag; therefore it is cleared
**	before either task is initiated.
*/
	terminate = 0;
	if (mtask(listener, STACK, LISTENID, LISTENPRI) != 0)
	{
	    error("Can't initiate listener task.");
	    error("Error code is: %d.\n", errno);
	    return;
	}
	if (mtask(talker, STACK, TALKID, TALKPRI) != 0)
	{
	    error("Can't initiate talker task.");
	    error("Error code is: %d.\n", errno);
	    return;
	}
/*
**	After creating the sub-tasks the main task (this thread)
**	enters the following loop.  The main task runs once every
**	two seconds; on each iteration it checks the ``terminate''
**	flag.  When it finds the terminate flag set it pops out of
**	the loop and terminates the ``listener'' and ``talker''
**	sub-tasks.
*/
	while (terminate == 0)
	    sleep(2);
	if (midkill(LISTENID) == -1)
	{
	    error("Can't terminate listener task.");
	    return;
	}
	if (midkill(TALKID) == -1)
	{
	    error("Can't terminate talker task.");
	    return;
	}
}


/*
 *	d e f a u l t s 
 *
 *	Establish default values for the myriad global variables.
 *
 */

static void defaults()
{

#if UCB4X | MVUX			/* Default to 7-bit masking, CRLF */
	image = FALSE;			/* translation and filename case */
	convert++;			/* conversion for UNIX systems */
#else
	image = TRUE;			/* Default to no processing for */
	convert = FALSE;		/* non-UNIX systems */
#endif
	eol = CR;			/* eol for outgoing packets */
	quote = '#';			/* for quoting control characters */
	pad = 0;			/* no padding */
	padchar = 0;			/* pad with ASCII nul */
	escchr = ESCCHR;		/* default escape character */
	strcpy(lname, "");		/* no line name */
	strcpy(llast, "");		/* no default either */
	strcpy(tname, TTY);		/* terminal name */
}


/*
 *  d o c o n n e c t 
 *
 *  Top level control for connect command.
 *
 */

static void doconnect()
{

/*
**	Open the communications line to the terminal.  This line can be
**	closed when the connect command completes.
*/
	if (openline(tname, &trfp, &twfp) == -1)
	    return;
/*
**	Inform the user that the connection is established and how
**	to return.  Then invoke connect() to actually manage the
**	connection.
*/
	printf("Connected.  Use %cc to return to command level.\n", escchr);
	connect();		
	printf("Disconnected.\n");
/*
**	Close the line to the terminal.
*/
	(void) closeline(trfp, twfp);
}

/*
 *  d o h e l p
 *
 *  Print a simple help menu on the terminal.
 *
 */

static void dohelp()
{

/*
**  Print a help screen for the user.
*/
    printf("\nKermit Help:\n\n");
    printf("When you receive the prompt 'Kermit: ' you are expected to\n");
    printf("reply with a command.  The following commands are supported:\n\n");
    printf("\tc[lbe line baud escape]             - connect to remote.\n");
    printf("\ts[diflb line baud escape] file(s)   - send file(s).\n");
    printf("\tr[diflb line baud escape]           - receive file(s).\n");
    printf("\t! [command]                         - shell escape.\n");
    printf("\t?                                   - this help screen.\n");
    printf("\tq                                   - quit.\n\n");
#if 0
    printf("Commands:	c[lbe line baud esc.char] 	(to reconnect)\n");
    printf("or:		s[diflb line baud] file ...	(to send file(s))\n");
    printf("or:		r[diflb line baud]		(to receive )\n");
    printf("or:		![cmd]			        (for shell escape)\n");
    printf("or:		q				(TO QUIT)\n");
#endif
#if 0
    printf("Usage: kermit c[le line esc.char]		 (connect mode)\n");
    printf("or:	   kermit s[difl line] file ...		 (send mode)\n");
    printf("or:	   kermit r[difl line]			 (receive mode)\n");
    exit(1);
#endif
}


/*
 *  d o r e c e i v e
 *
 *  Top level control for receive command.
 *
 */

static void doreceive()
{

/*
**	Do the receive and print the resulting status.
*/
	if (recsw() == FALSE)
	    printmsg("Receive failed.");
	else
	    printmsg("Receive completed.");
}


/*
 *  d o s e n d
 *
 *  Top level control for send command.
 *
 */

static void dosend()
{

/*
**	Get the filename and do the send.
*/
	filename = filenames[file++];
	fp = NULL;		/* Indicate no file open yet */
	if (sendsw() == FALSE)	/* Send the file(s) */
	    printmsg("Send failed.");/* Report failure */
	else			/*  or */
	    printmsg("Done.");	/* success */
}


/*
 *  d o s h e l l
 *
 *  Function doshell() runs a shell command for the user.
 *
 */

static void doshell()
{
	int	args;			/* true if args to shell */
	char *	s;			/* work pointer */
	int	(* savehup)();		/* old SIGHUP value */	
	int	(* saveint)();		/* old SIGINT value */
	int	(* savequit)();		/* old SIGQUIT value */
	char 	path[BUFSIZ];		/* pathname to shell */
	char *	base;			/* pointer to base name of shell */
	int	code;			/* return code from wait() */
	int	pid;			/* child process id */

/*
**	Get the shell to use.  The full pathname to the shell is kept in
**	path; a pointer, base, is used to point at the basename of the
**	shell.  Finally, scan the shargs string to decide if any arguments
**	were given.
*/
	if (getenv("SHELL") != NULL)
	    strcpy(path, getenv("SHELL"));
	else
	    strcpy(path, SHELL);
	for (s = base = path; *s; )
	    if (*s++ == '/')
		base = s;
	for (s = shargs, args = 0; *s; s++)
	    if (*s != ' ' && *s != '\t')
		args = 1;
/*
**	Save the current values of SIGHUP, SIGINT and SIGQUIT.
**	Then execute the shell command line.  Finally, restore
**	signal handling.
*/
	savehup = signal(SIGHUP, SIG_IGN);
	saveint = signal(SIGINT, SIG_IGN);
	savequit = signal(SIGQUIT, SIG_IGN);
/*
**	The child does an execl() call.  If the call fails then
**	a diagnostic is written and the child exits().
*/
	if ((pid = vfork()) == 0)		/* child */
	{
	    if (args)
		execl(path, base, "-c", shargs, 0);
	    else
		execl(path, base, 0);
	    error("doshell: execl() has failed.");
	    _exit(1);
	}
/*
**	The parent waits for the child to terminate, then restores
**	signals.
*/
	else					/* parent */
	{
	    while (wait(&code) != pid)
		;
	    (void) signal(SIGHUP, savehup);
	    (void) signal(SIGINT, saveint);
	    (void) signal(SIGQUIT, savequit);
/*
**	    Clean up the argument list for the next pass, if any.  
**	    Write a new-line to stdout so that Kermit prompt is positioned
**	    correctly.  
*/
	    strcpy(shargs, "");
	    fputs("\n", stdout);
	}
}


/*
 *  e r r o r
 *
 *  Print error message.
 *
 *  If local, print error message with printmsg.
 *  If remote, send an error packet with the message.
 */

/*VARARGS1*/
static int error(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
    char msg[80];
    int len;

    if (remote)
    {
	sprintf(msg,fmt,a1,a2,a3,a4,a5); /* Make it a string */
	len = strlen(msg);
	spack('E',n,len,msg);		/* Send the error packet */
    }
    else
	printmsg(fmt, a1, a2, a3, a4, a5);
}


/*
 *  f l u s h i n p u t
 *
 *  Dump all pending input to clear stacked up NACK's.
 *  (Implemented only for Berkeley Unix at this time).
 */

#if UCB4X&(~NO_FIONREAD)
static void flushinput()
{
    long int count;			/* Number of bytes ready to read */
    long int i;				/* Number of bytes to read in loop */

    ioctl(ttyfd, FIONREAD, &count);	/* See how many bytes pending read */
    if (!count) return;			/* If zero, then no input to flush */

    while (count)			/* Loop till all are flushed */
    {
	i = (count<sizeof(recpkt)) ?	/* Read min of count and size of */
	    count : sizeof(recpkt);	/*  the read buffer */
	read(ttyfd, recpkt, i);		/* Read a bunch */
	count -= i;			/* Subtract from amount to read */
    }
}
#else
static void flushinput()		/* Null version for non-Berkeley Unix */
{}
#endif /* UCB4X&(~FIONREAD) */


/*
 *  g e t c m d l i n e
 *
 *  This function gets the next Kermit command line from the users
 *  terminal.  The Kermit prompt is written and a single line is 
 *  read.  The line is then expanded by 'echo'ing it through a shell.
 *  Once expanded, the arguments, ac and av, are set up as if by
 *  the C start-off routines.  This function returns the number of
 *  arguments on the command line.
 */
 
static int getcmdline(av, cmdline)
char * av[];					/* argument vector */
char * cmdline;					/* returned command line */
{
	int		ac;			/* argument count */
	char *		s;			/* work pointer */
	char 		buf[BUFSIZ];		/* work buffer */
	int		length;			/* command length */
	FILE *		pfp;			/* for popen() / pclose() */

/*
**	Print the Kermit prompt and read the line typed by the user.
**	If the users responds with ^D then hand-craft a quit command.
**	Clip off the delimeter, usually \n.
*/
	printf("Kermit: ");
	if (fgets(cmdline, CMDLINE, stdin) == NULL)
	{
	    printf("[EOT]\n");
	    strcpy(cmdline, "q\n");
	}
	if ((length = strlen(s = cmdline)) > 0)
	    cmdline[length - 1] = 0;
/*
**	Hand-craft a command line to expand the command line.  Then 
**	use popen() / pclose() to get the results of the expansion.
*/
#if 0
	sprintf(buf, "/bin/echo %s", cmdline);
	if ((pfp = popen(buf, "r")) == NULL)
	{
	    perror("getcmdline: popen");
	    exit(1);
	}
	if ((s = fgets(cmdline, CMDLINE, pfp)) == NULL)
	{
	    perror("getcmdline: fgets");
	    exit(1);
	}
	if (pclose(pfp) == -1)
	{
	    perror("getcmdline: pclose");
	    exit(1);
	}
#endif
/*
**	Finally, set up ac and av.  Note that each argument in cmdline
**	is terminated with a NULL so that it looks just like a 'real'
**	argv from C runtimes.
*/
	while (*s == ' ' || *s == '\t')			/* skip whitespace */
	    s++;
	for (av[ac = 0] = NULL; *s; ac++)
	{
	    av[ac] = s;					/* point to argument */
	    while (*s && *s != ' ' && *s != '\t')	/* skip argument body */
		s++;
	    if (*s)					/* if more arguments */
		*s++ = 0;
	    while (*s == ' ' || *s == '\t')		/* skip whitespace */
		s++;
	}
	av[ac] = NULL;
	return ac;
}


#if MVUX
/*
 *  l i s t e n e r
 *
 *  Special MV/UX sub-task to listen for characters from the remote system.
 */

static void listener()
{
    int		cc;				/* I/O buffer */

    while (1)
    {
	cc = fgetc(lrfp);
	fputc(cc, twfp);
    }
}
#endif


/*
 *  m a p s p e e d
 *
 *  Map an integer baud rate (300, 1200, etc.) to the appropriate manifest
 *  value (B300, B1200, etc.).  Returns manifest or -1 for illegal baud.
 */

static int mapspeed(baud)
int baud;					/* input baud rate */
{
	int	manifest;			/* manifest value */

	switch (baud)
	{
	    case 110:	
		manifest = B110;
		break;

	    case 150:
		manifest = B150;
		break;

	    case 300:
		manifest = B300;
		break;
	
	    case 1200:
		manifest = B1200;
		break;

	    case 2400:
		manifest = B2400;
		break;

	    case 4800:
		manifest = B4800;
		break;

	    case 9600:
		manifest = B9600;
		break;

	    default:			/* error case */
		manifest = -1;
		break;
	}
	return manifest;
}


/*
 *  o p e n l i n e 
 *  
 *  Function openline() opens read and write file pointers to the 
 *  given file, assumed to be a communications line.  If an error
 *  is detected then a diagnostic is written and -1 is returned.
 *  On no error, 0 is returned.
 *
 */

static int openline(name, rfp, wfp)
char * name;					/* device name to open */
FILE ** rfp;					/* read file pointer */
FILE ** wfp;					/* write file pointer */
{
#if MVUX
	char *		rmode = "j";		/* DG binary read */
	char *		wmode = "k";		/* DG binary write */
#else
	char *		rmode = "r";		/* normal read */
	char *		wmode = "w";		/* normal write */
#endif

	if ((*rfp = fopen(name, rmode)) == NULL)
	{
	    error("Can't open %s for mode %s.", name, rmode);
	    error("Error code: %d.", errno);
	    return -1;
	}
	if ((*wfp = fopen(name, wmode)) == NULL)
	{
	    error("Can't open %s for mode %s.", name, wmode);
	    error("Error code: %d.", errno);
	    return -1;
	}
	setbuf(*rfp, NULL);
	setbuf(*wfp, NULL);
	return 0;
}


/*
 *  p a r s e c m d 
 *
 *  Function parsecmd() does a simple minded parse of the given command
 *  line.  While it would be nice to conform the UNIX standard and use
 *  getopt() to parse this is not done.  The change would like cause more
 *  conflict than good.  Sigh.  
 *
 *  If the command line does not contain a Kermit command letter this 
 *  function returns ``cmd_none''.
 *
 */

static cmdtype parsecmd(ac, av)
int ac;						/* argument count */
char * av[];					/* argument vector */
{
	int		c;			/* current character */
	cmdtype		cmd;			/* current command */
	int		index;			/* current argument index */
	char *		s;			/* work pointer */

/*
**	The default Kermit standard for command lines requires that the
**	first argument begin with a command, then optional arguments.
**	Check that this is the case, returning cmd_none in the case of
**	an empty command line.
*/
	cmd = cmd_none;
	if (ac == 0)				/* empty command line */
	    return cmd;
/*
**	Parse the first argument.  This is the command plus any of the
**	options.
*/
	remote = 0;				/* default to local */
	s = av[index = 0];
	switch (c = *s++)
	{
	    case 'c':				/* connect command */
		cmd = cmd_connect;
		break;

	    case 'r':				/* receive command */
		cmd = cmd_receive;
		remote = 1;			/* default to remote */
		break;

	    case 's':				/* send command */
		cmd = cmd_send;	
		remote = 1;			/* default to remote */
		break;

	    case '!':				/* shell command */
		cmd = cmd_shell;
		while (av[++index] != NULL)
		{
		    strcat(shargs, " ");
		    strcat(shargs, av[index]);
		}
		break;

	    case '?':				/* help command */
		cmd = cmd_help;	
		break;

	    case 'q':				/* quit command */
		cmd = cmd_quit;
		break;

	    default:				/* unknown command */
		error("Not a valid command: '%c'.  Use '?' for help.", c);
		return cmd_none;
		break;
	}
/*
**	If the command was one of [c,r,s] then continue parsing for options.
**	Otherwise return immediately to caller.
*/
	if (cmd == cmd_connect || cmd == cmd_receive || cmd == cmd_send)
	{
	    while (*s)			/* while more arguments */
	    {
		switch (c = *s++)
		{
		    case 'd':
			debug++;	/* debug */
			break;

		    case 'i':		/* image mode */
			image = 1;
			break;

		    case 'f':		/* convert file names */
			convert = 1;
			break;

		    case 'b':		/* baud rate (speed) */
			if (av[++index] == NULL)
			{
			    error("Option 'b' requires baud rate.");
			    return cmd_none;
			}
			speed = atoi(av[index]);
			break;

		    case 'e':
			if (av[++index] == NULL)
			{
			    error("Option 'e' requires escape character.");
			    return cmd_none;
			}
			escchr = av[index][0];
			break;

		    case 'l':		/* line */
			lflag = 1;
			remote = 0;
			if (av[++index] == NULL)
			{
			    error("Option 'l' requires a line name.");
			    return cmd_none;
			}
			strcpy(lname, av[index]);
			strcpy(llast, av[index]);
			break;

		    default:
			error("Not a valid option: '%c'.  Use ? for help.", c);
			return cmd_help;
			break;
		}
	    }
/*
**	    Now, if the command was cmd_send, pick up all files.
*/
	    if (cmd == cmd_send)
	    {
		for (files = 0, index++; av[index] != NULL; files++, index++)
		    filenames[files] = av[index];
		if (files == 0)
		{
		    error("No files specified for send command.");
		    return cmd_help;
		}
	    }
	}
	return cmd;
}


/*
 *  p r e r r p k t
 *
 *  Print contents of error packet received from remote host.
 */

static void prerrpkt(msg)
char * msg;
{
    printf("Kermit aborting with following error from remote host:\n%s\n",msg);
}


/*
 *  p r i n t m s g
 *
 *  Print error message on standard output if not remote.
 */

/*VARARGS1*/
static void printmsg(fmt, a1, a2, a3, a4, a5)
char * fmt;
{
    if (! remote)
    {
	printf(fmt, a1, a2, a3, a4, a5);
	printf("\n");
	fflush(stdout);			   /* force output  (UTS needs it) */
    }
}


/*
 *  r d a t a
 *
 *  Receive Data
 */

static int rdata()
{
    int num, len;			/* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* "abort" if too many tries */

    switch(rpack(&len,&num,packet))	/* Get packet */
    {
	case 'D':			/* Got Data packet */
	    if (num != n)		/* Right packet? */
	    {				/* No */
		if (oldtry++ > MAXTRY)
		    return('A');	/* If too many tries, abort */
		if (num == ((n==0) ? 63:n-1)) /* Else check packet number */
		{			/* Previous packet again? */
		    spack('Y',num,6,packet); /* Yes, re-ACK it */
		    numtry = 0;		/* Reset try counter */
		    return(state);	/* Don't write out data! */
		}
		else return('A');	/* sorry, wrong number */
	    }
	    /* Got data with right packet number */
	    bufemp(packet,len);		/* Write the data to the file */
	    spack('Y',n,0,0);		/* Acknowledge the packet */
	    oldtry = numtry;		/* Reset the try counters */
	    numtry = 0;			/* ... */
	    n = (n+1)%64;		/* Bump packet number, mod 64 */
	    return('D');		/* Remain in data state */

	case 'F':			/* Got a File Header */
	    if (oldtry++ > MAXTRY)
		return('A');		/* If too many tries, "abort" */
	    if (num == ((n==0) ? 63:n-1)) /* Else check packet number */
	    {				/* It was the previous one */
		spack('Y',num,0,0);	/* ACK it again */
		numtry = 0;		/* Reset try counter */
		return(state);		/* Stay in Data state */
	    }
	    else return('A');		/* Not previous packet, "abort" */

	case 'Z':			/* End-Of-File */
	    if (num != n) return('A');	/* Must have right packet number */
	    spack('Y',n,0,0);		/* OK, ACK it. */
	    fclose(fp);			/* Close the file */
	    n = (n+1)%64;		/* Bump packet number */
	    return('F');		/* Go back to Receive File state */

 	case 'E':			/* Error packet received */
 	    prerrpkt(recpkt);		/* Print it out and */
 	    return('A');		/* abort */

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

	default:     return('A');	/* Some other packet, "abort" */
    }
}


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

static int recsw()
{

    state = 'R';			/* Receive-Init is the start state */
    n = 0;				/* Initialize message number */
    numtry = 0;				/* Say no tries yet */

    while(TRUE)
    {
	if (debug) printf(" recsw 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':	return(FALSE);		/* "Abort" state */
	}
    }
}


/*
 *  r f i l e
 *
 *  Receive File Header
 */

static int rfile()
{
    int num, len;			/* Packet number, length */
    char filnam1[50];			/* Holds the converted file name */

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

    switch(rpack(&len,&num,packet))	/* Get a packet */
    {
	case 'S':			/* Send-Init, maybe our ACK lost */
	    if (oldtry++ > MAXTRY) return('A'); /* If too many tries "abort" */
	    if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */
	    {				/* Yes, ACK it again with  */
		spar(packet);		/* our Send-Init parameters */
		spack('Y',num,6,packet);
		numtry = 0;		/* Reset try counter */
		return(state);		/* Stay in this state */
	    }
	    else return('A');		/* Not previous packet, "abort" */

	case 'Z':			/* End-Of-File */
	    if (oldtry++ > MAXTRY) return('A');
	    if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */
	    {				/* Yes, ACK it again. */
		spack('Y',num,0,0);
		numtry = 0;
		return(state);		/* Stay in this state */
	    }
	    else return('A');		/* Not previous packet, "abort" */

	case 'F':			/* File Header (just what we want) */
	    if (num != n) return('A');	/* The packet number must be right */
	    strcpy(filnam1, packet);	/* Copy the file name */

	    if (convert)		/* Convert upper case to lower */
		for (filename=filnam1; *filename != '\0'; filename++)
		    if (*filename >= 'A' && *filename <= 'Z')
			*filename |= 040;

	    if ((fp=fopen(filnam1,"w"))==NULL) /* Try to open a new file */
	    {
		error("cannot create %s",filnam1); /* Give up if can't */
		return('A');
	    }
	    else			/* OK, give message */
		printmsg("Receiving %s as %s",packet,filnam1);

	    spack('Y',n,0,0);		/* Acknowledge the file header */
	    oldtry = numtry;		/* Reset try counters */
	    numtry = 0;			/* ... */
	    n = (n+1)%64;		/* Bump packet number, mod 64 */
	    return('D');		/* Switch to Data state */

	case 'B':			/* Break transmission (EOT) */
	    if (num != n) return ('A'); /* Need right packet number here */
	    spack('Y',n,0,0);		/* Say OK */
	    return('C');		/* Go to complete state */

 	case 'E':			/* Error packet received */
 	    prerrpkt(recpkt);		/* Print it out and */
 	    return('A');		/* abort */

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

	default:    return ('A');	/* Some other packet, "abort" */
    }
}


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

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

    switch(rpack(&len,&num,packet))	/* Get a packet */
    {
	case 'S':			/* Send-Init */
	    rpar(packet);		/* Get the other side's init data */
	    spar(packet);		/* Fill up packet with my init info */
	    spack('Y',n,6,packet);	/* ACK with my parameters */
	    oldtry = numtry;		/* Save old try count */
	    numtry = 0;			/* Start a new counter */
	    n = (n+1)%64;		/* Bump packet number, mod 64 */
	    return('F');		/* Enter File-Receive state */

 	case 'E':			/* Error packet received */
 	    prerrpkt(recpkt);		/* Print it out and */
 	    return('A');		/* abort */

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

	default:     return('A');	/* Some other packet type, "abort" */
    }
}


/*
 *  r p a c k
 *
 *  Read a Packet
 */

static int rpack(len,num,data)
int *len, *num;				/* Packet length, number */
char *data;				/* Packet data */
{
    int i, done;			/* Data character number, loop exit */
     char t,				/* Current input character */
 	type,				/* Packet type */
 	cchksum,			/* Our (computed) checksum */
 	rchksum;			/* Checksum received from other host */
 
#if UCB4X | MVUX			/* TOPS-20 can't handle timeouts... */
    if (setjmp(env)) return FALSE;	/* Timed out, fail */
    signal(SIGALRM,clkint);		/* Setup the timeout */
    if ((timint > MAXTIM) || (timint < MINTIM)) timint = MYTIME;
    alarm(timint);
#endif /* UCB4X */

    while (t != SOH)			/* Wait for packet header */
    {
	t = fgetc(crfp);
	t &= 0177;			/* Handle parity */
    }

    done = FALSE;			/* Got SOH, init loop */
    while (!done)			/* Loop to get a packet */
    {
	t = fgetc(crfp);		/* Get Character */
	if (!image) t &= 0177;		/* Handle parity */
	if (t == SOH) continue;		/* Resynchronize if SOH */
	cchksum = t;			/* Start the checksum */
	*len = unchar(t)-3;		/* Character count */

	t = fgetc(crfp);		/* Get character */
	if (!image) t &= 0177;		/* Handle parity */
	if (t == SOH) continue;		/* Resynchronize if SOH */
	cchksum = cchksum + t;		/* Update checksum */
	*num = unchar(t);		/* Packet number */

	t = fgetc(crfp);		/* Get character */
	if (!image) t &= 0177;		/* Handle parity */
	if (t == SOH) continue;		/* Resynchronize if SOH */
	cchksum = cchksum + t;		/* Update checksum */
	type = t;			/* Packet type */

	for (i=0; i<*len; i++)		/* The data itself, if any */
	{				/* Loop for character count */
	    t = fgetc(crfp);		/* Get character */
	    if (!image) t &= 0177;	/* Handle parity */
	    if (t == SOH) continue;	/* Resynch if SOH */
	    cchksum = cchksum + t;	/* Update checksum */
	    data[i] = t;		/* Put it in the data buffer */
	}
	data[*len] = 0;			/* Mark the end of the data */

	t = fgetc(crfp);		/* Get last character (checksum) */
	rchksum = unchar(t);		/* Convert to numeric */
	t = fgetc(crfp);		/* Get EOL character and toss it */
	if (!image) t &= 0177;		/* Handle parity */
	if (t == SOH) continue;		/* Resynchronize if SOH */
	done = TRUE;			/* Got checksum, done */
    }

#if UCB4X | MVUX
    alarm(0);				/* Disable the timer interrupt */
#endif

    if (debug>1)			/* Display incoming packet */
    {
	if (data != NULL)
	    data[*len] = '\0';		/* Null-terminate data to print it */
	printf("  rpack type: %c\n",type);
	printf("	 num:  %d\n",*num);
	printf("	 len:  %d\n",*len);
	if (data != NULL)
	    printf("	    data: \"%s\"\n",data);
    }
					/* Fold in bits 7,8 to compute */
    cchksum = (((cchksum&0300) >> 6)+cchksum)&077; /* final checksum */

    if (cchksum != rchksum) return(FALSE);

    return(type);			/* All OK, return packet type */
}


/*  r p a r
 *
 *  Get the other host's send-init parameters
 *
 */

static void rpar(data)
char data[];
{
    spsiz = unchar(data[0]);		/* Maximum send packet size */
    timint = unchar(data[1]);		/* When I should time out */
    pad = unchar(data[2]);		/* Number of pads to send */
    padchar = ctl(data[3]);		/* Padding character to send */
    eol = unchar(data[4]);		/* EOL character I must send */
    quote = data[5];			/* Incoming data quote character */
}
 

/*
 *  s b r e a k
 *
 *  Send Break (EOT)
 */

static int sbreak()
{
    int num, len;			/* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* If too many tries "abort" */

    spack('B',n,0,packet);		/* Send a B packet */
    switch (rpack(&len,&num,recpkt))	/* What was the reply? */
    {
	case 'N':			/* NAK, just stay in this state, */
	    num = (--num<0 ? 63:num);	/* unless NAK for previous packet, */
	    if (n != num)		/* which is just like an ACK for */
		return(state);		/* this packet so fall thru to... */

	case 'Y':			/* ACK */
	    if (n != num) return(state); /* If wrong ACK, fail */
	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* and bump packet count */
	    return('C');		/* Switch state to Complete */

 	case 'E':			/* Error packet received */
 	    prerrpkt(recpkt);		/* Print it out and */
 	    return('A');		/* abort */

	case FALSE: return(state);	/* Receive failure, stay in B */

	default:    return ('A');	/* Other, "abort" */
   }
}


/*
 *  s d a t a
 *
 *  Send File Data
 */

static int sdata()
{
    int num, len;			/* Packet number, length */

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

    spack('D',n,size,packet);		/* Send a D packet */
    switch(rpack(&len,&num,recpkt))	/* What was the reply? */
    {		    
	case 'N':			/* NAK, just stay in this state, */
	    num = (--num<0 ? 63:num);	/* unless it's NAK for next packet */
	    if (n != num)		/* which is just like an ACK for */
		return(state);		/* this packet so fall thru to... */
		
	case 'Y':			/* ACK */
	    if (n != num) return(state); /* If wrong ACK, fail */
	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* Bump packet count */
	    if ((size = bufill(packet)) == EOF) /* Get data from file */
		return('Z');		/* If EOF set state to that */
	    return('D');		/* Got data, stay in state D */

 	case 'E':			/* Error packet received */
 	    prerrpkt(recpkt);		/* Print it out and */
 	    return('A');		/* abort */

	case FALSE: return(state);	/* Receive failure, stay in D */

	default:    return('A');	/* Anything else, "abort" */
    }
}


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

static int sendsw()
{

    state = 'S';			/* Send initiate is the start state */
    n = 0;				/* Initialize message number */
    numtry = 0;				/* Say no tries yet */
    while(TRUE)				/* Do this as long as necessary */
    {
	if (debug) printf("sendsw state: %c\n",state);
	switch(state)
	{
	    case 'S':	state = sinit();  break; /* Send-Init */
	    case 'F':	state = sfile();  break; /* Send-File */
	    case 'D':	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':	return (FALSE);		 /* "Abort" */
	    default:	return (FALSE);		 /* Unknown, fail */
	}
    }
}


/*
 *  s e o f
 *
 *  Send End-Of-File.
 */

static int seof()
{
    int num, len;			/* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* If too many tries, "abort" */

    spack('Z',n,0,packet);		/* Send a 'Z' packet */
    switch(rpack(&len,&num,recpkt))	/* What was the reply? */
    {
	case 'N':			/* NAK, just stay in this state, */
	    num = (--num<0 ? 63:num);	/* unless it's NAK for next packet, */
	    if (n != num)		/* which is just like an ACK for */
		return(state);		/* this packet so fall thru to... */

	case 'Y':			/* ACK */
	    if (n != num) return(state); /* If wrong ACK, hold out */
	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* and bump packet count */
	    if (debug) printf("	  Closing input file %s, ", filename);
	    fclose(fp);			/* Close the input file */
	    fp = NULL;			/* Set flag indicating no file open */ 

	    if (debug) printf("looking for next file...\n");
	    if (file == files)		/* Any more files ? */
		return('B');		/* No */
	    filename = filenames[file++];
	    if (debug) printf("	  New file is %s\n", filename);
	    return('F');		/* More files, switch state to F */

 	case 'E':			/* Error packet received */
 	    prerrpkt(recpkt);		/* Print it out and */
 	    return('A');		/* abort */

	case FALSE: return(state);	/* Receive failure, stay in Z */

	default:    return('A');	/* Something else, "abort" */
    }
}


/*
 *  s f i l e
 *
 *  Send File Header.
 */

static int sfile()
{
    int num, len;			/* Packet number, length */
    char filnam1[50],			/* Converted file name */
	*newfilnam,			/* Pointer to file name to send */
	*cp;				/* char pointer */

    if (numtry++ > MAXTRY) return('A'); /* If too many tries, give up */
    
    if (fp == NULL)			/* If not already open, */
    {	if (debug) printf("   Opening %s for sending.\n", filename);
	fp = fopen(filename,"r");	/* open the file to be sent */
	if (fp == NULL)			/* If bad file pointer, give up */
	{
	    error("Cannot open file %s", filename);
	    return('A');
	}
    }

    strcpy(filnam1, filename);		/* Copy file name */
    newfilnam = cp = filnam1;
    while (*cp != '\0')			/* Strip off all leading directory */
	if (*cp++ == '/')		/* names (ie. up to the last /). */
	    newfilnam = cp;

    if (convert)			/* Convert lower case to upper	*/
	for (cp = newfilnam; *cp != '\0'; cp++)
	    if (*cp >= 'a' && *cp <= 'z')
		*cp ^= 040;

    len = cp - newfilnam;		/* Compute length of new filename */

    printmsg("Sending %s as %s",filename,newfilnam);

    spack('F',n,len,newfilnam);		/* Send an F packet */
    switch(rpack(&len,&num,recpkt))	/* What was the reply? */
    {			
	case 'N':			/* NAK, just stay in this state, */
	    num = (--num<0 ? 63:num);	/* unless it's NAK for next packet */
	    if (n != num)		/* which is just like an ACK for */ 
		return(state);		/* this packet so fall thru to... */

	case 'Y':			/* ACK */
	    if (n != num) return(state); /* If wrong ACK, stay in F state */
	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* Bump packet count */
	    size = bufill(packet);	/* Get first data from file */
	    return('D');		/* Switch state to D */

 	case 'E':			/* Error packet received */
 	    prerrpkt(recpkt);		/* Print it out and */
 	    return('A');		/* abort */

	    case FALSE: return(state);	/* Receive failure, stay in F state */

	    default:	return('A');	/* Something else, just "abort" */
	}
}


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

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

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

    flushinput();			/* Flush pending input */

    spack('S',n,6,packet);		/* Send an S packet */
    switch(rpack(&len,&num,recpkt))	/* What was the reply? */
    {
	case 'N':  return(state);	/* NAK, try it again */

	case 'Y':			/* ACK */
	    if (n != num)		/* If wrong ACK, stay in S state */
		return(state);		/* and try again */
	    rpar(recpkt);		/* Get other side's init info */

	    if (eol == 0) eol = '\n';	/* Check and set defaults */
	    if (quote == 0) quote = '#';

	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* Bump packet count */
	    return('F');		/* OK, switch state to F */

 	case 'E':			/* Error packet received */
 	    prerrpkt(recpkt);		/* Print it out and */
 	    return('A');		/* abort */
 
	case FALSE: return(state);	/* Receive failure, try again */

	default: return('A');		/* Anythig else, just "abort" */
   }
 }


/*
 *  s p a c k
 *
 *  Send a Packet
 */

static int spack(type,num,len,data)
char type, *data;
int num, len;
{
    int i;				/* Character loop counter */
    char chksum, buffer[100];		/* Checksum, packet buffer */
    register char *bufp;		/* Buffer pointer */

    if (debug>1)			/* Display outgoing packet */
    {
	if (data != NULL)
	    data[len] = '\0';		/* Null-terminate data to print it */
	printf("  spack type: %c\n",type);
	printf("	 num:  %d\n",num);
	printf("	 len:  %d\n",len);
	if (data != NULL)
	    printf("	    data: \"%s\"\n",data);
    }
  
    bufp = buffer;			/* Set up buffer pointer */
    for (i=1; i<=pad; i++) 		/* Issue any padding */
	fputc(padchar, cwfp);

    *bufp++ = SOH;			/* Packet marker, ASCII 1 (SOH) */
    *bufp++ = tochar(len+3);		/* Send the character count */
    chksum  = tochar(len+3);		/* Initialize the checksum */
    *bufp++ = tochar(num);		/* Packet number */
    chksum += tochar(num);		/* Update checksum */
    *bufp++ = type;			/* Packet type */
    chksum += type;			/* Update checksum */

    for (i=0; i<len; i++)		/* Loop for all data characters */
    {
	*bufp++ = data[i];		/* Get a character */
	chksum += data[i];		/* Update checksum */
    }
    chksum = (((chksum&0300) >> 6)+chksum)&077; /* Compute final checksum */
    *bufp++ = tochar(chksum);		/* Put it in the packet */
    *bufp = eol;			/* Extra-packet line terminator */
    fwrite(buffer, 1, bufp - buffer + 1, cwfp);	/* Send the packet */
}


/*
 *  s p a r
 *
 *  Fill the data array with my send-init parameters
 *
 */

static void spar(data)
char data[];
{
    data[0] = tochar(MAXPACK);		/* Biggest packet I can receive */
    data[1] = tochar(MYTIME);		/* When I want to be timed out */
    data[2] = tochar(MYPAD);		/* How much padding I need */
    data[3] = ctl(MYPCHAR);		/* Padding character I want */
    data[4] = tochar(MYEOL);		/* End-Of-Line character I want */
    data[5] = MYQUOTE;			/* Control-Quote character I send */
}


#if MVUX
/*
 *  t a l k e r 
 *
 *  Special MV/UX sub-task to send keyboard characters to remote system.
 *
 */

static void talker()
{
	int	cc;				/* I/O buffer */

	while (1)
	{
	    if ((cc = strip(fgetc(trfp))) == escchr)
	    {
		if ((cc = strip(fgetc(trfp))) == escchr)
		    fputc(escchr, lwfp);
		else
		{
		    switch (cc)
		    {
			case '#':		/* simulate break */
			    break;
			
			case 'c':		/* return to local machine */
			case 'C':
			    terminate = 1;
			    break;

			case 'h':		/* verify running status */
			case 'H':
			    fprintf(twfp, "\r\n(Kermit)\r\n");
			    fputs("\r\n\r\n", twfp);
			    break;

			default:
			    fputc(BELL, twfp);
			    break;
		    }
		}
	    }
	    else
		fputc(cc, lwfp);
	}
}
#endif


/*
 *  u s a g e 
 *
 *  Print summary of usage info and quit
 *
 */

static void usage()
{
#if UCB4X | MVUX
    printf("Usage: kermit c[lbe line baud esc.char]	 (connect mode)\n");
    printf("or:	   kermit s[diflb line baud] file ...	 (send mode)\n");
    printf("or:	   kermit r[diflb line baud]		 (receive mode)\n");
#else
    printf("Usage: kermit c[le line esc.char]		 (connect mode)\n");
    printf("or:	   kermit s[difl line] file ...		 (send mode)\n");
    printf("or:	   kermit r[difl line]			 (receive mode)\n");
#endif
    exit(1);
}
