/*************************************************************
 * vt100 terminal emulator - KERMIT protocol support
 *			:ts=8
 *
 *	v2.9 ACS - Kermit shouldn't NAK packet 0 but timeout waiting for
 *		   send-init then NAK if necessary; terminate each packet with
  *		   EOL only; don't sendstring("\r") but sendchar() it.
 *	v2.8a 880230 ACS - saybye() won't do anything if not in kermit
 *			  mode.
 *	v2.7 870825 ACS - Fixed the "multiple-send" problem in
 *			  doksend() et al; show status using the *InfoMsg*()
 *			  routines in window.c; fixed erroneous calls to
 *			  spack() and rpack(); better error handling.
 *	v2.6 870227 DBW - bug fixes for all the stuff in v2.5
 *	v2.5 870214 DBW - more additions (see readme file)
 *	v2.4 861214 DBW - lots of fixes/additions (see readme file)
 *	v2.3 861101 DBW - minor bug fixes
 *	v2.2 861012 DBW - more of the same
 *	v2.1 860915 DBW - new features (see README)
 *	     860901 ACS - Added eight bit quoting
 *	     860830 Steve Drew Wild card support, err recovry,bugs.
 *	     860823 DBW - Integrated and rewrote lots of code
 *	     860811 Steve Drew multi filexfer, bugs, status line ect..
 *	v2.0 860809 DBW - Major rewrite
 *	v1.1 860720 DBW - Switches, 80 cols, colors, bug fixes
 *	v1.0 860712 DBW - First version released
 *
 *************************************************************/

#include "vt100.h"

#define MAXPACKSIZ 94	    /* Maximum msgpkt size */
#define CR	   13	    /* ASCII Carriage Return */
#define LF	   10	    /* ASCII line feed */
#define SP	   32	    /* ASCII space */
#define DEL	  127	    /* Delete (rubout) */

#define MAXTRY	  5	   /* Times to retry a msgpkt */
#define MYQUOTE  '#'	   /* Quote character I will use */
#define MYRPTQ	 '~'	   /* Repeat quote character */
#define MYEBQ	 '&'	   /* 8th bit prefix character */
#define MYPAD	   0	   /* Number of padding charss I will need */
#define MYPCHAR    0	   /* Padding character I need (NULL) */
#define MYEOL	'\n'	/* End-Of-Line character I need */
#define IDOLONG	2	/* I do LONG packets! */

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

/* Global Variables */

int sending,	/* Indicates that we're sending, not receiving */
    lastpkt,	/* Last successful packet # sent/received */
    ulp,		/* Using LONG packets */
    size,	/* Size of present data */
    osize,	/* Size of last data entry */
    rpsiz,	/* Maximum receive msgpkt size */
    spsiz,	/* Maximum send msgpkt size */
    timint,	/* Time interval to wait */
    pad,		/* How much padding to send */
    n,		/* Packet number */
    tp,		/* total packets */
    numtry,	/* Times this msgpkt retried */
    retry,	/* total retries */
    oldtry,	/* Times previous msgpkt retried */
    sendabort,	/* flag for aborting send file  */
    rptflg,	/* are we doing repeat quoting */
    ebqflg,	/* are we doing 8th bit quoting */
    notfirst,	/* is this the first file received */
    first,	/* is this the first time in a file */
    rpt,		/* current repeat count */
    next,	/* what is the next character */
    t;		/* current character */
long totbytes;	/* total bytes xfered on this file */

char state,	/* Present state of the automaton */
    padchar,	/* Padding character to send */
    eol,		/* End-Of-Line character to send */
    quote,	/* Quote character in incoming data */
    rptq,	/* Quote character for repeats */
    ebq,		/* Quote character for 8th bit quoting */
    ackpkt[MAXPACKSIZ+20],	/* ACK/NAK packet buffer */
    *msgpkt = NULL,		/* Message Packet buffer is AllocMem()d */
    *spackbuf = NULL,	/* Dynamically allocated buffer for spack() */
    filnam[40],	/* remote file name */
    snum[10],
    mainmode[10];

FILE *fp;	/* file for send/receive */

static void spack(), print_our_err(), print_host_err(),
	    dostats(), ClearBuffer();

