/*
 * A completely new C Kermit module.
 *
 * Based on code from Frank Da Cruz's excellent book, _Kermit: A File
 * Transfer Protocol_, Digital Press, 1986.
 *
 * As this code is almost entirely from said book, it is certainly covered
 * by that book's copyright.  Basically, this means the code is freely
 * distributable, and can be used in a commercial program provided such
 * use does not increase the program's cost to the purchaser beyond a small
 * handling fee.
 *
 * Stephen Walton, swalton@solar.stanford.edu
 * Dept. of Physics and Astronomy
 * California State University, Northridge
 * Northridge, CA 91330
 *
 *			ORGANIZATION
 *
 *  This file is in three pieces, and could probably be broken into
 * two files.  Leading off are #DEFINE's and declarations of
 * global and external variables.  Following are the unmodified sources
 * from The Book.   Then are the z* file-handling routines written using
 * standard Unix-style (almost ANSI C) file routines.
 *
 * I make no apologies for the organization; my primary goals were (1) to
 * use the unmodified book source except for errors found by Lint, and
 * (2) to allow this file to be plugged into an otherwise unmodified
 * terminal program for the Commodore Amiga computer called VT100.  The
 * comments starting with the string "lint" are for the Lint program
 * of Gimpel Software, available for the Commodore Amiga and MS-DOS
 * machines.  I highly recommend it.
 *
 * A few words about style herein.  Both "book Kermit" and the official
 * C Kermit release make extensive use of global variables to set various
 * options and keep track of what is going on.  I don't like this, but
 * since example source code must be part of the Kermit specification,
 * I chose to keep that organization.  There are several places where I
 * I have tried to modularize things better.  First of all, everything
 * which is not needed outside of this file is declared "static".  Second,
 * I have kept the book Kermit code pretty much intact, except for some lint
 * related things and one extra convention:  tmsg() appends characters to
 * an existing status line, but tmsgl() is required to force that output to
 * be seen.  Hence, I've changed the last in each series of tmsg() calls to
 * tmsgl().
 */


/*
 * Revision History--Versions 0 through 1 never left me.
 *
 * Version 0.5--Created and linted.
 * Version 0.6--Added RESUME to handling of "unknown packets".
 *	      --Added proto() function to wrap around wart() for handling
 *		of startup and cleanup functions.
 *	      --Changed handling of timeouts in input():  instead of an
 *		error("Timeout") call, it puts the "Timeout" message in
 *		the data field and pretends it read an 'E' packet.
 * Version 0.7--Fixed a problem with rpsiz and MAXRP.  I had set rpsiz
 *		to MAXRP in rpar() and was calling ttinl() with a max
 *		argument of MAXRP, which resulted in truncated packets;
 *		to wit, MAXRP+1 characters could come into ttinl counting
 *		the eol.  Created defines DRPSIZ/DSPSIZ for default values
 *		for rpsiz and spsiz, and increased MAXRP.
 *	      --Changed rcvpkt, sndpkt, and data so that they are pointers
 *		instead of static arrays, and allocate and de-allocate
 *		them in proto().
 * Version 0.8--Added client support.  To make a Get
 *		command, point the char *cmarg at the remote file
 *		specification and set the start state to 'r'
 *		(extern char start in the calling file).
 * Version 0.9--Added long packet support.  This required adding an extern
 *		int urpsiz which is set to the desired receive packet size
 *		by the user interface.
 *	      --Since the above required using the capas, I also added the
 *		code from C Kermit 4E(070) for negotiating window size
 *		in rpar() and spar(). Of course, we don't do windows yet.
 *	      --Fixed a bug which is also part of C Kermit 4E(070).
 *		If we tell the sender that we can receive a packet
 *		of size MAXRP, the packet can contain as many as
 *		MAXRP+5 characters, counting the EOL and extended
 *		headers.  So rpack() must be able to handle somewhat
 *		more characters than the actual maximum packet length.
 *		I defined MAXRP here to be 1024, but only allow
 *		rpsiz in proto() to be MAXRP-20.
 * Version 0.95--Added code to gracefully abort transfer.  This works
 *		via the cx and cz external variables and the state 'a'.
 * Version 1.0--Unleashed upon the world.
 * Version 1.1--Marco Papa (papa on BIX, papa@pollux.usc.edu on Internet)
 *		added the code between the #ifdef XPRKERMIT...#endif
 *		pairs, which turns this code into a routine which can
 *		be used as an Amiga external file transfer protocol.
 *		Upon getting it back from him, I fixed two lingering
 *		bugs:  there was an "if (ebqflg & b8)" in encode()
 *		which should be &&, and the first argument of encode()
 *		needed to be declared as an int, not a char.
 *	      --Allow user to set bctr (block check to request) himself.
 *	      --If urpsiz is illegal, we set rpsiz to a legal value;  I also
 *		set urpsiz to this as well, so the user knows what's up.
 * Version 1.2--Added a typedef of CHAR to unsigned char to allow this code
 *		to work with the latest version of Wart (V 1A(006) dated
 *		12 January 1989.
 * Version 1.3--21 December 1989
 *		(1)  Added a tflush() call to the end of decode().  This
 *		gives the user an opportunity to flush out the messages
 *		from X packets, if the I/O is buffered.
 *
 * Version 1.4-- 5 January 1989
 * 		(1) Added the counters for characters on a per-file and
 *		per-transaction basis.  Later, we can use the per-file
 *		ones and a timer to figure out how long we have to go
 *		on the current file.
 *		(2) Started replacing tchar() calls with calls to the
 *		screen() function, again a la C Kermit.  #define's for
 *		the manifest constants for screen() are in kermitproto.h,
 * 		copied from C Kermit.
 *
 * Version 1.6-- 18 August 1990 (Version 1.4 was released)
 *		(1) Added ANSI-specific items, in particular the #include
 *		of "kermitproto.h" and "xproto.h" which have all of the
 *		prototypes.
 *		(2) Fixed gnfile(), which was not being properly re-
 *		initialized if a send was canceled.
 *
 * Version 1.63--2 December 1990
 *		(1) Changed the behavior of the xpr chkint() function so
 *		that the error exit is taken if (*xpr_chkabort)() returns
 *		-1:  an error packet is made up and sent and the protocol
 *		quits immediately.
 *		(2) Discovered that the file character count in gnchar()
 *		skipped nulls!  Fixed.
 *
 * Version 1.64--5 December 1990
 *		Fixed zgetc(), which was hanging onto characters from the
 *		previous file transfer if it was cancelled.
 *
 * Version 1.67--14 December 1990 (includes 1.65 through 1.67 changes)
 *		Added buffered I/O from C Kermit 5A(163).
 *		Replaced getpkt() with version from C Kermit 5A.  Got rid
 *		   of encode(), since it is built in.
 *		Fixed a long standing block check 2 bug--a missing break
 *		   in the case statement in rpack()!
 *
 * Version  1.72--18 December 1990
 *		Fixed *MAJOR* bugs in getpkt() from C Kermit:  maxsize()
 *		   is spurious, and should be bufmax;  and the leftover[]
 *		   array needs to be 7 characters long, not 6:  the worst
 *		   case is two successive eight-bit-quoted control chars.
 */

/*
 * Run this through the Wart preprocessor which comes with C Kermit.
 */

#ifndef _lint
static char rcsid[] = "$Header: Work:src/xprkermit/RCS/kermitproto.w,v 1.3 91/11/09 06:49:18 swalton Exp Locker: swalton $";
#endif

#ifdef XPRKERMIT
extern long (*xupdate)(), (*xswrite)(), (*xfopen)(), (*xfclose)(), (*xfread)(),
     (*xfwrite)(), (*xsread)(),  (*xchkabort)(void), (*xffirst)(), (*xfnext)(),
     (*xsflush)(void), (*xfinfo)(), (*xgets)(), (*xunlink)(), (*xsquery)(void),
     (*xchkmisc)(void);
extern long brkflag;
#include "types.h"
#include "xprkermit.h"
#endif

#include "kermitproto.h"
#include <stdlib.h>
#include <string.h>