char *
getfname(name)	/* returns ptr to start of file name from spec */
char *name;
{
    int l;

    l = strlen(name);
    while(l && name[l] != '/' && name[l] != ':') l--;
    if (name[l] == '/' || name[l] == ':') l++;
    return(name += l);
}

doksend(file,more)
char *file;
int more;
{
    int amount, c, wild;
    char *p, **list = NULL;

    msgpkt    = (char *)AllocMem((long)(MAXLONGPKS+20), MEMF_PUBLIC|MEMF_CLEAR);
    spackbuf  = (char *)AllocMem((long)(MAXLONGPKS+20), MEMF_PUBLIC|MEMF_CLEAR);
    sending = 1;
    if (!strcmp(file,"$")) { saybye(); return(USERABORT); }
    p = file;
    while(*p && *p != '*' && *p != '?') p++;
    if (*p) {
	wild = TRUE;
	list = expand(file, &amount);
	if (list == NULL) InfoMsg1Line("KERMIT: No wild card match");
    }
    else {
	wild = FALSE;
	amount = 1;
    }
    /*	  The "multiple send" problem is brought about by attempting to
    ** send multiple files in a single "batch" (via globbing, e.g. *.foo)
    ** to a remote kermit that is NOT in server mode.  A 'Z' packet
    ** (meaning end-of-file) is sent after each of the files with a 'B'
    ** packet (meaning end-of-batch) coming after the last 'Z' packet.
    ** The old code reset the packet # on each iteration.  We do it
    ** outside of the for loop. */
    n = lastpkt = 0;
    ulp = 0;	/* Assume we won't use LONG packets */
    for (c = 0; c < amount; c++) {
	if (wild == TRUE) p = list[c];
	    else  p = file;
	strcpy(filnam,getfname(p));
	ttime = TTIME_KERMIT;
	tp = retry = numtry = 0; totbytes = 0L;
	if ((fp = fopen(p,"r")) == NULL) {
	    InfoMsg2Line("KERMIT: Can't open send file:", p);
	    continue;
	}
	strcpy(mainmode,"SEND");
	ClearBuffer();

	/*  This is another piece of the multiple-send fix.  Sendsw() needs
	** to know 1) that this is the first file so it can send a send-init
	** packet and 2) if this is the last file so it can send a B packet
	** to indicate end-of-batch.  The last piece of the multiple-send fix
	** is in sendsw() itself. */
	if ( sendsw(c == 0, c >= (amount-1)) ) /* Successful send? */
	    ScrollInfoMsg(1);
	fclose(fp);
    }
    free_expand(list);
    FreeMem(spackbuf,	(long)(MAXLONGPKS+20));
    FreeMem(msgpkt,	(long)(MAXLONGPKS+20));
    msgpkt = spackbuf = NULL;
    return(TRUE);
}

dokreceive(file,more)
char *file;
int more;
{
    int retval;

    msgpkt    = (char *)AllocMem((long)(MAXLONGPKS+20), MEMF_PUBLIC|MEMF_CLEAR);
    spackbuf  = (char *)AllocMem((long)(MAXLONGPKS+20), MEMF_PUBLIC|MEMF_CLEAR);
    ttime = TTIME_KERMIT;
    sending = 0;
    if (!strcmp(file,"$")) { saybye(); return(USERABORT); }
    strcpy(filnam, file);
    if (server) strcpy(mainmode,"GET");
    else        strcpy(mainmode,"RECV");
    tp =  lastpkt = retry = n =  numtry = notfirst = 0; totbytes = 0L;
    ClearBuffer();
    retval  = recsw();
    FreeMem(spackbuf,	(long)(MAXLONGPKS+20));
    FreeMem(msgpkt,	(long)(MAXLONGPKS+20));
    msgpkt = spackbuf = NULL;
    return(retval);
}