/* kermitproto.c */
static int input(void);
static void errpkt(char *s);
#pragma regcall(errpkt(a0))
static int nxtpkt(void);
static int tinit(void);
static int error(char *s);
#pragma regcall(error(a0))
static int ack(void);
static int ack1(char *s);
#pragma regcall(ack1(a0))
static int nak(void);
static int sinit(int c);
#pragma regcall(sinit(d0))
static int scmd(int t, char *s);
#pragma regcall(scmd(d0,a0))
static int rinit(void);
static void resetc(void);
static int sfile(void);
static int sdata(void);
static int seof(char *s);
#pragma regcall(seof(a0))
static int seot(void);
static int rcvfil(void);
static int reof(char *);
#pragma regcall(reof(a0))
static void fstats(void);
static int closof(int nokeep);
#pragma regcall(closof(d0))
static int spack(int type, int n, int len, CHAR *d);
#pragma regcall(spack(d0,d1,d2,a0))
static int resend(void);
static int rpack(void);
static unsigned chksum(CHAR *s);
#pragma regcall(chksum(a0))
static unsigned chk1(CHAR *s);
#pragma regcall(chk1(a0))
static unsigned chk3(CHAR *s);
#pragma regcall(chk3(a0))
static int getpkt(int maxlen);
#pragma regcall(getpkt(d0))
static int decode(void);
static int encstr(char *s);
#pragma regcall(encstr(a0))
static int decstr(char *s);
#pragma regcall(decstr(a0))
static int spar(CHAR *s);
#pragma regcall(spar(a0))
static char *rpar(void);
static int zclear(void);
static int zopeni(char *name);
#pragma regcall(zopeni(a0))
static int zopeno(char *name);
#pragma regcall(zopeno(a0))
static int zclosi(void);
static int zcloso(int discard);
#pragma regcall(zcloso(d0))
static int zrtol(char *s1, char *s2, int warn);
#pragma regcall(zrtol(a0,a1,d0))
static int zltor(char *s1, char *s2);
#pragma regcall(zltor(a0,a1))
static int zinfill(void);
static int zoutdump(void);
static int tflush(void);

/*
 * Buffered I/O stuff stolen from real C Kermit
 */
#define INBUFSIZE 1024			/* Size of record buffer. */
#define OBUFSIZE 1024			/* Size of output buffer. */
#define MAXNAMELEN 256			/* Maximum file name length */

static CHAR *zinbuffer, *zoutbuffer;
static CHAR *zinptr, *zoutptr;		/* Buffer pointers */
static int zincnt, zoutcnt;			/* Buffer char counters */


/* get the next char; sorta like a getc() macro */
#define zminchar() (((--zincnt)>=0) ? ((int)(*zinptr++) & 0377) : zinfill())

/* stuff a character into the input buffer */
#define zmstuff(c) zinptr--, *zinptr = c, zincnt++

/* put a character to a file, like putchar() macro */
#define zmchout(c) \
((*zoutptr++=(CHAR)(c)),(((++zoutcnt)>=OBUFSIZE)?zoutdump():0))

/*
 * Here are the variables which need to be set to startup values, and which
 * also can be freely changed between protocol transfers.  At first I thought
 * to declare them all "extern" in order to force definition elsewhere.
 * On reflection, it makes sense to both declare them here and set them to
 * their default startup values.  Thus they can be ignored outside of this
 * module if you so desire.
 *
 * Note that the names are very systematic:  Names beginning with "r" have
 * to do with values I use for received packets;  those beginning with "s"
 * are values I use for sending packets.  Also note we set some, others are
 * set for us.  I have made the ones we get in spar() static (local),
 * and the ones we send in rpar() global.
 *
 * First the ints.
 */

int	cx = 0,
	cz = 0,		/* Flags for aborting transfers.  cx (control-X)*
			 * is set to 1 if an abort of the current file	*
			 * is desired, cz (control-Z) if an abort of	*
			 * an entire batch transfer is desired.		*/
	rpsiz = MAXRP,	/* Maximum packet size				*/
			/* Like most of the receive packet parameters,	*
			 * this one is actually set by the sender.  But	*
			 * since the sender has the option to not send	*
			 * these, we must initialize to "reasonable"	*
			 * defaults.					*/
	bctr = 1,	/* Block check type to request.			*/
	limit = 5,	/* Retry limit on receive			*/
	warn = 0,	/* 1 for warn before overwriting files		*/
	keep = 0,	/* 1 to keep incomplete files			*/
	rpadn = 0,	/* Number of pad characters I require.		*/
	rtimo = 10;	/* How long I want you to wait before you	*
			 * you time me out.				*/

CHAR	rmark = '\1',	/* Start of packet marker for receive		*/
	reol = '\r',	/* End of packet marker for receive		*/
	start = 0,	/* Start state					*/
	sctlq = '#',	/* Control character quote character for send	*/
	rpadc = '\0';	/* Pad character I want you to use		*/

/*
 * Variables which MUST be set by the external interface.
 */
extern int
	parity,		/* 0 for no parity--need for proper 8th-bit quote */
	text,		/* Flag 1 for text file, 0 for binary file	*/
	urpsiz;		/* Maximum receive packet size user wants.	*/

extern char
	*cmarg;		/* Character string containing Kermit server cmd */

/*
 * Variables having to do with the status of the file transfer.
 */
static long tfc,	/* File chararacters sent/received, total. */
	    ffc,	/*   "       "           "        , current file. */
	    timeouts,	/* Number of timeouts */
	    tlci,	/* Comm line characters in, total. */
	    flci,	/*   "    "      "       ", current file. */
	    tlco,	/*   "    "      "       ", total. */
	    flco;	/*   "    "      "       ", current file. */
/*
 * In a fit of cleverness, here are some macro defines for variables we *
 * aren't currently using. Only now we tell Lint to ignore constant	*
 * Booleans!
 */

/*lint -e506 */

#define local 1		/* Local mode flag--that is, I'm on your end	*/
#define server 0	/* We are never server				*/
#define delay 0		/* Time to delay before sending first packet	*/
#define xpkt 0		/* Send X packet instead of F?			*/

/*
 * This block of defines is strictly internal flags of various kinds.	*
 * I hope to Grid that I've got them all.  Someday this will be cleaner *
 */
static int
	spsiz = DSPSIZ,	/* Maximum send packet size			*/
	wsize = MAXWS,	/* Maximum window size				*/
	sndpkl,		/* Size of packet currently being attempted	*/
	filcnt,		/* Number of files transferred so far		*/
	bctu = 1,	/* Block check type to use			*/
	rqf,		/* Flag for 8th bit quote negotiations		*/
	ebqflg = 0,	/* 8th-bit quoting flag				*/
	xflag,		/* Output to screen for generic server commands	*/
	rq = 0,		/* Received 8bq bid				*/
	rpt = 0,	/* Repeat count					*/
	rptflg = 0,	/* Repeat processing flag			*/
	capas = 10,	/* Final position of inbound capas mask		*/
	atcapr = 0,	/* Attribute capability requested		*/
	atcapu = 0,	/* Attribute capability used			*/
	swcapr = 0,	/* Sliding windows capability requested		*/
	swcapu = 0,	/* Sliding windows capability used		*/
	lpcapr = 0,	/* Long packets capability requested		*/
	lpcapu = 0,	/* Long packets capability used			*/
	rsn,		/* Received sequence number			*/
	seq = 0,	/* Current sequence number			*/
	rln,		/* Length of received data field		*/
	size,		/* Current size of output packet data		*/
	first = 0,	/* Some kind of lookahead flag			*/
	stimo = 5,	/* Timeout interval for me to use		*/
	spadn = 0;	/* Number of pad characters for me to use	*/

#define atcapb 8	/* Attribute capability bit			*/
#define swcapb 4	/* Sliding windows capability bit		*/
#define lpcapb 2	/* Long packets capability bit			*/
#define closif zclosi	/* I use closif() to close the input file in	*
			 * case it needs to be more complex later, but	*
			 * for now it just calls the z routine.		*/

static char
	filnam[MAXNAMELEN], /* Current file name			*/
        myname[MAXNAMELEN], /* Current local file name			*/
	ssc,		/* Start server command				*/
	memstr,		/* Use in-memory string?			*/
	*osp = NULL;	/* Output string pointer			*/

static CHAR
	smark = '\1',	/* Start of packet marker for send		*/
	spadc = '\0',	/* Pad character to use on sending		*/
	seol = '\r',	/* End of packet marker for sending		*/
	rctlq = '#',	/* Control character quote char for receiving	*/
	ebq = '&',	/* 8th-bit prefix				*/
	sq = 'Y',	/* Sent 8bq bid					*/
	rptq = '~',	/* Repeat prefix				*/
	*memptr = NULL,	/* Pointer to characters in memory		*/
	*sndpkt,	/* Send packet.					*/
	*rcvpkt,	/* Receieve packet.				*/
	*data,		/* Data to send/receive before encode/decode	*/
	*rdatap;	/* Pointer to null-terminated data field	*/

#define ERR(s) error(s); RESUME
#define RESUME return

/*lint -save -e525 -e527	We don't care how Wart formats!		*/
/*lint -e716 -e744 -e570 -esym(745,wart)	Other Wart peculiarities */

%states ssfil ssdat sseot
%states srini srfil srdat
%states sipkt srgen
%%

 /* Start states */

s {					/* - Start State - */
    tinit();				/* Initialize transaction */
    if (sinit('S') < 0) { ERR("sinit"); }	/* Build, send Send-Init. */
    else {				/* If successful, */
	filcnt = 0;			/* initialize file counter */
	BEGIN ssfil;			/* and switch to ssfil state. */
    }
}
v { tinit(); rinit(); BEGIN srini; }	/* - Receive - */

r {					/* Get */
    tinit(); ssc = 0;
    if (sinit('I') < 0) { ERR("sinit"); }
    else BEGIN sipkt;
}

c {					/* Host */
    tinit(); ssc = 'C';
    if (sinit('I') < 0) { ERR("sinit"); }
    else BEGIN sipkt;
}

g {					/* Generic */
    tinit(); ssc = 'G';
    if (sinit('I') < 0) { ERR("sinit"); }
    else BEGIN sipkt;
}

a {					/* Immediate protocol abort */
    spack('E', seq, 21, (CHAR *) "User aborted protocol");
    closif();  closof(1);		/* Close files, deleting output */
    RESUME;
}

/* Dynamic states, sending file(s) */

<ssfil>Y {				/* - Send File State - */
    if (filcnt++ == 0) spar(rdatap);	/* Set parameters if 1st time */
    cx = 0;				/* Reset file interruption flag */
    bctu = bctr;			/* Switch to negotiated block check */
    resetc();				/* Reset global counters. */
#ifdef XPRKERMIT
    ST_Display_CRC(bctu);
#endif
    /* Is there a file to send in an uncancelled batch? */
    if (!cz && gnfile(filnam, sizeof(filnam)) > 0) {
	if (sfile() < 0) { ERR("sfile"); }	/* Yes, open it, send F packet */
	else BEGIN ssdat;		/* and if no error, switch state. */
    } else {				/* No (more) files to send */
	if (seot() < 0) { ERR("seot"); }	/* so send B packet */
	else BEGIN sseot;		/* and switch to sseot state. */
    }
}
<ssdat>Y {				/* - Send Data State - */
    int x;
    if (rln == 1 && *rdatap == 'X')	/* Did ACK contain X as data? */
	cx = 1;				/* Yes, set control-x flag. */
    else if (rln == 1 && *rdatap == 'Z') /* Did ACK contain Z as data? */
	cz = 1;				/* Yes set control-z flag. */
    /* Check here for cancellation of transfer and data left to send. */
    if (cx || cz || (x = sdata()) == 0) {
	if (seof((cx || cz) ? "D" : "") < 0) {	/* If not, send Z packet. */
	    ERR("seof");
	}
	else BEGIN ssfil;		/* and go back to ssfil state. */
    } else if (x < 0) { ERR("sdata"); }	/* Handle file i/o errors. */
}
<sseot>Y { RESUME; }			/* - Send B, done. */

/* Dynamic states, receiving file(s) */

<srini,srgen>S {
    resetc();
    spar(rdatap);
    (void) ack1(rpar());
    bctu = bctr;
    BEGIN srfil;
}

<srfil>B { (void) ack(); RESUME; }

<srfil>F { if (rcvfil() < 0) { ERR("rcvfil"); } else { (void) ack(); BEGIN srdat; } }

<srdat>D {
   if (cx)
	ack1("X");
   else if (cz)
	ack1("Z");
   else	if (decode() < 0) {
	ERR("Error writing data");
   } else
	(void) ack();
}

<srdat>Z {
    if (reof(filnam) < 0) {		/* Close & dispose of the file */
	errpkt("Can't close file");	/* If problem, send error message */
	RESUME;				/* and quit */
    } else {				/* otherwise */
	ack();				/* acknowledge the EOF packet */
	BEGIN srfil;			/* and await another file */
    }
}

/* Dynamic states, server mode */

<sipkt>Y {			/* Got ACK for I packet */
    spar(rdatap);		/* Set parameters from it */
    start = 'E';		/* Force entry into next state */
}

<sipkt>E {			/* Got E for I packet */
    if (ssc) {
	if (scmd(ssc,cmarg) < 0) { ERR("scmd"); }
	else BEGIN srgen;
    } else {
	if (scmd('R',cmarg) < 0) { ERR("scmd"); }
	else BEGIN srini;
    }
}

<srgen>Y { xflag = 1; decode(); RESUME; }

<srgen,srfil>X { xflag = 1; decode(); ack(); BEGIN srdat; }

/* Error state */

E { error((char *) rdatap);
    (void) closif();
    (void) closof(1);		/* close files, discarding output */
    RESUME; }

. { error("Unexpected packet type"); RESUME; }
				/* Handle unwanted packet types. */
%%

/*lint -restore -esym(551,tlci,tlco,flci,flco,rptn,atcapu,tfc) */

static void
errpkt(char *msg) {
    spack('E', seq, (int) strlen(msg), (CHAR *) msg);
}

static
int
input() {				/* Return packets	*/
    int type, try;

    if (start != 0) {			/* Start state in effect? */
	type = start;			/* Yes, call it a packet type, */
	start = 0;			/* nullify the start state, */
	return(type);			/* and return the type. */
    }
    for (try = 0; ; try++) {
	type = rpack();			/* No start state, read a packet. */
	/*
	 * Some checks we do every packet.
	 */
	if (try > limit) {		/* If too mahy tries, */
	    strcpy((char *) data, "Too many retries");	/* give up */
	    rdatap = data;		/* Make up pretend 'E' packet */
	    type = 'E';			/* and return it */
	    break;
	}
#ifdef XPRKERMIT
	if (xchkmisc)
            (void) (*xchkmisc)();
#endif
	if (chkint() < 0) {		/* Check for console interrupt. */
	    errpkt("User cancelled.");
	    strcpy((char *) data, "User cancelled.");
	    rdatap = data;
	    type = 'E';
	    break;
	}
	/*
	 * Now see if we got the packet we want.
	 */
	if (rsn == seq && strchr("TQN",type) == 0)	/* packet we want */
	    break;
	else if (type == 'N' && rsn == ((seq + 1) & 63)) {
					/* NAK for next packet */
	    type = 'Y';			/* is ACK for current. */
	    break;
	} else {			/* Otherwise, */
	    (void) resend();		/* resend previous packet. */
	}
    }
    ttflui();				/* Got a good one, clear buffer. */
    return(type);			/* Return its type. */
}

static
nxtpkt() {
    seq = (seq + 1) & 63;		/* Next packet number, mod 64 */
}

/*  R E S E T C  --  Reset per-transaction character counters */
static void resetc(void) {
    tfc = tlci = tlco = 0;	/* Total file chars, line chars in & out */
    timeouts = 0;		/* Timeouts */
}

static
tinit() {				/* Transaction initialization */
    seq = 0;				/* Start off with packet 0 */
    ebqflg = 0;				/* 8-bit quoting off */
    sq = 'Y';				/* Normal 8-bit quote bid */
    rqf = -1;				/* Flag other's bid not yet seen */
    rptflg = 0;				/* No repeat counts by default */
    bctu = 1;				/* Block check to use back to 1 */
    xflag = 0;				/* Output normally to file */
    memstr = 0;				/* Reset memory-string flag */
    memptr = NULL;			/*  and poiinter */
    osp = NULL;				/* ... */
    cx = cz = 0;			/* Reset interrupt flags */
    *filnam = *sndpkt = *rcvpkt = '\0';  /* Clear string buffers */
    spsiz = rpsiz;			/* Initial send-packet size */
}

static
error(s) char *s; {			/* Fatal error */
    if (local) {			/* If in local mode */
	screen(SCR_EM,0,0L,s);		/* Type message on console */
    } else {				/* Otherwise, */
	(void) spack('E',seq,(int) strlen(s), (CHAR *) s); /* Send in error packet. */
    }
    return;
}

static
ack() {
    int x;				/* Empty acknowledgement */
    x = spack('Y',seq,0,(CHAR *) "");	/* Send the packet */
    nxtpkt();				/* Increment the packet number */
    return(x);
}

static
ack1(s) char *s; {
    int x;				/* Acknowledgement with data */
    x = spack('Y',seq,(int)strlen(s),(CHAR *)s);
    nxtpkt();				/* Increment packet number */
    return(x);
}

static
nak() {					/* Negative acknowledgement */
    return(spack('N',seq,0,(CHAR *)""));	/* Never has data! */
}

/* Functions used by file sender. */

/* sinit()--start the transaction by filling in the initialization string
 * and sending it in an S packet.
 */

static
sinit(c) char c; {
    char *s;

    gninit();				/* Initialize gnfile() function. */
    s = rpar();
    if (local == 0 && c == 'S' && server == 0) {
#ifdef XPRKERMIT
	ST_Display_String(STMsg,"Escape back to local system, RECEIVE command");
#else
	tmsgl("Escape back to local system, give RECEIVE command...");
#endif
	sleep(delay);
    }
    return(spack(c,seq,(int)strlen(s),(CHAR *)s));
}

/*
 * scmd() -- send a preformatted Kermit server command string.
 */
static
scmd(t, s) char t, *s; {	/* Send a packet of the given type */
    encstr(s);			/* Encode the command string */
    spack(t,seq,size,data);
}

/* rinit() -- do whatever is needed to initialize receive.  Now a no-op.
 */
static
rinit()
{
}

/* sfile() -- open the file and send the File-Header packet.  Assumes that
 * the global string pointer filnam references the file name.
 */