sendsw(firstfile, lastfile)
int firstfile, lastfile; /* Multiple-send fix */
{
    char sinit(), sfile(), sdata(), seof(), sbreak();
    sendabort = 0;
    /* Multiple-send fix.  If this is the first file of the batch then enter
    ** send-init state otherwise just enter send-file state. */
    if(firstfile)
	state = 'S';
    else
	state = 'F';
    while(TRUE) {
	switch(state) {
	case 'S':   state = sinit();  break;
	case 'F':   state = sfile();  break;
	case 'D':   state = sdata();  break;
	case 'Z':   state = seof();   break;
	case 'B':   if (lastfile || sendabort) {
			/* Multiple-send fix.  If this is the last file then
			** send a B packet to indicate end-of-batch. */
			state = sbreak();
			break;
		     }
		     return(TRUE);	/* Otherwise, just return. */
	case 'C':   if (sendabort) return(FALSE);
		    else return(TRUE);
	case 'E':   dostats('E',"ERROR");  /* so print the err and abort */
		    print_host_err(ackpkt);
		    return(FALSE);
	case 'A':   if (timeout == USERABORT) {
			timeout = GOODREAD;
			n = (n+1)%64;
			sendabort = 1;
			dostats('A',"ABORT");
			strcpy(msgpkt, "D");
			state = 'Z';
			break;
		    }
		    if (timeout == TIMEOUT) dostats('A',"TMOUT");
		    else { /* protocol error dectected by us */
			dostats('A',"ERROR");
			print_our_err();
		    }
		    return(FALSE);
	default:    return(FALSE);
	}
    }
}

char sinit()
{
    int num, len;

    retry++;
    if (numtry++ > MAXTRY) return('A');
    spar(msgpkt);

    spack('S',n,13,msgpkt);
    switch(rpack(&len,&num,ackpkt)) {
    case 'N':	return(state);
    case 'Y':	if (n != num) return(state);
		rpar(ackpkt, len);
		if (eol == 0) eol = '\n';
		if (quote == 0) quote = MYQUOTE;
		numtry = 0;
		retry--;
		n = (n+1)%64;
		return('F');
    case 'E':	return('E');
    case FALSE:	if (timeout == USERABORT) state = 'A';
		return(state);
    default:	return('A');
    }
}

char sfile()
{
    int num, len;

    retry++;
    if (numtry++ > MAXTRY) return('A');

    spack('F',n,strlen(filnam),filnam);
    switch(rpack(&len,&num,ackpkt)) {
    case 'N':
	num = (--num<0 ? 63:num);
	if (n != num) return(state);
    case 'Y':
	if (n != num) return(state);
	numtry = 0;
	retry--;
	n = (n+1)%64;
	first = 1;
	size = getpkt();
	return('D');
    case 'E':
	return('E');
    case FALSE:	if (timeout == USERABORT) state = 'A';
		   return(state);
    default:
	return('A');
    }
}

char sdata()
{
    int num, len;

    retry++;
    if (numtry++ > MAXTRY) return('A');

    spack('D',n,size,msgpkt);
    switch(rpack(&len,&num,ackpkt)) {
    case 'N':
	num = (--num<0 ? 63:num);
	if (n != num) return(state);
    case 'Y':
	if (n != num) return(state);
	numtry = 0;
	retry--;
	n = (n+1)%64;
	if ((size = getpkt()) == 0) return('Z');
	return('D');
    case 'E':
	return('E');
    case FALSE: if (timeout == USERABORT) state = 'A';
		   return(state);
    default:
	return('A');
    }
}

char seof()
{
    int num, len;
    retry++;
    if (numtry++ > MAXTRY) return('A');

/*   if (timeout == USERABORT) {*/	/* tell host to discard file */
/*	timeout = GOODREAD;	*/
/*	  spack('Z',n,1,"D");	*/
/*	  }			*/
/*   else			*/
	spack('Z',n,sendabort,msgpkt);
    switch(rpack(&len,&num,ackpkt)) {
    case 'N':
	num = (--num<0 ? 63:num);
	if (n != num) return(state);
    case 'Y':
	if (n != num) return(state);
	numtry = 0;
	dostats('Z',"DONE");
	retry--;
	n = (n+1)%64;
	return('B');
    case 'E':	return('E');
    case FALSE:	return(state);
    default:	return('A');
    }
}

char sbreak()
{
    int num, len;
    retry++;
    if (numtry++ > MAXTRY) return('A');

    spack('B',n,0,msgpkt);
    switch (rpack(&len,&num,ackpkt)) {
    case 'N':
	num = (--num<0 ? 63:num);
	if (n != num) return(state);
    case 'Y':
	if (n != num) return(state);
	dostats('B', "DONE");
	numtry = 0;
	retry--;
	n = (n+1)%64;
	return('C');
    case 'E':	return('E');
    case FALSE:	return(state);
    default:	return ('A');
    }
}