static
sfile() {
    int x;
    char pktnam[MAXNAMELEN];
    if (zopeni(filnam) < 0)		/* Try to open file. */
	return -1;
    zltor(filnam,pktnam);		/* OK, convert name. */
    x = encstr(pktnam);			/* Encode the result */
    if (local) {			/* If in local mode, */
#ifdef XPRKERMIT
	ST_Display_String(STFile, pktnam);
	ST_Display_String(STUplSize, filnam);
#else
	tmsg("Sending ");		/* let user know we're */
	tmsg(filnam);			/* sending this file */
	tmsg(" as ");			/* under */
	tmsgl(pktnam);			/* this name */
#endif
    }
    first = 1;				/* Flag beginning of file */
    ffc = flci = flco = 0;		/* Zero per-file char count */
    nxtpkt();				/* Increment packet number */
    return(spack((xpkt ? 'X' : 'F'),seq,x,data)); /* Send packet */
}

/* sdata() -- get next packet's worth of data */

static
sdata() {
    int x;

    if (spsiz > 94)			/* Long packets */
        x = getpkt(spsiz - bctu - 6);
    else				/* Short packets. */
        x = getpkt(spsiz - bctu - 3);
    if (x == 0)				/* If no data left to send, */
	return(0);			/* return EOF indication. */
    nxtpkt();
    return(spack('D',seq,x,data));	/* Send the data packet */
}

/* seof -- close the input file and send a Z packet. */

static
seof(s) char *s; {
    if (closif() < 0)			/* Try to close the file. */
	return -1;			/* On error, return failure. */
    else {				/* Otherwise, */
#ifdef XPRKERMIT
	if (local) ST_Display_String(STMsg,"OK");
#else
	if (local) tmsgl("OK");		/* if local, reassure user. */
#endif
	nxtpkt();
	return(spack('Z',seq,(int)strlen(s),(CHAR *) s)); /* Send Z packet */
    }
}

/* seot -- send B packet. */

static
seot() {
    nxtpkt();
#ifdef XPRKERMIT
    if (local) ST_Display_String(STMsg,"Done");
#else
    if (local) tmsgl("Done");
#endif
    return(spack('B',seq,0,(CHAR *) ""));
}

static
rcvfil() {				/* Receive a file */

    ffc = flci = flco = 0;		/* Initialize per-file char count */
    decstr(filnam);			/* Decode name */
    zrtol(filnam,myname,warn);		/* Convert to local form. */
    if (zopeno(myname) < 0)
	return -1;
    else {				/* File open OK, give message. */
	if (local && !xflag) {
#ifdef XPRKERMIT
	    ST_Display_String(STFile,myname);
	    ST_Display_CRC(bctu);
#else
	    tmsg("Receiving "); tmsg(filnam); tmsg(" as "); tmsgl(myname);
#endif
	}
	return 0;
    }
}

/*  R E O F  --  Receive End Of File  */

static
reof(f) char *f; {
    int x;
    char *p;

    if (cx == 0) cx = (*rdatap == 'D');		/* Got discard directive? */
    x = closof((cx || cz) && (keep == 0));
    if (cx || cz) {
	cx = 0;
	if (keep)
	  p = "*** Incomplete file";
	else
	  p = "*** Discarding file";;
	screen(SCR_EM, 0, 0L, p);
    } else {
	fstats();
    }
    *filnam = '\0';
    return(x);
}

static void				/* Update statistics */
fstats() {
    tfc += ffc;
}

static
closof(nokeep) int nokeep; {		/* Close output file, but */
    if (xflag) return 0;		/* not if it's the screen. */
    if (zcloso(nokeep) < 0)
	return -1;
    return 0;
}

static
spack(type,n,len,d) char type; int n, len; CHAR *d; {
    int i = 0, j, k;
    unsigned int x;
    CHAR *sohp;				/* Mark start of packet data. */

    for (i = 0; i < spadn; i++)
	sndpkt[i] = spadc;		/* Do requested padding */
    sohp = sndpkt + i;
    sndpkt[i++] = smark;		/* Packet mark */
    k = i++;				/* Remember this place */
    sndpkt[i++] = tochar(n);		/* Sequence number */
    sndpkt[i++] = (CHAR) type;		/* Packet type */
    j = len + bctu;			/* True length */
    if (j > 92) {			/* Long packet? */
	sndpkt[k] = tochar(0);		/* Set LEN to 0 */
	sndpkt[i++] = tochar(j / 95);	/* High part of length */
	sndpkt[i++] = tochar(j % 95);	/* Low part of length */
	sndpkt[i] = '\0';		/* Header checksum */
	sndpkt[i++] = tochar(chk1(sndpkt+k));
    } else
	sndpkt[k] = tochar(j+2);	/* True length. */

    for (j = len; j > 0; j--) {		/* Data */
	sndpkt[i++] = *d++;
    }
    sndpkt[i] = '\0';			/* Null terminate. */
    switch (bctu) {
	case 1:				/* Type 1 - 6 bit checksum */
	    sndpkt[i++] = tochar(chk1(sndpkt+k));
	    break;
	case 2:				/* Type 2 - 12 bit checksum */
	    x = chksum(sndpkt+k);
	    sndpkt[i++] = tochar((x >> 6) & 077);
	    sndpkt[i++] = tochar(x & 077);
	    break;
	case 3:				/* Type 3 - 16 bit CRC-CCITT */
	    x = chk3(sndpkt + k);
	    sndpkt[i++] = tochar((x >> 12) & 017);
	    sndpkt[i++] = tochar((x >> 6) & 077);
	    sndpkt[i++] = tochar(x & 077);
	    break;
	default:
	    return -1;			/* Error message here? */
	}
    sndpkt[i++] = seol;			/* End of line */
    sndpkt[i] = '\0';			/* Null string-terminat	or. */
    sndpkl = i;				/* Remember length. */
    i = ttol((char *) sndpkt,sndpkl);	/* Send the packet. */
    tlco += sndpkl;			/* Count characters output. */
    flco += sndpkl;
    screen(SCR_PT, type, (long) n, (char *) sohp);
    return(i);
}

static
resend() {
    int x;
    if (*sndpkt)
	x = ttol((char *) sndpkt,sndpkl); /* Send previous packet */
    else
	x = nak();			/* or NAK if none */
    if (local && !xflag) tchar('%');	/* Let the user know. */
    return(x);
}

static unsigned
chk1(packet) CHAR *packet; {		/* Compute Kermit's */
    unsigned s, t;			/* 1-character block check. */
    s = chksum(packet);			/* Get the arithmetic sum. */
    t = (((s & 192) >> 6) + s) & 63;	/* Fold it into 6 bits. */
    return(t);
}

static unsigned
chksum(p) CHAR *p; {			/* Compute the checksum. */
    unsigned m;
    long s;

    m = (parity) ? 0177 : 0377;		/* Mask for parity bit.	*/
    s = 0;
    for (; *p != '\0'; p++)		/* For each character, */
	s += *p & m;			/* accumulate the sum, */
    return(s & 07777);			/* and then return it. */
}

/*
 * rpack reads a packet and returns the packet type, or else Q if the
 * packet was invalid, or T if a timeout occured.   Upon successful return,
 * sets the global variables:
 *	rsn	- the received sequence number
 *	rln	- length of the received data field
 *	rdatap	- a pointer to the null-terminated contents of the data field
 */
static
rpack() {
    int i, j, type, rlnpos;
    unsigned x;
    CHAR pbc[4];			/* Packet block check. */
    CHAR *sohp;				/* Start of packet data. */

    rsn = rln = -1;			/* In case of failure. */

    *rcvpkt = '\0';			/* Initialize receive buffer. */
    j = ttinl((char *) rcvpkt,MAXRP,reol,stimo);
    if (j < 0) {
        if (j == -1) {
            screen(SCR_PT, 'T', (long) rsn, "");
	    return('T');		/* Timed out. */
	} else
	    return(j);
    }

    tlci += j;				/* Count received characters. */
    flci += j;				/* On per-file basis. */
    for (i = 0; rcvpkt[i] != rmark && (i < j); i++)	/* Find mark. */
	;
    if (i == j) return('Q');		/* If no mark, bad packet. */
    sohp = rcvpkt+i;

    rlnpos = ++i;			/* Got it, remember position. */
    if ((j = unchar(rcvpkt[i++])) == 0) { /* Long packet? */
    	j = rlnpos + 5;			/* Yes, check header */
    	if (j > MAXRP) return('Q');	/* Be defensive. */
    	x = rcvpkt[j];			/* Remember header checksum */
    	rcvpkt[j] = '\0';
    	if (unchar(x) != chk1(rcvpkt+rlnpos))	/* Check header */
	    return('Q');
	rcvpkt[j] = (CHAR) x;			/* Restore packet */
	rln = unchar(rcvpkt[j-2]) * 95 + unchar(rcvpkt[j-1]) - bctu;
	j = 3;
    } else {
	rln = j - bctu - 2;		/* Regular packet */
	j = 0;				/* No extended header */
    }
    rsn = unchar(rcvpkt[i++]);		/* Sequence number. */
    type = rcvpkt[i++];			/* Packet type */
    i += j;				/* Skip extended header, if any */
    rdatap = rcvpkt + i;		/* The data itself. */
    j = rln + i;			/* Position of block check. */
    if (j > MAXRP)
	return('Q');			/* Be defensive! */
    for (i = 0; i < bctu; i++)		/* Copy the block check. */
	pbc[i] = rcvpkt[j+i];
    rcvpkt[j] = '\0';
    switch (bctu) {			/* Which block check type? */
	case 1:
	    if (unchar(*pbc) != chk1(rcvpkt+rlnpos)) return('Q');
	    break;
	case 2:
	    x = unchar(*pbc) << 6 | unchar(pbc[1]);
	    if (x != chksum(rcvpkt+rlnpos)) return('Q');
	    break;
	case 3:
	    x = unchar(*pbc) << 12 | unchar(pbc[1]) << 6 | unchar(pbc[2]);
	    if (x != chk3(rcvpkt+rlnpos)) return('Q');
	    break;
	default:
	    error("Impossible block check type.");
    }
    screen(SCR_PT, type, (long) rsn, (char *) sohp);
    return type;			/* Otherwise, return packet type */
}