/* timeout equals USERABORT so lets end the file and quit  */
/* when host receives 'Z' packet with "D" in data field he */
/* should discard the file.				   */
/*
sabort()
{
    dostats(' ',"ABORT");
    n = (n+1)%64;
    retry--;
    state = 'Z';
    while (state == 'Z') state = seof();
    while (state == 'B') state = sbreak();
    return(FALSE);
}
*/


recsw()
{
    char rinit(), rfile(), rdata();
    int first_time = 1;

    state = 'R';

    while(TRUE) {
	switch(state) {
	case 'R':	ulp = 0;	/* Assume we won't use LONG packets */
			state = rinit();
			break;
	case 'Z':
	case 'F':	state = rfile(first_time); first_time = 0;
			break;
	case 'D':	state = rdata();
			break;
	case 'C':	return(TRUE);
	case 'E':
	case 'A':   /* easy way to cleanly abort
			should really send and ACK
			with "X" in data field and
			wait for host to abort but
			not all kermits support
			this feature.		*/
			if (timeout == USERABORT){
			    /* send an error packet back   */
			    dostats('A',"ABORT");
			    spack('E',n,12,"User aborted");
		        }
		        else if (timeout == TIMEOUT) {
			    /* we timed out waiting */
			    /* will we need to spack here ?*/
			    dostats('A',"TMOUT");
			}
			/* must be 'E' from host or we detected a protocol 
			** error */
			else dostats('A',"ERROR");

			if (state == 'E') print_host_err(msgpkt);
			else if (timeout == GOODREAD) /* tell host why */
			    print_our_err();
			    /* will this kill all files ?*/
			do  {
			    ttime = 2;
			    readchar();
			}  while (timeout == GOODREAD);
			fclose(fp);
			sendchar('\r');
			return(FALSE);
	default:	return(FALSE);
	}
    }
}

char rinit()
{
    int len, num, temp;
    retry++;
    if (numtry++ > MAXTRY) return('A');

    if (server)
	spack('R',n,strlen(filnam),filnam);

    switch(rpack(&len,&num,msgpkt)) {
    case 'S':
	rpar(msgpkt, len);
	/*   Rpar() will set ulp if we can use long packets.  We can't use
	** that value right away cause we've gotta ACK with normal pkts. */
	temp = ulp; ulp = 0;
	spar(msgpkt);
	spack('Y',n,13,msgpkt);
	ulp = temp;	/* Restore using long pkts flag */
	oldtry = numtry;
	numtry = 0;
	retry--;
	n = (n+1)%64;
	return('F');
    case 'E':
	return('E');
    case 'N':		/* Other side NAKed us... */
	return(state); /* ...so try again	  */
    case FALSE:
	if (timeout == USERABORT) return('A');
	if (timeout == TIMEOUT)   return(state); /* Resend Rec-init on a timeout */
	spack('N',n,0,"");
	return(state);
    default:
	return('A');
    }
}