/*
 * CHK3
 * Calculate the 16-bit CRC of a null-terminated string using a
 * byte-oriented tableless algorithm devised by Andy Lowry (Columbia
 * University).  The magic number 010201 is derived from the CRC-CCITT
 * polynomial x^16+x^12+x^5+1.
 */
static unsigned
chk3(s) CHAR *s; {
    unsigned c, q;
    unsigned long crc = 0;

    while ((c = *s++) != '\0') {
	if (parity) c &= 0177;
	q = (crc ^ c) & 017;		/* Low order nybble */
	crc = (crc >> 4) ^ (q * 010201);
	q = (crc ^ (c >> 4)) & 017;	/* High order nybble */
	crc = (crc >> 4) ^ (q * 010201);
    }
    return((unsigned) crc);
}

/*  G E T P K T -- Fill a packet data field  */

/*
 Gets characters from the current source -- file or memory string.
 Encodes the data into the packet, filling the packet optimally.
 Set first = 1 when calling for the first time on a given input stream
 (string or file).

 Uses global variables:
 t     -- current character.
 first -- flag: 1 to start up, 0 for input in progress, -1 for EOF.
 next  -- next character.
 data  -- pointer to the packet data buffer.
 size  -- number of characters in the data buffer.
 memstr - flag that input is coming from a memory string instead of a file.
 memptr - pointer to string in memory.
 (*sx)()  character set translation function

Returns the size as value of the function, and also sets global "size",
and fills (and null-terminates) the global data array.  Returns 0 upon eof.

Rewritten by Paul W. Placeway (PWP) of Ohio State University, March 1989.
Incorporates old getchx() and encode() inline to eliminate function calls,
uses buffered input for much-improved efficiency, and clears up some
confusion with line termination (CRLF vs LF vs CR).
*/

/*
 * Below defines for using the C Kermit 5A getpkt() routine in this module.
 */
#define NLCHAR '\n'
#define LF '\n'
#define SP ' '
#define DEL 0177
#define debug(a,b,c,d)
#define binary (!text)
#define cxseen cx
#define fmask 0377
#define myctlq sctlq
#define NOCSETS

static CHAR t, next;
static int rptn;

getpkt(bufmax) int bufmax; {		/* Fill one packet buffer */
    register CHAR rt = t, rnext = next; /* register shadows of the globals */
    register CHAR *dp, *odp, *p1, *p2;	/* pointers... */
    register int x;			/* Loop index. */
    register int a7;			/* Low 7 bits of character */
    static CHAR leftover[7] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0' };

    if (first == 1) {		/* If first time thru...  */
	ffc = 0;
	first = 0;		/* remember, */
	*leftover = '\0';   	/* discard any interrupted leftovers, */
	/* get first character of file into t, watching out for null file */
	if (memstr) {
	    if ((rt = *memptr++) == '\0') { /* end of string ==> EOF */
		first = -1;
	        size = 0;
		debug(F100,"getpkt: empty string","",0);
		return (0);
	    }
	} else {
	    if ((x = zminchar()) < 0) { /* End of file or input error */
		first = -1;
	        size = 0;
		if (x == -2) {		/* Error */
		    debug(F100,"getpkt: input error","",0);
		    cxseen = 1;
		} else debug(F100,"getpkt: empty file","",0);
		return(0);
	    }
	    ffc++;			/* Count a file character */
	    rt = x;
	    debug(F101,"getpkt zminchar","",rt);
	}

#ifndef NOCSETS
	debug(F101,"getpkt about to call translate function","",rt);
	if (!binary && sx) rt = (*sx)(rt); /* Translate */
	debug(F101," translate function returns","",rt);
#endif /* NOCSETS */

	rt &= fmask;			/* bytesize mask */

	/* PWP: handling of NLCHAR is done later (in the while loop)... */

    } else if ((first == -1) && (*leftover == '\0')) /* EOF from last time? */
        return(size = 0);

    /* Do any leftovers */

    dp = data;
    for (p1 = leftover; (*dp = *p1) != '\0'; p1++, dp++) /* copy leftovers */
    	;
    *leftover = '\0';
    if (first == -1)
      return(size = (dp - data));	/* Handle final leftovers */

    /* Now fill up the rest of the packet. */
    rpt = 0;				/* Clear out any old repeat count. */
    while (first > -1) {		/* Until EOF... */
	if (memstr) {			/* get next character */
	    if ((rnext = *memptr++) == '\0') { /* end of string ==> EOF */
		first = -1;		/* Flag eof for next time. */
	    } else {
#ifndef NOCSETS
		if (!binary && sx) rnext = (*sx)(rnext); /* Translate */
#endif /* NOCSETS */
		ffc++;
		rnext &= fmask;		/* Bytesize mask. */
	    }
	} else {
	    if ((x = zminchar()) < 0) { /* End of file or error */
		first = -1;		/* Flag eof for next time. */
		if (x == -2) cxseen = 1; /* If error, cancel this file */
	    } else {
#ifndef NOCSETS
		if (!binary && sx) x = (*sx)(x); /* Translate */
#endif /* NOCSETS */
		rnext = x & fmask;	/* Bytesize mask. */
		ffc++;			/* Count it */
	    }
	}

	/* PWP: handling of NLCHAR is done in a bit...  */

	odp = dp;			/* Remember current position. */

	/* PWP: the encode() procedure, in-line (for speed) */
	if (rptflg) {			/* Repeat processing? */
	    if (
#ifdef NLCHAR
		/*
		 * PWP: this is a bit esoteric, so bear with me.
		 * If the next char is really CRLF, then we cannot
		 * be doing a repeat (unless CR,CR,LF which becomes
		 * "~ <n-1> CR CR LF", which is OK but not most efficient).
		 * I just plain don't worry about this case.  The actual
		 * conversion from NL to CRLF is done after the rptflg if...
		 */
	    (binary || (rnext != NLCHAR)) &&
#endif /* NLCHAR */
	    rt == rnext && (first == 0)) { /* Got a run... */
		if (++rpt < 94) {	/* Below max, just count */
		    continue;		/* go back and get another */
		}
		else if (rpt == 94) {	/* Reached max, must dump */
		    *dp++ = rptq;
		    *dp++ = tochar(rpt);
		    rptn += rpt;
		    rpt = 0;
		}
	    } else if (rpt == 1) {	/* Run broken, only 2? */
		/*
		 * PWP: Must encode two characters.  This is handled
		 * later, with a bit of blue smoke and mirrors, after
		 * the first character is encoded.
		 */
	    } else if (rpt > 1) {	/* More than two */
		*dp++ = rptq;	/* Insert the repeat prefix */
		*dp++ = tochar(++rpt);	/* and count. */
		rptn += rpt;
		rpt = 0;		/* Reset repeat counter. */
	    }
	}

#ifdef NLCHAR
	/*
	 * PWP: even more esoteric NLCHAR handling.  Remember, at
	 * this point t may still be the _system_ NLCHAR.  If so,
	 * we do it here.
	 */
	if (!binary && (rt == NLCHAR)) {
	    *dp++ = myctlq;	/* just put in the encoding directly */
	    *dp++ = 'M';		/* == ctrl(CR) */
	    if ((dp-data) <= bufmax) odp = dp;	/* check packet bounds */
	    rt = LF;
	}
#endif

	/* meta control stuff fixed by fdc */
	a7 = rt & 0177;			/* Low 7 bits of character */
	if (ebqflg && (rt & 0200)) {	/* Do 8th bit prefix if necessary. */
	    *dp++ = ebq;
	    rt = a7;
	}
	if ((a7 < SP) || (a7 == DEL)) { /* Do control prefix if necessary */
	    *dp++ = myctlq;
	    rt = ctrl(rt);
	}
	if (a7 == myctlq)		/* Prefix the control prefix */
	    *dp++ = myctlq;

	if ((rptflg) && (a7 == rptq))	/* If it's the repeat prefix, */
	    *dp++ = myctlq;		/* quote it if doing repeat counts. */

	if ((ebqflg) && (a7 == ebq))	/* Prefix the 8th bit prefix */
	    *dp++ = myctlq;		/* if doing 8th-bit prefixes */

	*dp++ = rt;			/* Finally, insert the character */

	if (rpt == 1) {			/* Exactly two copies? */
	    rpt = 0;
	    p2 = dp;			/* save current size temporarily */
	    for (p1 = odp; p1 < p2; p1++) /* copy the old chars over again */
		*dp++ = *p1;
	    if ((p2-data) <= bufmax) odp = p2; /* check packet bounds */
	}
	rt = rnext;			/* Next is now current. */
	if ((dp-data) >= bufmax) {	/* If too big, save some for next. */
	    size = (dp-data);
	    *dp = '\0';			/* mark (current) the end. */
	    if ((dp-data) > bufmax) {	/* if packet is overfull */
		for (p1 = leftover, p2=odp; (*p1 = *p2) != '\0'; p1++,p2++)
		    ;
		debug(F111,"getpkt leftover",leftover,size);
		debug(F101," osize","",(odp-data));
		size = (odp-data);	/* Return truncated packet. */
		*odp = '\0';		/* mark real end */
	    } else {			/* If the packet is exactly full, */
		debug(F101,"getpkt exact fit","",size);
	    }
	    t = rt; next = rnext;	/* save for next time */
	    return(size);
	}
    }					/* Otherwise, keep filling. */
    size = (dp-data);
    *dp = '\0';				/* mark (current) the end. */
    debug(F111,"getpkt eof/eot",data,size); /* Fell thru before packet full, */
    return(size);		     /* return partially filled last packet. */
}

/*
 * Decodes the data pointed to by the global pointer rdatap.
 */
static
decode() {
    unsigned a, a7, b8;

    while ((a = *rdatap++) != '\0') {
	rpt = 1;			/* Initialize repeat count. */
	if (rptflg) {			/* Repeat processing? */
	    if (a == rptq) {		/* Yes, have repat prefix? */
		rpt = unchar(*rdatap++); /* Yes, get count. */
		a = *rdatap++;		/* and following character. */
	    }
	}
	b8 = 0;				/* Assume 8th bit not on. */
	if (ebqflg) {			/* Doing 8th-bit prefixing? */
	    if (a == ebq) {		/* Yes, have 8th-bit prefix? */
		b8 = 128;		/* Yes, remember bit 8 on */
		a = *rdatap++;		/* and get following character. */
	    }
	}
	if (a == rctlq) {		/* Is it control prefix? */
	    a = *rdatap++;		/* Yes, get next character */
	    a7 = a & 127;		/* and its low 7 bits. */
	    if (a7 > 62 && a7 < 96)	/* Encoded control character? */
	        a = ctrl(a);		/* Yes, controllify */
	}
	a |= b8;			/* OR in the 8th bit. */
	if (text)
	    if (a == '\r') continue;	/* Discard carriage returns. */
	for (; rpt > 0; rpt--) {
	    if (osp)
	        *osp++ = a;
	    else if (xflag)		/* If flagged... */
		tchar(a);		/* ... output to terminal */
	    else {
	        if (zmchout(a) < 0) return -1; /* Output the character. */
	        ffc++;
	    }
	}
    }
    tflush();				/* Flush out message (if xflag) */
    return(0);
}

static
encstr(s) char *s; {			/* Fill a packet from the string */
    first = 1;				/* Start lookahead. */
    memptr = (CHAR *) s;		/* Set input string pointer */
    memstr = 1;
    (void) getpkt(spsiz);		/* Fill a packet */
    memstr = 0;				/* Reset input string pointer */
    return(size);			/* Return data field length */
}

static
decstr(s) char *s; {			/* Decode packet data into a string */
    osp = s;				/* Set output string pointer */
    (void) decode();			/* Decode the string */
    *osp = '\0';			/* Terminate the string */
    osp = NULL;				/* Reset output string pointer */
}

static
spar(s) CHAR *s; {			/* Set parameters */
    int x;

    s--;				/* Line up with field numbers. */

    /* Limit on size of outbound packets */
    x = (rln >= 1) ? unchar(s[1]) : 80;
    spsiz = (x < 10) ? 80 : x;

    /* Timeout on inbound packets */
    x = (rln >= 2) ? unchar(s[2]) : 5;
    stimo = (x < 0) ? 5 : x;


    /* Outbound padding */
    spadn = 0; spadc = '\0';
    if (rln >= 3) {
	spadn = unchar(s[3]);
	if (rln >= 4)
	    spadc = ctrl(s[4]);
    }

    /* Outbound packet terminator */
    seol = (rln >= 5) ? unchar(s[5]) : '\r';
    if (seol < 2 || seol > 31) seol = '\r';

    /* Control prefix */
    x = (rln >= 6) ? s[6] : '#';
    rctlq = ((x > 32 && x < 63) || (x > 95 && x < 127)) ? x : '#';

    /* 8th-bit quoting */
    rq = (rln >= 7) ? s[7] : 0;
    if (rq == 'Y') rqf = 1;
    else if ((rq > 32 && rq < 63) || (rq > 95 && rq < 127)) rqf = 2;
    else rqf = 0;

    switch (rqf) {
	case 0: ebqflg = 0; break;
	case 1: if (parity) { ebqflg = 1; ebq = '&'; } break;
	case 2: if (ebqflg = (ebq == sq || sq == 'Y')) ebq = rq;
    }

    /* Block check */
    x = 1;
    if (rln >= 8) {
	x = s[8] - '0';
	if (x < 1 || x > 3) x = 1;
    }
    bctr = x;

    /* Repeat prefix */
    if (rln >= 9) {
	rptq = s[9];
	rptflg = ((rptq > 32 && rptq < 63) || (rptq > 95 && rptq < 127));
    } else rptflg = 0;

    /* Capabilities */
    atcapu = lpcapu = swcapu = 0;	/* No capabilities by default	*/
    if (rln >= 10) {
	x = unchar(s[10]);
	atcapu = (x & atcapb) && atcapr;	/* Attribute packets */
	lpcapu = (x & lpcapb) && lpcapr;	/* Long packets */
	swcapu = (x & swcapb) && swcapr;	/* Sliding windows */
	for (capas = 10; (unchar(s[capas]) & 1) && (rln >= capas); capas++)
	    ;					/* Skip to capas + 1 */
    }

    /* Long packets */
    if (lpcapu) {			/* Flag set above */
	if (rln > capas+2) {
	    x = unchar(s[capas+2]) * 95 + unchar(s[capas+3]);
	    spsiz = x > MAXSP ? MAXSP : x;
	}
	if (spsiz < 10) spsiz = 80;	/* Be defensive... */
    }

    /* Sliding windows */
    if (swcapu) {
	if (rln > capas+1) {
	    x = unchar(s[capas+1]);
	    wsize = x > MAXWS ? MAXWS : x;
	} else
	    wsize = 1;
    }
}

/* Fill the array with my send-init parameters */

static char *
rpar() {
    data[1] = tochar(DRPSIZ);		/* Biggest packet I can receive */
    data[2] = tochar(rtimo);		/* When I want to be timed out */
    data[3] = tochar(rpadn);		/* How much padding I need */
    data[4] = ctrl(rpadc);		/* Padding character I want */
    data[5] = tochar(reol);		/* End-of-Line character I want */
    data[6] = sctlq;			/* Control-Quote character I send */
    switch(rqf) {			/* 8th-bit prefix */
	case -1:
	case  1: if (parity) ebq = sq = '&'; break;
	case  0:
	case  2: break;
    }
    data[7] = sq;
    data[8] = bctr + '0';		/* Block Check Type */
    if (rptflg) data[9] = rptq; else data[9] = '~';
    data[10] = tochar(atcapr?atcapb:0 | lpcapr?lpcapb:0 | swcapr?swcapb:0);
    capas = 10;
    data[capas+1] = tochar(swcapr ? wsize : 0);	/* Window size */
    data[capas+2] = tochar(rpsiz / 95);	/* Long packet size */
    data[capas+3] = tochar(rpsiz % 95);	/* ... */
    data[capas+4] = '\0';
    return((char *) (data+1));		/* Return a pointer to the string */
}

/*
 * proto()--Kermit protocol entry point.  Set your start state and call
 * this, NOT wart().  Modified to set long packets capability on the
 * basis of the packet size set in the external user interface.
 */