char rfile(first_time)
int first_time;
{
    int num, len, temp;
    USHORT a, a7, b8;
    char *fileptr, *buf;

    retry++;
    if (numtry++ > MAXTRY) return('A');

    switch(rpack(&len,&num,msgpkt)) {
    case 'S':
	if (oldtry++ > MAXTRY) return('A');
	if (num == ((n==0) ? 63:n-1)) {
	    /*   Rpar() will set ulp if we can use long packets.  We can't use
	    ** that value right away cause we've gotta ACK with normal pkts. */
	    temp = ulp; ulp = 0;
	    spar(msgpkt);
	    spack('Y',num,13,msgpkt);
	    ulp = temp;
	    numtry = 0;
	    return(state);
	}
	else return('A');
    case 'Z':
	if (oldtry++ > MAXTRY) return('A');
	if (num == ((n==0) ? 63:n-1)) {
	    spack('Y',num,0,"");
	    ScrollInfoMsg(1);
	    numtry = 0;
	    return(state);
	}
	else return('A');
    case 'F':
	if (num != n) return('A');
	if(!first_time) {	/* Scroll the Z packet line up */
	    dostats('Z', "DONE");
	    ScrollInfoMsg(1);
	}
	buf = msgpkt;
	fileptr = filnam;
	while ((a = *buf++) != '\0') { /* Terminator added by rpack() */
	    if (rptflg) {
		if (a == rptq) {
		    rpt = unchar(*buf++);
		    a = *buf++;
		}
	    }
	    b8 = 0;
	    if (ebqflg) {		/* 8th-bit prefixing? */
		if (a == ebq) {		/* Yes, got an 8th-bit prefix? */
		    b8 = 0200;		/* Yes, remember this, */
		    a = *buf++;		/* and get the prefixed character. */
		}
	    }
	    if (a == quote) {
		a  = *buf++;
		a7 = a & 0177;
		if ((a7 >= 0100 && a7 <= 0137) || a7 == '?') a = ctl(a);
	    }
	    a |= b8;
	    if (rpt == 0) rpt = 1;
	    if (p_mode == 1 && a == '\r') continue;
	    for (; rpt > 0; rpt--) *fileptr++ = a;
	    *fileptr = '\0';	/* Terminate the filename */
	}

	if (p_convert) {
	    char *p;
	    p = &filnam[0];
	    while (*p) { *p = tolower(*p); p++; }
	}
	if (notfirst) {
	    totbytes = 0L;
	    dostats('F',"RECV");
	}
	else {	/* is the first file so emit actual file name from host */
	    notfirst++;
	}
	if ((fp = fopen(filnam,"w")) == NULL) {
	    InfoMsg2Line("KERMIT: Unable to create file:", filnam);
	    strcpy(msgpkt,"VT100 - Kermit - cannot create file: ");
	    strcat(msgpkt,filnam);
	    spack('E',n,strlen(msgpkt),msgpkt); /* let host know */
	    dostats('E',"ERROR");
	    return ('\0');	 /* abort everything */
	}
	spack('Y',n,0,"");
	oldtry = numtry;
	numtry = 0;
	retry--;
	n = (n+1)%64;
	return('D');

    /* totaly done server sending no more */
    case 'B':
	if (num != n) return ('A');
	spack('Y',n,0,"");
	dostats('B', "DONE");
	ScrollInfoMsg(1);
	retry--;
	return('C');
    case 'E':
	return('E');
    case FALSE:
	if (timeout == USERABORT) return('A');
	spack('N',n,0,"");
	return(state);
    default:
	return ('A');
    }
}

char rdata()
{
    int num, len;
    retry++;
    if (numtry++ > MAXTRY) return('A');

    switch(rpack(&len,&num,msgpkt)) {
    case 'D':
	if (num != n) {
	    if (oldtry++ > MAXTRY) return('A');
	    if (num == ((n==0) ? 63:n-1)) {
	       spack('Y',num,6,msgpkt);
	       numtry = 0;
	       return(state);
	    }
	    else return('A');
	}
	decode();
	spack('Y',n,0,"");
	oldtry = numtry;
	numtry = 0;
	retry--;
	n = (n+1)%64;
	return('D');
    case 'Z':
	if (num != n) return('A');
	spack('Y',n,0,"");
	n = (n+1)%64;
	dostats('Z',"DONE");
	retry--;
	fclose(fp);
	return('Z');
    case 'F':
	if (oldtry++ > MAXTRY) return('A');
	if (num == ((n==0) ? 63:n-1)) {
	    spack('Y',num,0,"");
	    numtry = 0;
	    return(state);
	}
    case 'E':
	return('E');
    case FALSE:
	if (timeout == USERABORT) return('A');
	spack('N',n,0,"");
	return(state);
    default:
	return('A');
    }
}

static void
spack(type,num,len,data)
char type, *data;
int num, len;
{
    int i;
    char chksum, t;
    register char *bufp;

    if(sending && (lastpkt != num)) {
	tp++;
	lastpkt = num;
    }
    dostats(type,mainmode);
    bufp = spackbuf;
    ClearBuffer();

    for (i=1; i<=pad; i++) sendchar(padchar);

    *bufp++ = SOH;
    if(ulp && len > (MAXPACKSIZ-3))	/* Using long packets */
	t = tochar(0);
    else
	t = tochar(len+3);
    *bufp++ = t; chksum  = t;
    t = tochar(num);
    *bufp++ = t; chksum += t;
    *bufp++ = type; chksum += type;
    if(ulp && len > (MAXPACKSIZ-3)) {	/* Using long packets */
	unsigned int pl = len + 1;

	t = tochar(pl / 95);
	*bufp++ = t; chksum += t;
	t = tochar(pl % 95);
	*bufp++ = t; chksum += t;
	t = tochar((((chksum&0300) >> 6)+chksum)&077);
	*bufp++ = t; chksum += t;
    }
    for (i=0; i<len; i++) {
	*bufp++ = data[i]; chksum += data[i];
    }
    chksum = (((chksum&0300) >> 6)+chksum)&077;
    *bufp++ = tochar(chksum);
    if (eol)
	*bufp++ = eol; /* Use sender's requested end-of-line */
    else
	*bufp++ = '\r';
    *bufp   = '\0';
    sendstring(spackbuf);
}

rpack(len,num,data)
int *len, *num;
char *data;
{
    int i, done;
    char type, cchksum, rchksum;
    char t = '\0';

    do {
	t = readchar();
	if (timeout != GOODREAD) return(FALSE);
    } while (t != SOH);

    done = FALSE;
    while (!done) {
	t = readchar();
	if (timeout != GOODREAD) return(FALSE);
	if (t == SOH) continue;
	cchksum = t;
	*len = unchar(t)-3;
	t = readchar();
	if (timeout != GOODREAD) return(FALSE);
	if (t == SOH) continue;
	cchksum += t;
	*num = unchar(t);
	t = readchar();
	if (timeout != GOODREAD) return(FALSE);
	if (t == SOH) continue;
	cchksum += t;
	type = t;
	if((*len == -3) && ulp) {	/* Using long packets */
	    t = readchar();
	    if (timeout != GOODREAD) return(FALSE);
	    if (t == SOH) continue;
	    cchksum += t;
	    *len = unchar(t)*95;
	    t = readchar();
	    if (timeout != GOODREAD) return(FALSE);
	    if (t == SOH) continue;
	    cchksum += t;
	    *len += unchar(t);
	    (*len)--;
	    t = readchar();
	    if (timeout != GOODREAD) return(FALSE);
	    if (t == SOH) continue;
	    if(unchar(t) != ((((cchksum&0300) >> 6)+cchksum)&077)) return(FALSE);;
	    cchksum += t;
	}
	for (i=0; i<*len; i++) {
	    t = readchar();
	    if (timeout != GOODREAD) return(FALSE);
	    if (t == SOH) continue;
	    cchksum = cchksum + t;
	    data[i] = t;
	}
	data[*len] = 0;
	t = readchar();
	if (timeout != GOODREAD) return(FALSE);
	rchksum = unchar(t);
	t = readchar();
	if (timeout != GOODREAD) return(FALSE);
	if (t == SOH) continue;
	done = TRUE;
    }
    if(type != 'B' && type != 'Z')
	dostats(type,mainmode);
    cchksum = (((cchksum&0300) >> 6)+cchksum)&077;
    if (cchksum != rchksum) return(FALSE);
    if(!sending && (*num != lastpkt)) {
	tp++;
	lastpkt = *num;
    }
    return((int)type);
}

getpkt() {
    int i,eof;

    static char leftover[10] = { '\0', '\0', '\0', '\0', '\0',
				'\0', '\0', '\0', '\0', '\0' };

    if (first == 1) {
	first = 0;
	*leftover = '\0';
	t = getc(fp);
	if (t == EOF) {
	    first = 1;
	    return(size = 0);
	}
	totbytes++;
    }
    else if (first == -1) {
	first = 1;
	return(size = 0);
    }
    for (size = 0; (msgpkt[size] = leftover[size]) != '\0'; size++) ;
    *leftover = '\0';
    rpt = 0;
    eof = 0;
    while (!eof) {
	next = getc(fp);
	if (next == EOF) {
	    first = -1;
	    eof   =  1;
	}
	else totbytes++;
	osize = size;
	encode(t);
	t = next;
	if (size == spsiz-3) return(size);
	if (size > spsiz-3) {
	    for (i = 0; (leftover[i] = msgpkt[osize+i]) != '\0'; i++)
		;
	    size = osize;
	    msgpkt[size] = '\0';
	    return(size);
	}
    }
    return(size);
}