void
proto()
{
#ifdef XPRKERMIT
    struct XPR_UPDATE xpru;

    xpru.xpru_protocol = "XPR-Kermit";
    xpru.xpru_updatemask = XPRU_PROTOCOL;
    calla(xupdate, &xpru);
    zclear();
#endif
    sndpkt = malloc(MAXSP+100);
    rcvpkt = malloc(MAXRP+200);
    data = malloc(MAXSP+50);
    zinbuffer = malloc(INBUFSIZE);
    zoutbuffer = malloc(OBUFSIZE);
    if (urpsiz > 94) {				/* Long packets? */
	rpsiz = (urpsiz > MAXRP - 20 ? MAXRP - 20 : urpsiz);
	lpcapr = 1;				/* Request long packets */
    } else {					/* No long packets */
	lpcapr = 0;
	if (urpsiz < 10)			/* Too small?	*/
	    rpsiz = 80;
	else
	    rpsiz = DRPSIZ;
    }
    urpsiz = rpsiz;
    cx = cz = 0;				/* Haven't aborted yet */
    if (bctr < 1)
	bctr = 1;			/* Legal block check? */
    if (bctr > 3)
	bctr = 3;
    if (sndpkt == NULL || rcvpkt == NULL || data == NULL || zinbuffer == NULL ||
        zoutbuffer == NULL)
#ifdef XPRKERMIT
	ST_Display_String(STMsg,"Can't allocate memory");
#else
	tmsgl("Can't allocate memory for Kermit!!");
#endif
    else
	wart();
    if (sndpkt) { free(sndpkt); sndpkt = NULL; }
    if (rcvpkt) { free(rcvpkt); rcvpkt = NULL; }
    if (data) { free(data); data = NULL; }
    if (zinbuffer) { free(zinbuffer); zinbuffer = NULL; }
    if (zoutbuffer) { free(zoutbuffer); zoutbuffer = NULL; }
}

/*
 * That ends the system-independent Kermit modules.  What follows
 * are the system-dependent ones.
 */

/*
 * File I/O modules for XPR Kermit.
 */
#define EOF -1
typedef struct FILE FILE;

static FILE *ifp = NULL, *ofp = NULL;

static
zclear()
{
    ifp = NULL; ofp = NULL;
}

static
zopeni(name) char *name; {

    zincnt = 0;
    ifp = (FILE *) callaa(xfopen, name, "r");
    if (ifp == NULL)
	return -1;
    else
	return 0;
}

#include <dos/dos.h>
#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>

static
zopeno(name) char *name; {

    zoutcnt = 0;
    zoutptr = zoutbuffer;

    ofp = (FILE *) callaa(xfopen, name, "w");
    if (ofp == NULL)
	return -1;
    else
	return 0;
}

static
zclosi() {
    int x;

    if (ifp == NULL)
	return 0;
    x = calla(xfclose, ifp);
    ifp = NULL;
    if (x < 0)
	return -1;
    else
	return 0;
}

static
zcloso(discard) int discard; {
    int x;

    if (ofp == NULL)
	return 0;
    if (zoutcnt > 0)
        if (zoutdump() != 0)
            return -1;
    x = calla(xfclose, ofp);
    ofp = NULL;
    if (x < 0)
	return -1;
    else if (discard) {
	if (xunlink != NULL) {
	    if (calla(xunlink, myname) < 0) {
		return -1;
	    }
	}
	return 0;
    }
    return 0;
}

#include <ctype.h>

extern int convert;		/* 0 for literal files, 1 for translate */

/*  Z R T O L  --  Convert remote filename into local form  */

/*  For AMIGA, this means changing uppercase letters to lowercase.  */

zrtol(name,name2,mywarn) char *name, *name2; int mywarn; {
    if (convert) {
        for ( ; *name != '\0'; name++) {
            *name2++ = isupper(*name) ? tolower(*name) : *name;
        }
        *name2 = '\0';
    } else
        strcpy(name2,name);
}

/* name from local to remote format */

static
zltor(s1,s2) char *s1, *s2; {
    char *EndPath();
    char dotseen = 0;

    if (!convert)
	strcpy(s2, s1);
    else {
	s1 = EndPath(s1);	/* strip dir/disk */
	while (*s1 != '\0') {
	    if (islower(*s1))
		*s2 = toupper(*s1);
	    else if (isalnum(*s1))
		*s2 = *s1;
	    else if (*s1 == '.')
		if (!dotseen) {
		    dotseen = 1;
		    *s2 = *s1;
		} else
		    *s2 = 'X';
	    /* else a character we're not prepared to handle. */
	    s1++; s2++;
    	}
	*s2 = '\0';
    }
}

/*
 * System-dependent function to do buffered input and output.
 * Text conversion now done in the packet routines.
 */
static
zinfill(void) {
    zincnt = calladda(xfread, zinbuffer, sizeof (char), INBUFSIZE, ifp);
    if (zincnt == 0) return (-1); /* end of file */
    zinptr = zinbuffer;	   /* set pointer to beginning, (== &zinbuffer[0]) */
    zincnt--;			/* one less char in buffer */
    return((int)(*zinptr++) & 0377); /* because we return the first */
}

static
zoutdump(void) {
    zoutptr = zoutbuffer;		/* reset buffer pointer in all cases */
    if (zoutcnt == 0) {			/* nothing to output */
	return(0);
    } else if (zoutcnt < 0) {		/* unexpected negative value */
	zoutcnt = 0;			/* reset output buffer count */
	return(-1);			/* and fail. */
    }
    if (calladda(xfwrite, zoutbuffer, sizeof (char), zoutcnt, ofp)) {
	zoutcnt = 0;			/* reset output buffer count */
	return(0);			/* things worked OK */
    } else {
	zoutcnt = 0;			/* reset output buffer count */
	return(-1);			/* error return */
    }
}

int ttol(s, n)
char *s;
int n;
{
	long status;

	status = callad(xswrite, s, (long) n);
	if (status == 0) return(n);
	else return(-1);
}

#include "timer.h"
#include <clib/exec_protos.h>
#include <pragmas/exec_pragmas.h>

#ifdef MYREAD
/* Private buffer for myread() and its companions.  Not for use by anything
 * else.  ttflui() is allowed to reset them to initial values.  ttchk() is
 * allowed to read my_count.
 *
 * my_item is an index into mybuf[].  Increment it *before* reading mybuf[].
 *
 * A global parity mask variable could be useful too.  We could use it to
 * let myread() strip the parity on its own, instead of stripping sign
 * bits as it does now.
 */

#define MYBUFLEN 256
static CHAR mybuf[MYBUFLEN];		/* Buffer, including push back */
static int my_count = 0;		/* Number of chars still in mybuf */
static int my_item = -1;		/* Last index read from mybuf[] */

/* myread() -- Efficient read of one character from communications line.
 *
 * Uses a private buffer to minimize the number of expensive read() system
 * calls.  Essentially performs the equivalent of read() of 1 character, which
 * is then returned.  By reading all available input from the system buffers
 * to the private buffer in one chunk, and then working from this buffer, the
 * number of system calls is reduced in any case where more than one character
 * arrives during the processing of the previous chunk, for instance high
 * baud rates or network type connections where input arrives in packets.
 * If the time needed for a read() system call approaches the time for more
 * than one character to arrive, then this mechanism automatically compensates
 * for that by performing bigger read()s less frequently.  If the system load
 * is high, the same mechanism compensates for that too.
 *
 * myread() is a macro that returns the next character from the buffer.  If the
 * buffer is empty, mygetbuf() is called.  See mygetbuf() for possible error
 * returns.
 *
 * This should be efficient enough for any one-character-at-a-time loops.
 * For even better efficiency you might use memcpy()/bcopy() or such between
 * buffers (since they are often better optimized for copying), but it may not
 * be worth it if you have to take an extra pass over the buffer to strip
 * parity and check for CTRL-C anyway.
 *
 * Note that if you have been using myread() from another program module, you
 * may have some trouble accessing this macro version and the private variables
 * it uses.  In that case, just add a function in this module, that invokes the
 * macro.
 */
#define myread(timo)  (--my_count < 0 ? mygetbuf(timo) : 255 & (int)mybuf[++my_item])

/*
 * System-dependent mygetbuf() routine.  Fills buffer with as many characters
 * as it can get, provided it can get the next one within timo seconds.
 * Returns the number of characters read, or 0 if none were read within
 * timo seconds, or a negative value if some other error occured.
 */
static int
myfillbuf(int timo) {
	long n;

	/*
	 * Find out how many characters are available, if any.
	 * We can only do this if xsquery is non-NULL.
	 */
	if (xsquery != NULL && (n = (*xsquery)()) > 0) {
	    calladd(xsread, mybuf, n, 0L);
	    return n;
	} else
		/*
		 * xsread() returns exactly what we want: zero if no
		 * character is received within timo seconds, and -1
		 * on error.  Might as well ask for a bufferful,
		 * provided first one arrives soon enough.
		 */
		return(calladd(xsread, &mybuf[0], (long) MYBUFLEN ,
                               timo*1000000L));
}

/* mygetbuf(timo) -- Fill buffer for myread() and return first character,
 * within timo seconds.
 *
 * This function is what myread() uses when it can't get the next character
 * directly from its buffer.  First, it calls a system dependent myfillbuf()
 * to read at least one new character into the buffer, and then it returns
 * the first character just as myread() would have done.  This function also
 * is responsible for all error conditions that myread() can indicate.
 *
 * Returns: When OK	=> a positive character, 0 or greater.
 *          When TIMEOUT=> -1
 *	    When error	=> -2.
 *
 */
static int
mygetbuf(int timo) {
    my_count = myfillbuf(timo);
    if (my_count <= 0)
      return(my_count - 1);
    --my_count;
    return(255 & (int)mybuf[my_item = 0]);
}
#endif /*MYREAD*/