static void
encode(a)
char a;
{
    int a7,b8;

    if (p_mode == 1 && a == '\n') {
	rpt = 0;
	msgpkt[size++] = quote;
	msgpkt[size++] = ctl('\r');
	if (size <= spsiz-3) osize = size;
	msgpkt[size++] = quote;
	msgpkt[size++] = ctl('\n');
	msgpkt[size]   = '\0';
	return;
    }
    if (rptflg) {
	if (a == next && (first == 0)) {
	    if (++rpt < 94) return;
	    else if (rpt == 94) {
		msgpkt[size++] = rptq;
		msgpkt[size++] = tochar(rpt);
		rpt = 0;
	    }
	}
	else if (rpt == 1) {
	    rpt = 0;
	    encode(a);
	    if (size <= spsiz-3) osize = size;
	    rpt = 0;
	    encode(a);
	    return;
	}
	else if (rpt > 1) {
	    msgpkt[size++] = rptq;
	    msgpkt[size++] = tochar(++rpt);
	    rpt = 0;
	}
    }
    a7 = a & 0177;
    b8 = a & 0200;

    if (ebqflg && b8) {			/* Do 8th bit prefix if necessary. */
	msgpkt[size++] = ebq;
	a = a7;
    }

    if ((a7 < SP) || (a7==DEL)) {
	msgpkt[size++] = quote;
	a = ctl(a);
    }
    if (a7 == quote) msgpkt[size++] = quote;
    if ((rptflg) && (a7 == rptq)) msgpkt[size++] = quote;

    if ((ebqflg) && (a7 == ebq)) /* Prefix the 8th bit prefix */
	msgpkt[size++] = quote;	/* if doing 8th-bit prefixes */

    msgpkt[size++] = a;
    msgpkt[size] = '\0';
}

static void
decode()
{
    USHORT  a, a7, b8;
    char *buf;

    buf = msgpkt;
    rpt = 0;

    while ((a = *buf++) != '\0') { /* Terminator added by rpack() */
	if (rptflg) {
	    if (a == rptq) {
		rpt = unchar(*buf++);
		a = *buf++;
	    }
	}
	b8 = 0;
	if (ebqflg) {		     /* 8th-bit prefixing? */
	    if (a == ebq) {	     /* Yes, got an 8th-bit prefix? */
		b8 = 0200;	     /* Yes, remember this, */
		a = *buf++;	     /* and get the prefixed character. */
	    }
        }
	if (a == quote) {
	    a  = *buf++;
	    a7 = a & 0177;
	    if ((a7 >= 0100 && a7 <= 0137) || a7 == '?') a = ctl(a);
	}
	a |= b8;
	if (rpt == 0) rpt = 1;
	if (p_mode == 1 && a == '\r') continue;
	totbytes += rpt;
	for (; rpt > 0; rpt--) putc(a, fp);
    }
    return;
}

static void
spar(data)
char data[];
{
    data[0] = tochar(MAXPACKSIZ);
    data[1] = tochar(TTIME_KERMIT);
    data[2] = tochar(MYPAD);
    data[3] = ctl(MYPCHAR);
    data[4] = tochar(MYEOL);
    data[5] = MYQUOTE;
    if ((p_parity > 0) || ebqflg) {	   /* 8-bit quoting... */
	data[6] = MYEBQ;	  /* If parity or flag on, send &. */
	if ((ebq > 0040 && ebq < 0100) || /* If flag off, then turn it on  */
	   (ebq > 0140 && ebq < 0177) || /* if other side has asked us to */
	   (ebq == 'Y')) 
	    ebqflg = 1;
    }
    else				    /* Normally, */
	data[6] = 'Y';			    /* just say we're willing. */
    data[7] = '1';
    data[8] = MYRPTQ;
    data[9] = tochar(IDOLONG);	/* Tell 'em I do LONG packets */
    data[10] = tochar(0);	/* Don't do windows */
    data[11] = tochar(p_kmaxpack / 95);
    data[12] = tochar(p_kmaxpack % 95);
    data[13] = '\0';
}