/*
 * T T I N L
 *
 * Return a line in the buffer pointed to by s from the serial line.
 * The line must end with eol and be no longer than max characters.
 * timeout is a timeout interval in seconds.
 *
 * Returns
 *	> 0	Success--number of characters read
 *	= 0	Shouldn't happen!
 * 	-1	Timeout
 *	-2	Error of some other kind
 *
 * This version for XPR Kermit.  We ignore negative returns from
 * the xpr_sread() function, preferring to take care of these
 * via the timeout mechanism.
 */
int ttinl(s, max, eol, timeout)
char *s;
int max, timeout, eol;
{
	int x = 0;
	long i;
	struct timerequest *Timereq;
	unsigned mask;

	*s = '\0';
	mask = (parity ? 0177 : 0377);
	/*
	 * Set up and post timer request for overall timeout.
	 * As a compromise between correct and efficient, I only check
	 * the overall timeout if a one-character read with a one second
	 * timeout fails first.
	 */
	if (!(Timereq = CreateTimer(UNIT_VBLANK)))
		return -1;
	Timereq->tr_time.tv_secs = timeout;
	Timereq->tr_time.tv_micro = 0;
	Timereq->tr_node.io_Command = TR_ADDREQUEST;
	SendIO((struct IORequest *) Timereq);
#ifndef MYREAD
	/*
	 * Willy Langeveld assures me that, since serial.device does its
	 * own buffering, calling xsread once for each character isn't
	 * going to take too long.
	 *
	 */
	for (x = 0; x < max; ) {
		if ((i = calladd(xsread, s, 1L, 1000000L)) < 0) {
			if (chkint() < 0) {
				x =  -2;
				goto leave;
			}
		} else if (i == 1) {
			x++;
			if ((*s++ &= mask) == eol)
				break;
		} else if (CheckIO((struct IORequest *) Timereq)) { /* timeout */
			x = -1;			/* Flag timeout abort */
			goto leave;
		}
	}
	*s = '\0';				/* Normal termination */
    leave:					/* Common cleanup code */
	AbortIO((struct IORequest *) Timereq);
	WaitIO((struct IORequest *) Timereq);
	DeleteTimer(Timereq);
	return(x);
#else
	/*
	 * The following call to myread sets up a 1 second per-character
	 * timeout.  We have to be careful not to return from ttinl until
	 * the entire timeout interval has passed, however.
	 */

	for (x = 0; x < max; ) {
		if ((*s = myread(1)) < -1) {
			/*
			 * The following code deals with the case that
			 * xsread() returns a negative value if the
			 * user has clicked on the "Cancel File" or
			 * "Cancel Batch" gadgets.  However, we don't
			 * want to abort receiving the current packet
			 * in this case;  calling chkint() now sets
			 * the global cx and/or cz flags, but in the
			 * case of a complete abort, chkint() returns
			 * a negative value.
			 */
			if (chkint() < 0) {
				x = -2;		/* Error */
				goto leave;
			}
		} else if (CheckIO((struct IORequest *) Timereq)) { /* timeout */
			x = -1;			/* Flag timeout abort */
			goto leave;
		} else if (*s >= 0) {		/* Successful get of character */
			x++;
			if ((*s++ &= mask) == eol)
				break;
		}
		
	}
	*s = '\0';				/* Normal termination */
    leave:					/* Common cleanup code */
	AbortIO((struct IORequest *) Timereq);
	WaitIO((struct IORequest *) Timereq);
	DeleteTimer(Timereq);
	return(x);
#endif /*MYREAD*/
}

/*
 * This is the XPR Kermit version of chkint.  We allow multiple levels
 * of interrupt, if available.
 */
int chkint(void)			/* Check for console interrupts. */
{
	int x;

	x = (*xchkabort)();
	switch (x) {
		case 0:			/* No abort pending */
			return 0;
		case 1:			/* Abort file only. */
			cx = 1;
			break;
		case 2:			/* Abort file and batch. */
			cx = cz = 1;
			break;
		case -1:		/* Emergency escape -- kill all */
			cx = cz = 1;
			return -1;
		default:
			;
	}
	return 1;
}

void ttflui()
{
	(void) (*xsflush)();
#ifdef MYREAD
	my_count = 0;
	my_item = -1;
#endif
}

void sleep(sec)
int sec;
{
	struct timeval tv;

	if (sec) {
		tv.tv_secs = sec;
		tv.tv_micro = 0;
		MyDelay(&tv, UNIT_VBLANK);
	}
}

#define MAXMESSAGE 50
static char message[MAXMESSAGE+1];
static int nmess = 0;
static long errors = 0, blocks = 0;

/*  screen(f,c,n,s)
      f - argument descriptor
      c - a character or small integer
      n - a long integer
      s - a string.
 Fill in this routine with the appropriate display update for the system.
 This version for XPR Kermit.
*/

screen(f,c,n,s) int f; long n; char c; char *s; {
    struct XPR_UPDATE xpru;

    if (!local || xflag) return;
    switch (f) {
    case SCR_PT:			/* Packet Transferred. */
	if (c == 'Y') return;		/* Don't bother with ACK's. */
	xpru.xpru_packettype = c;
	xpru.xpru_blocksize = strlen(s);
	xpru.xpru_blocks = ++blocks;
	if (c == 'D' || c == 'Z') {	/* If data or EOF packet ... */
	    xpru.xpru_bytes = ffc;	/* ... update transfer count */
	    xpru.xpru_updatemask = XPRU_BYTES;
	} else if (c == 'T') {		/* Timeout */
	    xpru.xpru_timeouts = timeouts++;
	    xpru.xpru_updatemask = XPRU_TIMEOUTS;
        } else
	    xpru.xpru_updatemask = 0;
	xpru.xpru_updatemask |= XPRU_PACKETTYPE | XPRU_BLOCKSIZE |
		XPRU_BLOCKS;
	break;
    case SCR_EM:			/* Error message. */
	xpru.xpru_errormsg = s;
	xpru.xpru_updatemask = XPRU_ERRORMSG;
	break;
    default:
	return;
    }
    calla(xupdate, &xpru);
}

void tchar(c)
char c;
{
	struct XPR_UPDATE xpru;

	if (c == '%') {		/* retry */
		xpru.xpru_errors = ++errors;
		xpru.xpru_updatemask = XPRU_ERRORS;
	} else if (c == '#') { /* fake it */
		xpru.xpru_errors = errors = 0;
		xpru.xpru_blocks = blocks = 0;
		nmess = 0;			/* Make sure message empty. */
		xpru.xpru_updatemask = XPRU_BLOCKS | XPRU_ERRORS;
	} else {
		message[nmess++] = c;
		if (nmess >= MAXMESSAGE)
			tflush();
		return;
	}
	calla(xupdate, &xpru);
}

static
tflush()
{
	struct XPR_UPDATE xpru;

	if (nmess <= 0)
		return;
	message[nmess] = '\0';
	xpru.xpru_errormsg = message;
	xpru.xpru_updatemask = XPRU_ERRORMSG;
	calla(xupdate, &xpru);
	nmess = 0;
}

ST_Display_String(Item, S)
int Item;
char *S;
{
  struct XPR_UPDATE xpru;

  switch (Item) {
  case STMsg:
  	xpru.xpru_msg = S;
	xpru.xpru_updatemask = XPRU_MSG; break;
  case STFile:
  	xpru.xpru_filename = S;
	xpru.xpru_updatemask = XPRU_FILENAME; break;
  case STUplSize:
  	xpru.xpru_filesize = callad(xfinfo, S, 1L);
	xpru.xpru_updatemask = XPRU_FILESIZE; break;
  default:
	return;
  }
  calla(xupdate, &xpru);
}

ST_Display_CRC(Item)
int Item;
{
  struct XPR_UPDATE xpru;

  switch (Item) {
  case 1:
  	xpru.xpru_blockcheck = "Check-6"; break;
  case 2:
  	xpru.xpru_blockcheck = "Check-12"; break;
  case 3:
  	xpru.xpru_blockcheck = "CRC-16"; break;
  default:
	return;
  }
  xpru.xpru_updatemask = XPRU_BLOCKCHECK;

  calla(xupdate, &xpru);
}

extern char *p_pattern;

#ifndef TRUE
#define TRUE	1
#define FALSE	0
#endif

static int firsttime = TRUE;

int gnfile(fname, len)
char *fname;
int len;	/* it is 50 */
{
	char buffer[256];
	static long gnstate;

	if (firsttime) {
		gnstate = callaa(xffirst, buffer, p_pattern);
		if (gnstate) firsttime = FALSE;
	} else {
		gnstate = calldaa(xfnext, gnstate, buffer, p_pattern);
		if (gnstate == 0L) firsttime = TRUE;
	}
	if (gnstate && (int)strlen(buffer) < len) {
		strcpy(fname, buffer);
		return((int)gnstate);
	} else	return(0);
}

void gninit(void) {
	firsttime = TRUE;
}

char *EndPath(p)
char *p;
{
	char *q;

	q = p;
	while (*p != '\0') {
		if ((*p == '/') || (*p == ':'))
			q = p + 1;
		p++;
	}
	return(q);
}