static void
rpar(data, len)
char data[];
int  len;
{
    int ospsiz;

    spsiz   = unchar(data[0]);
    ospsiz  = spsiz;
    ttime   = unchar(data[1]);
    pad     = unchar(data[2]);
    padchar = ctl(data[3]);
    eol     = unchar(data[4]);
    quote   = data[5];
    rptflg  = 0;
    ebqflg  = 0;
    if (len >= 6 && data[6] != 0) {
	ebq = data[6];
	if ((ebq > 040 && ebq < 0100) ||
	    (ebq > 0140 && ebq < 0177)) ebqflg = 1;
	else if (((p_parity > 0) || ebqflg) && (ebq == 'Y')) {
	    ebqflg = 1;
	    ebq = '&';
	}
	else ebqflg = 0;
    }
    if (len >= 8 && data[8] != 0) {
	rptq    = data[8];
	rptflg  = ((rptq > 040 && rptq < 0100) ||
		   (rptq > 0140 && rptq < 0177));
    }
    if(len >= 9 && data[9] != 0) {
	int capas;
	for(capas=9; data[capas] & 1; capas++) ; /* Skip over continuations */
	if((ulp = (data[9] & IDOLONG)) == IDOLONG) {
	    spsiz = 500; /* Default if no packet size specified */
	    if(len >= capas+3) {
		spsiz = (unchar((data[capas+2])) * 95) + unchar(data[capas+3]);
		if(spsiz > MAXLONGPKS)
		    spsiz = MAXLONGPKS;
		else if(spsiz < 10)	/* Reasonable? */
		    spsiz = 500;
	    }
	}
    }
}

void
saybye()
{
   int len, num, gotbuf = 0;

   if(msgpkt == NULL) {	/* No msgpkt buffer, create one */
	msgpkt    = (char *)AllocMem((long)(MAXLONGPKS+20), MEMF_PUBLIC|MEMF_CLEAR);
	spackbuf  = (char *)AllocMem((long)(MAXLONGPKS+20), MEMF_PUBLIC|MEMF_CLEAR);
        if(msgpkt == NULL || spackbuf == NULL) {
	    InfoMsg1Line("KERMIT: Insufficient free memory, BYE bypassed");
	    return;
	}
	gotbuf = 1;
    }

   if(numreqs != 0)	/* Requester's up... */
	Delay(5L);	/* ...so wait for Intuition, just in case. */
   spack('G',n,1,"F");  /* shut down server no more files */
   rpack(&len,&num,ackpkt);
   if(gotbuf) {
       FreeMem(spackbuf, (long)(MAXLONGPKS+20));
       FreeMem(msgpkt,   (long)(MAXLONGPKS+20));
       msgpkt = spackbuf = NULL;
   }
}

static void
print_our_err()
{
    if (retry > MAXTRY || oldtry > MAXTRY) {
	InfoMsg1Line("KERMIT: Too may retries for packet");
	strcpy(msgpkt,"VT100 KERMIT: Too many retries for packet");
    }
    else {
	InfoMsg1Line("KERMIT: Protocol Error");
	strcpy(msgpkt,"VT100 KERMIT: Protocol Error");
    }
    spack('E',n,strlen(msgpkt),msgpkt);
}

static void
print_host_err(msg)
char *msg;
{
    InfoMsg2Line("KERMIT: Host Error:", msg);
}

static void
dostats(type,stat)
char type,*stat;
{
    char *statusform = "%5s %-15.15s Pkt: %4d Retr: %2d Bytes: %6ld Type: %c",
	 status[80];

    if (type == 'Y' || type == 'N' || type == 'G') return;

    sprintf(status, statusform, stat, filnam, tp, retry-1,
	    (LONG)totbytes, type);
    InfoMsgNoScroll(status);
}

static void
ClearBuffer()
{
    AbortIO((struct IORequest *)Read_Request);
    Wait(1L << Read_Request->IOSer.io_Message.mn_ReplyPort->mp_SigBit);
    WaitIO((struct IORequest *)Read_Request);
    Read_Request->IOSer.io_Command = CMD_CLEAR;
    DoIO((struct IORequest *)Read_Request);
    Read_Request->IOSer.io_Command = CMD_READ;
    SendIO((struct IORequest *)Read_Request);
}
