#include <ascii.h>		/* ASCII crap */
#include <filexfer.h>		/* transfer types and returns */
#include <xfbuf.h>

/*
	XMODEM, TELINK drivers
	T. Jennings 27 Apr 91

Meets FSC-0001-9 and FSC-0011, including spoof to DietIFNA


Note that protocols "FSC001X" and "FSC001T" are pure XMODEM/TELINK 
that does not allow spoofing to DIETIFNA, nor accept illegalities such
as 'C' as a NAK, etc.


File transfer protocol support routines needed, besides
the usual:

xferstat(n,z,0,s)Controls error reporting and status logging for
int n;		file transfers. The 0 is for Fido's _fspr that
long z;		has parameterized strings; it forces inline formats.
int 0;		N is the function call number;
char *s;	0 == Initialize: clears the file status counters
		totl_???? to zero, and readies the display.

		1 == start new file: Displays the passed filename (s)
		and overall file size (z).

		2 == status display: current block number, etc. Z
		is current byte offset. s is a runtime message.

		3 == error message: similar to above but can be
		handled differently if necessary.

bad_name(s)	Returns true if the passed ASCII name is a device
		name.

baudrate()	Returns the current baud rate.

fexist(fn)	Returns true if this file cannot be overwritten;
char *fn;	ie. if it exists and overwriting is not allowed by
		the program.

chkabort()	Checks the local keyboard and returns true if the
		file transfer should be aborted.
*/

/*
"Fido" is a trademark of Tom Jennings. It you utter it send me a dollar.
"FidoNet" is a also registered trademark of Tom Jennings. If you even think 
it send me two dollars. If you use both, send me ten dollars and your first
born child. All rights reserved. So there.


	Fido Software
	164 Shipley
	San Francisco CA 94107
	(415)-764-1688
*/

/* These are my assumptions as to data element sizes for the source here. You
will need to change these if the following assumptions arent right:

char			8 bits or more, signed or not
int 			more than 8 bits, though 8 will work 99% of the time
			(that last 1% if left as an exercise for the programmer)

long			more than 16 bits, preferably 32. Note as above

FLAG			at least one bit long; set to 0 or 1, and tested
			for != 0
BYTE			8 bits long. You can define as necessary.
WORD			16 bits. Ditto
LONG			32 bits. Ditto Ditto

BYTE, WORD and LONG are used for two reasons: to take advantage of the word
length in modulo arithmetic (ie. XMODEM block numbers, 0...255) or because
they are an interface to other code, ie. WORD baud rates passed to the 
drivers. Changing these assumptions may not be trivial, since they are
frequently buried into the algorithms. (Check out XMODEM.C for a classic
example of this.) */

#define FLAG char	/* just a boolean */
#define BYTE char	/* Lattice chars are 8 bit */
#define WORD unsigned	/* Lattice unsigneds are 16 bits */
#define LONG long	/* Lattice ... */

#define SS 80		/* Standard String */

long lseek();
long sizmem();
char *getmem();

char *skip_delim();		/* library stuff */
char *next_arg();
char *strip_path();

#define abs(n) (n>=0?n:-n)	/* absolute value, */
#define max(n,m) (n>m?n:m)	/* maximum value */
#define min(n,m) (n>m?m:n)	/* minimum value, */

/* Global statistics */

extern unsigned totl_files;	/* how many sent/received */
extern long totl_bytes;		/* bytes */
extern unsigned totl_errors;	/* file failures */
extern unsigned totl_process;	/* 1 == file in process */
extern unsigned totl_recoveries; /* number of resends */

/* Special flags to make for tight protocol */

static FLAG badcode;	/* 1 == allow sloppy protocol */
static FLAG spoof;	/* 1 == allow spoof to DIETIFNA (skip filename) */

/* Transmit one or more files. The list fn contains one or more file names;
if explicit is true, then they should be used as is else the system path
name should be used. */

far xsend(fn,namelist,listlen,filemode)
char *fn;		/* filename for XMODEM */
char *namelist;		/* list of sent names */
int listlen;		/* max. len of the list */
int filemode;		/* protocol type */
{
char fspec[SS];		/* full file to search for */
char fname[SS];		/* full filename to send */
char sendname[SS];	/* name we transmit */
char *cp;
int i;
int error;
struct _xfbuf xfbuf;
FLAG dofn;		/* 1 == do modem7 filename */
FLAG dotb;		/* 1 == do telink block */

	totl_process= totl_files= totl_errors= totl_recoveries= 0;
	totl_bytes= 0L;

	spoof= 0;				/* do not allow skip filename */
	badcode= 1;				/* allow sloppy protocol */
	switch (filemode) {
		case FSC001T:			dotb= 1; dofn= 1; badcode= 0; break;
		case FSC001X:			dotb= 0; dofn= 0; badcode= 0; break;
		case DIETIFNA:			dotb= 1; dofn= 1; spoof= 1; break;
		case TELINK: case TELINKC:	dotb= 1; dofn= 1; break;
		case MODEM7: case MODEM7C:	dotb= 0; dofn= 1; break;
		case XMODEM: case XMODEMC:	dotb= 0; dofn= 0; break;
	}

	xferstat(0,0L,0,"");				/* start the display, */
	if (! *fn) {					/* if no files to send */
		switch (waitnak()) {			/* do initial sync */
			case ABORT:			/* manually aborted */
			case ERROR:			/* no response */
				error= ERROR;
				goto done;
		}
		modout(EOT);				/* say "no more" */
		error= OK;
		goto done;				/* return no error */
	}
	while (*fn) {
		cpyarg(fspec,fn);			/* get an atom, */
		fn= next_arg(fn);			/* for next time */

		*sendname= NUL;				/* no rename yet, */
		for (cp= fspec; *cp; ++cp) {	
			if (*cp == '=') break;		/* look for "NAME=NAME" */
		}
		if (*cp == '=') {
			*cp++= NUL;			/* remove "=NAME" */
			strcpy(sendname,cp);		/* save 2nd name */
			sendname[13]= NUL;		/* force legal */
		}
		i= 0; 
		xfbuf.s_attrib= 0;
		while (_find(fspec,i++,&xfbuf)) {
			strip_path(fname,fspec);	/* get the prefix, */
			strcat(fname,xfbuf.name);	/* add the filename */
			xferstat(1,xfbuf.fsize,0,fname);/* display it, */

			if (*sendname) xferstat(3,xfbuf.fsize,0,"Sending as \"%s\"",sendname);
			else strcpy(sendname,xfbuf.name); /* use actual filename */

			totl_process= 1;		/* file in process */
			error= sendfile(fname,dofn,dotb,&xfbuf,sendname);
			totl_process= 0;		/* file complete */

			if (error != OK) break;		/* check errors */
			if (strlen(fname) < listlen) {	/* keep a list */
				strcat(namelist,fname);
				strcat(namelist," ");
				listlen -= strlen(fname);
			}
			++totl_files;			/* count another */
			*sendname= NUL;			/* clear filename */
		}
		if (error != OK) {
			++totl_errors;
			break;
		}
	}

/* No more files to send. If simple XMODEM, we are already done; there was 
only one file to send, and hence the file-EOT completed the transfer. For all
others, the receiver is now waiting to start another file receive; wait for 
sync, then send EOT to signal no-more. */

	if (!totl_errors && (dofn || dotb)) {		/* MODEM7/TELINK/DIETIFNA */
		xferstat(2,0L,0,"All Files Sent");
		waitnak();				/* sync up */
		modout(EOT);				/* send "no more" */
	}
done:
	xferstat(3,0L,0,"Protocol Complete");
	return(error);
}

/* Receive files: the filename is used for XMODEM only; for TELINK
only the path is used. */

far xrecv(fn,namelist,listlen,filemode)
char *fn;		/* filename for XMODEM */
char *namelist;		/* list of received names */
int listlen;		/* max. len of the list */
int filemode;		/* protocol type */
{
int i;
char fname[SS];		/* full filename with prefix */
FLAG dofn;		/* 1 == do modem7 filename */
FLAG dotb;		/* 1 == MUST do telink block */
FLAG docrc;		/* 1 == do CRC */

	totl_process= totl_files= totl_errors= totl_recoveries= 0;
	totl_bytes= 0L;

	spoof= 0;				/* do not allow skip filename */
	badcode= 1;				/* allow sloppy protocol */
	switch (filemode) {
		case FSC001X:	dotb= 0; dofn= 0; docrc= 1; badcode= 0; break;
		case FSC001T:	dotb= 0; dofn= 1; docrc= 1; badcode= 0; break;
		case DIETIFNA:	dotb= 1; dofn= 1; docrc= 1; spoof= 1; break;
		case TELINK:	dotb= 0; dofn= 1; docrc= 0; break;
		case TELINKC:	dotb= 0; dofn= 1; docrc= 1; break;
		case MODEM7:	dotb= 0; dofn= 1; docrc= 0; break;
		case MODEM7C:	dotb= 0; dofn= 1; docrc= 1; break;
		case XMODEM:	dotb= 0; dofn= 0; docrc= 0; break;
		case XMODEMC:	dotb= 0; dofn= 0; docrc= 1; break;
	}
	xferstat(0,0L,0,"");
	while (1) {
		cpyarg(fname,fn);			/* copy each time */
		totl_process= 1;			/* file in process */
		i= getfile(fname,dofn,dotb,docrc);	/* get files */
		totl_process= 0;
		if (i != OK) break;			/* stop for any reason */
		++totl_files;				/* count 'em */
		if (strlen(fname) < listlen) {		/* getfile fills in */
			strcat(namelist,fname);		/* the filename */
			strcat(namelist," ");		/* add to the list */
			listlen -= strlen(fname);
		}
		if (!dofn && !dotb) break;		/* only one for XMODEM */
	}
	flush(0);					/* flush garbage, */

/* Process the result code. */

	switch (i) {
		case OK: 
		case EOT: break;			/* no error */

		case ABORT:				/* abort/cancel */
		case DISKFULL:				/* disk full */
			xferstat(3,0L,0,"\"%s\" bad -- deleted",fname);
							/* fall through */
		default:
			++totl_errors;			/* flag it */
			delete(fname);			/* delete it */
			break;
	}
	xferstat(3,0L,0,"Protocol Complete");
	return(i);
}		

/* Get a single file from the remote computer. */

static getfile(fname,dofn,dotb,docrc)
char *fname;	/* full pathname or path prefix */
int dofn;	/* 1 == do MODEM7 filename */
int dotb;	/* 1 == get TELINK block */
int docrc;	/* 1 == use CRC */
{
int c,i;
int file;				/* open file */
BYTE sector;				/* XMODEM block number */
char name[SS];				/* raw filename */
BYTE buff[128];				/* XMODEM data block */
BYTE ackchar;				/* ACK/NAK */
BYTE badack;				/* nack character -- 'C' initially */
BYTE aborts;				/* # of Control-Xs in a row */
int errors;				/* errors 0 - 9 */
long bytes;				/* total bytes in file */
long fsize;				/* file size or -1 */
long filetime;				/* creation time/date */

	aborts= 0;			/* no Control-Xs yet */
	errors= 0;			/* no errors */
	bytes= 0L;
	sector= 1;			/* XMODEM block, first time */
	file= -1;			/* file not open (none yet) */
	fsize= -1L;			/* unknown so far */
	filetime= -1L;

/* Strip the path prefix off the supplied name, and put the plain name into
a seperate place. MODEM7/TELINK will get the plain name from the horrible
protocol, and DietIFNA/TELINK get the name from the TELINK block; only for
XMODEM is the given name used. */

	cpyarg(name,strip_path(buff,fname));		/* strip of name first */
	strip_path(fname,fname);			/* then the path prefix */
	if (dofn || dotb) *name= NUL;			/* force getting a name */

	ackchar= badack= (docrc ? 'C' : NAK);		/* choose initial NAK */

	while (errors < 10) {				/* retry count */
		if (chkabort()) return(ABORT);		/* for manual abort */
		c= -1;					/* clear it first! */

/* If no file is open, first attempt to get the filename, if MODEM7/TELINK. It
may spoof to DietIFNA, in which case we need to wait for a TELINK block. If the
file is not open but we have a name, it was received via a TELINK block. */

		if (file == -1) {
			if (dofn && !*name) {		/* for TELINK/MODEM7... */
				c= getfname(name);	/* get the filename */
				switch (c) {
					case SYN: xferstat(3,0L,0,"BR1-3: Filename skip"); break;
					case OK: xferstat(3,0L,0,"BR1-2: Got filename"); break;
					case EOT: xferstat(3,0L,0,"BR1-1: No more files"); return(EOT);
					default: xferstat(3,0L,0,"BR1-4: Can't get filename"); return(ERROR);
				}
			}

/* If we have a filename, error check it and create it now. (For XMODEM, we 
had the name all along, and for TELINK/MODEM7, we just got it. For DietIFNA, 
we won't get it until we get a TELINK block.) */

			if (*name) {
				if (badname(name)) name[1]= '$';/* check for reserved filenames */
				strcat(fname,name);	/* assemble it */
				if (fsize != -1) xferstat(1,fsize,0,fname);
				else xferstat(1,0L,0,fname); /* display it */

				if (fexist(fname)) {	/* overwrite error */
					xferstat(3,0L,0,"File already exists");
					return(BADFILE);
				}
				file= creat(fname,2);	/* create it, */
				if (file == -1) {
					xferstat(3,0L,0,"Can't create file");
					return(BADFILE);
				}
			}
		}

/* If 'c' is not -1, (set way above) then it means that we detected a
start-of-block in getfname(), ie the FSC0011 skip-filename. Since the
block has already started, we should not ACK/NAK it, nor get the initial
character, since we have it already. */

		if (c == -1) {				/* spoof/block started */
			modout(ackchar);		/* previous ACK/NAK/'C' */
			ackchar= badack;		/* assume the worst */
			c= modin(600);			/* get initial character */
		}
		++errors;				/* assume an error */
		switch (c) {				/* see what it is */
			case SOH:			/* XMODEM data */
				badack= NAK;		/* normal nack char */
				if (dotb) {
					xferstat(3,0L,0,"Got SOH, need TELINK");
					break;
				}
				i= getblock(docrc,buff); /* get sect number */

badblk:;			++totl_recoveries;
				if (i < 0) {
					switch (i) {
						case -1: xferstat(3,bytes,0,"XR2: Timeout"); break; 
						case -2: xferstat(3,bytes,0,"XR2: Bad Sector Number"); break; 
						case -3: xferstat(3,bytes,0,"XR2: Bad Checksum"); break;
						case -4: xferstat(3,bytes,0,"XR2: Bad CRC"); break; 
						default: xferstat(3,bytes,0,"XR2: Bad Data Block"); break; 
					}
					break;		/* bad block */
				}
				if (i < sector) {	/* duplicate */
					xferstat(3,bytes,0,"Duplicate");
					goto goodblk;	/* ACK it */

				} else if (i > sector) {/* out of sync */
					xferstat(3,bytes,0,"Block Sync Error");
					break;
				}
				i= 128;			/* set bytes in buffer */
				if (fsize > 0L) {	/* correct if known */
					i= (fsize > 128L) ? 128 : fsize;
					fsize -= i;
				}
				bytes += i;		/* byte tally */
				if (put_buff(file,buff,i) != i) {
					xferstat(3,bytes,0,"DISK FULL!");
					close(file);	/* close it */
					return(DISKFULL);
				}
				xferstat(2,bytes,0,"XR2-3: Rec'v Data");
				++sector;

goodblk: ;			aborts= 0;		/* clear Control-X counter */
				errors= 0;		/* restart errors-in-a-row counter */
				ackchar= ACK;		/* ACK this block */
				break;

			case SYN:			/* TELINK block */
				xferstat(3,0L,0,"XR1-4: TELINK Block");
				badack= NAK;		/* normal nack char */
				i= getblock(0,buff);	/* get the block in checksum */

				if (i < 0) goto badblk;	/* report bad block */
				if (i != 0) {
					xferstat(3,0L + i,0,"Bad TELINK block #?");
					break;
				}
				cpyarg(name,&buff[8]);	/* copy the filename */

				fsize= 0L + buff[3];	/* OK, it's zero */
				fsize= (fsize << 8) | buff[2];
				fsize= (fsize << 8) | buff[1];
				fsize= (fsize << 8) | buff[0];

				filetime= 0L + buff[7];
				filetime= (filetime << 8) | buff[6];
				filetime= (filetime << 8) | buff[5];
				filetime= (filetime << 8) | buff[4];
				if (fsize == 0L) fsize= -1L;

				xferstat(1,fsize,0,"%s (%s)",fname,name);
				dotb= 0;		/* we have it now */
				goto goodblk;

			case EOT:			/* end of file */
				if (file == -1) {
					xferstat(2,0L,0,"No More Files");
					return(EOT);
				}
				close(file); file= -1;	/* close it */
				if (fsize > 0L) xferstat(3,fsize,0,"Early end of file");
				else xferstat(2,bytes,0,"End of File");
				if (filetime != -1L) ftime(1,file,&filetime);
				totl_bytes += bytes;	/* total received */
/* FSC-0011 */			delay(100);		/* wait for extra EOTs */
/* FSC-0011 */			flush(0);		/* purge everything */
				modout(ACK);		/* ACK the EOT, */
				return(OK);

			case CAN:			/* Control-X */
				xferstat(2,bytes,0,"Control-X");
				c= ABORT;		/* in case we return */
				if (++aborts > 4) errors= 10;
				break;

			case -1: xferstat(2,bytes,0,"Waiting to start"); 
				break;			/* NAK to restart */

			default: c= -1; flush(10); break; /* garbage */
		}
		if (errors) flush(0);			/* flush garbage if error */
	}
	if (file != -1) close(file);			/* close the file */
	if (errors) flush(0);
	return(c);
}

/* Receive a MODEM7 filename, put into the passed array; return ERROR if error,
OK if we get a filename, EOT if no more files. */

static getfname(name)
char *name;
{
int i;
char aborts;

	aborts= 0;
	for (i= 1; i <= 25; i++) {
		if (chkabort()) return(ABORT);	/* check for manual abort */
		switch (getfn(name,(i <= 5))) {
			case OK: return(OK);	/* got a name, */
			case SYN: return(SYN);	/* DietIFNA spoof */
			case CAN: if (++aborts > 4) return(ABORT); break;
			case EOT: return(EOT); 	/* no more files */
		}
		flush(0);			/* other garbage */
	}
	return(ERROR);
}

/* Send a filename, MODEM7 style. */

static sendfname(name)
char *name;
{
int i,aborts;

	aborts= 0;
	for (i= 1; i <= 20; i++) {		/* FSC-0011 */
		xferstat(2,0L + i,0,"MS0: Sending filename");
		switch (sendfn(name)) {
			case 'C': 
				if (spoof) return('C'); /* spoof to DietIFNA? */
				xferstat(3,0L + i,0,"Skip filename, ignored");
				break;

			case TIMEOUT:
				xferstat(3,0L + i,0,"Other end: no response");
				return(ERROR);

			case ERROR:
				xferstat(3,0L + i,0,"Error sending filename");
				break;

			case CAN: xferstat(3,0L + i,0,"Control-X");
				if (++aborts > 4) return(ABORT);
				break;

			case ABORT:
				xferstat(3,0L + i,0,"Aborted");
				return(ERROR);

			case OK: return(OK);	/* ... send OK! */
		}
	}
	xferstat(3,0L + i,0,"Filename not sent");
	return(ERROR);
}

/* Send a file in XMODEM. */

static sendfile(fname,dofn,dotb,xfbuf,sendname)
char *fname;			/* file to send */
int dofn;			/* 1 == do MODEM7 filename */
int dotb;			/* 1 == send TELINK block */
struct _xfbuf *xfbuf;		/* file info struct */
char *sendname;			/* name that we transmit */
{
int i;
int file;				/* open file handle */
BYTE sector;				/* >>> 8 BIT <<< XMODEM block number */
BYTE buff[128];				/* XMODEM data block */
char errors;				/* errors 0 - 9 */
char aborts;				/* # of Control-Xs */
int crcmode;				/* 1 == use CRC not checksum */
long bytes;

	sector= 1;			/* first XMODEM block number, */
	aborts= 0;			/* no Control-Xs yet */
	bytes= 0L;			/* bytes sent */

/* Here we muck with the flags. If we get a "skip filename" sequence, then
we must send the TELINK block, instead of just 4 times. We use 'dofn' to
remember if we sent the filename or not -- if not, and 'dotb' is true, the
TELNIK block is mandatory. */

	i= dofn ? sendfname(sendname) : OK;		/* possible MODEM7 filename send */
	switch (i) {
		case OK: break;				/* filename sent OK */
		case 'C': dofn= 0; dotb= 1; xferstat(3,0L,0,"MS0: Filename skipped"); break;
		default: return(i);			/* other error */
	}

/* Wait for NAK or 'C' to synchronize, and indicate CRC or checksum mode. */

	switch (crcmode= waitnak()) {			/* get initial NAK/CRC mode */
		case ERROR: return(ERROR);		/* timeout, etc */
		case ABORT: return(ABORT);		/* manual abort */
	}

	if (dotb) {
		i= sendtel(sendname,xfbuf,crcmode);	/* send TELINK block */
		if ((i != OK) && !dofn) {
			xferstat(3,0L,0,"FSC001-9 PROTOCOL VIOLATION: Filename skipped, TELINK block refused"); 
/*			if (! spoof) return(i);	*/	/* hard error! */
		}
	}

/* Preliminary bullshit out of the way; open the file and start the transfer. */

	file= open(fname,0);				/* open the file, */
	if (file == -1) {
		xferstat(3,0L,0,"Can't open file");
		return(ERROR);
	}
	for (errors= 0; errors < 10; ) {		/* note funny for() below ... */
		i= get_buff(file,buff,128);		/* read some file data, */
		if (! i) break;				/* end of file */
		while (i < 128) buff[i++]= 26;		/* pad last block */

		for (errors= 0; errors < 10; ++errors) { /* (... its really OK) */
			xferstat(2,bytes,0,"XS2-1: Send Data");/* stats for the nosy sysop */
			modout(SOH);			/* tell rcvr a block comes */
			sendblock(sector,crcmode,buff);
			i= getack(bytes);		/* get an acknowledge */
			if (i == ACK) {
				++sector;
				bytes += 128L;
				errors= 0;		/* reset errors-in-a-row */
				break;

			} else if (i == ABORT) {
				close(file);		/* stop immediately */
				return(ABORT);

			} else {
				if (i == NAK) ++totl_recoveries;
				flush(0);		/* flush garbage */
			}
		}
	}
	close(file);
	if (errors) return(ERROR);			/* bad send */

	xferstat(2,bytes,0,"XS2-2: End of File");
	i= send_eof(errors);				/* process EOF */
	if (i == OK) totl_bytes += xfbuf-> fsize;	/* file complete */
	return(i);
}

/* Send XMODEM/TELINK/DIETIFNA End of File. */

static send_eof(errors)
int errors;		/* != 0 if a failed transfer */
{
int i,aborts;

	aborts= 0;						/* Control-X counter */
	i= NAK;							/* send initial EOT */
	for (errors= 10; --errors > 0; ) {
		if (i == NAK) modout(EOT);			/* (re-)send EOT */
		switch (i= modin(600)) {			/* wait for ACK */
			case ACK: xferstat(2,0L + i,0,"XS4-2: File complete"); return(OK);
			case CAN: xferstat(3,0L + aborts,0,"XS4: Control-X"); if (++aborts > 4) return(ERROR);
			case -1: break;
			case 'C': xferstat(3,0L,0,"XS4-3: 'C' as EOT NAK?"); 
				if (badcode) {
					xferstat(3,0L,0,"(OK...)");
					i= NAK;
				}
				break;
			case NAK: xferstat(3,0L,0,"XS4-3: NAK"); break;
			default:  xferstat(3,0L,0,"XS4: Recv'd 0x%02x at EOF",i); flush(0);
		}
	}
	if (i == -1) xferstat(3,0L,0,"XS4-1: Send failed");
	return(ERROR);
}

/* Wait for an acknowledge, NAK, timeout or cancel. This waits 30 seconds 
per block maximum. */

static getack(bytes)
long bytes;		/* current file position (for error reports) */
{
char aborts;
int i,c;

	aborts= 0;

	for (i= 1; i <= 30; i++) {
		if (chkabort()) return(ABORT);
		c= modin(100);			/* 30 secs/block wait max */
		switch (c) {
			case ACK: return(c);
			case NAK: xferstat(3,bytes,0,"NAK"); return(c);
			case CAN: xferstat(3,0L + aborts,0,"Control-X"); 
				if (++aborts > 4) return(ABORT); break;
			case -1: break;
			case 'C': xferstat(3,bytes,0,"'C' as NAK?"); 
				if (badcode) {
					xferstat(3,bytes,0,"(OK...)"); 
					return(c);
				} else xferstat(3,bytes,0,"(Disallowed)");
				flush(0);
				break;
			default: xferstat(3,bytes,0,"Recv'd 0x%02x",c); flush(0); break;
		}
	}
	if (c == -1) xferstat(3,bytes,0,"Timeout");
	return(c);
}

/* Wait for an initial NAK or CRC; return 0 for NAK, 1 for CRC, or ERROR
for anything else. */

static waitnak() {
int i;
char aborts,n,c;

	xferstat(2,0L,0,"XS0: Waiting to Start");
	aborts= 0;
	for (n= 1; n <= 30; n++) { 		/* 60 seconds max */
		if (chkabort()) return(ABORT);	/* check for manual abort */
		switch (i= modin(200)) {
			case NAK: xferstat(2,0L + n,0,"XS0-3: NAK"); return(0);
			case 'C': xferstat(2,0L + n,0,"XS0-3: CRC"); return(1);
			case CAN: xferstat(3,0L + aborts,0,"XS0: Control-X"); 
				if (++aborts > 4) return(ABORT); break;
			case -1: break;
			default: /* xferstat(3,0L + n,0,"XS0: Recv'd 0x%02x",i); */ break;
		}
		flush(0);
	}
	if (i == -1) xferstat(3,0L + n,0,"Timeout");
	return(ERROR);
}

/* Send the TELINK block. */

static sendtel(fn,xfbuf,crcmode)
char *fn;
struct _xfbuf *xfbuf;
int crcmode;
{
int c,i;
char buff[128],aborts;

/* FSC001 */

	for (i= 0; i < sizeof(buff); i++) buff[i]= 0;	/* clear data block, */

	buff[0]= xfbuf-> fsize & 0xff;			/* file size */
	buff[1]= (xfbuf-> fsize >> 8) & 0xff;
	buff[2]= (xfbuf-> fsize >> 16) & 0xff;
	buff[3]= (xfbuf-> fsize >> 24) & 0xff;
	buff[4]= xfbuf-> time & 0xff;			/* DOS time, */
	buff[5]= (xfbuf-> time >> 8) & 0xff;
	buff[6]= xfbuf-> date & 0xff;			/* DOS date, */
	buff[7]= (xfbuf-> date >> 8) & 0xff;

	for (i= 8; i < 24; i++) {			/* install filename */
		if (*fn) buff[i]= *fn++;		/* left-justified */
		else buff[i]= ' ';			/* space filled */
	}
	strcpy(&buff[25],"Fido/FidoNet");		/* sending program */
	buff[41]= crcmode ? 1 : 0;			/* CRC flag */

	aborts= 0;
	for (i= 1; i <= 2; i++) {			/* FSC001 says 2 tries */
		xferstat(2,0L + i,0,"XS0-3: TELINK block");
		if (chkabort()) return(ABORT);		/* (manual abort) */

		modout(SYN);				/* TELINK block start */
		sendblock(0,0,buff);			/* send in checksum mode, */
		switch (c= getack(0L)) {		/* get an acknowledge */
			case ACK: xferstat(2,0L,0,"XS0-4: Acknowledged"); 
				return(OK);		/* got it */
			case ABORT:			/* (manual abort) */
			case ERROR: return(ERROR);	/* or serious problem */
		}
	}
	return(c);					/* just give up */
}

/* Get an XMODEM block, return its sequence number, or an error code: -1 for
timeout, -2 bad sector number, -3 bad checksum, -4 bad CRC. */

static getblock(crcflag,buffer)
int crcflag;
char *buffer;
{
int i,v;
int sector;
int chksum;

	sector= modin(600);			/* get the sector number, */
	i= modin(600);				/* and its complement */
	if (i == -1) return(-1);		/* timeout! */
	if (i + sector != 255) return(-2);	/* bad sector number! */
	i= 128;					/* block size */

	if (crcflag) {				/* CRC */
		clrcrc();
		while (i--) {
			v= modin(600);		/* get the byte, */
			if (v == -1) return(-1);/* timeout! */
			*buffer++= v;		/* store the byte */
			updcrc(v);		/* update the CRC */
		}
		v= modin(600); updcrc(v); if (v == -1) return(-1);
		v= modin(600); updcrc(v); if (v == -1) return(-1);
		if (chkcrc()) return(-4);	/* -4 if bad CRC */

	} else {				/* checksum */
		chksum= 0;
		while (i--) {
			v= modin(600);		/* get the byte, */
			if (v == -1) return(-1);/* timeout! */
			*buffer++= v;		/* store the byte */
			chksum += v;
		}
		if (modin(600) != (chksum & 255)) return(-3);
	}
	return(sector);				/* good block */
}

/* Transmit an XMODEM block with the specified sector number and checksum/CRC
mode. No return value. */

static sendblock(sector,crcflag,buffer)
BYTE sector;
int crcflag;
char *buffer;
{
int i;
WORD crc;
BYTE chksum;

	modout(sector);			/* send the sector number */
	modout(~sector);		/* and its complement */

	chksum= 0;			/* maintain both CRC and checksum, */
	clrcrc();

	for (i= 0; i < 128; i++) modout(buffer[i]);
	for (i= 0; i < 128; i++) {	/* output data then do CRC/checksum */
		chksum += buffer[i];
		updcrc(buffer[i]);
	}

	flush(0);			/* flush out garbage, */
	if (crcflag) {
		crc= fincrc();		/* get CRC bytes, */
		modout(crc >> 8);	/* MS byte first, */
		modout(crc);		/* then LS byte, */

	} else modout(chksum);		/* send checksum, */
}

/* Get data to send; if the diverter is on, get them from the packeter. 
Return the number of bytes actually read. */

static get_buff(f,buff,n)
int f;			/* file handle, or dummy */
char *buff;		/* buffer to put data in, */
int n;			/* requested byte count */
{
int i;

	n= read(f,buff,n);	/* from a disk file */
	return(n);
}

/* Output the buffer to the disk file or unpacketer. Return the number of bytes
sucessfully written. */

static put_buff(f,buff,n)
int f;			/* file handle, or dummy */
char *buff;		/* buffer to put data in, */
int n;			/* requested byte count */
{
int i;

	n= write(f,buff,n);				/* or to the file */
	return(n);					/* say what we did */
}

/* Transmit a filename for batch mode tranmission. Return ERROR if cant do it,
or OK if sent properly. It is assumed that the receiver is ready to receive
the filename we have to send. */

static sendfn(name)
char *name;
{
BYTE chksum;
char localname[SS];
int c,i;

	for (i= 1; i <= 30; i++) {		/* get initial character */
		if (chkabort()) return(ABORT);
		c= modin(200);			/* 60 sec max */
		if (c == NAK) break;		/* got name NAK */
		else if ((c == CAN) || (c == 'C')) return(c);
		else if (c != -1) xferstat(3,0L + i,0,"SendFN got 0x%02x",c);
		flush(0);			/* flush built-up garbage */
	}
	if (i >= 30) return(TIMEOUT);		/* timeout (fatal) */

	cvt_to_fcb(name,localname);		/* convert name, */
	chksum= 0;				/* start checksum, */
	modout(ACK);				/* all ready, */
	xferstat(2,0L,0,"MS1: Sending filename");	/* FSC001/0011 */
	for (i= 0; i < 11; i++) {
		c= localname[i];		/* get name char, */
		chksum += c;			/* maintain checksum, */
		modout(c);			/* send name char, */
		if (modin(1000) != ACK) break;	/* if not an ACK */
	}
	xferstat(2,0L,0,"MS2: Get filename checksum");
	if (i >= 11) {				/* if all sent OK */	
		modout(SUB);			/* end of name, */
		chksum += SUB;			/* stupid protocol */
		if (modin(1000) == (chksum & 0xff)) { /* get recvr's checksum, */
			modout(ACK);		/* if good, say so, */
			return(OK);		/* return happy :-), */
		}
	}
	xferstat(3,0L,0,"MS2: Filename checksum error");
	modout('u');				/* else not sent OK, */
	return(ERROR);				/* return sad :-( */
}

/* Get a filename from the sender. */

static getfn(name,skipfn)
char *name;
int skipfn;		/* 1 == attempt filename skip */
{
int i;
int c;			/* NOTE: 'c' is an integer !!! */
BYTE chksum;
char newname[SS];

	if (skipfn && spoof) {				/* attempt to switch to DietIFNA */
		xferstat(2,0L,0,"MR0: Trying filename skip");
		modout('C');

	} else {
		xferstat(2,0L,0,"MR0b: Rec'v filename");
		modout(NAK);
	}
	c= modin(200);					/* get first/sync character, */
	if (c != ACK) return(c);			/* not MODEM7 */

	xferstat(2,0L,0,"MR2: Get filename");
	chksum= 0;
	for (i= 0; i < 11; i++) {			/* 11 char max, */
		c= modin(1000);				/* get a char, */
		switch (c) {
			case -1: return(ERROR);		/* timeout */
			case 'u': return(ERROR);	/* error */
			case EOT: return(EOT);		/* no more files */
			case SUB: break;		/* end of name */
			default:
				chksum += c;		/* file name char I guess */
				newname[i]= c;		/* stash it */
				modout(ACK);		/* say "ok" */
				break;
		}
	}
	if (modin(1000) == SUB) {			/* if end of name, */
		chksum += SUB;				/* stupid protocol, */
		modout(chksum);				/* send check sum, */
		if (modin(1000) == ACK) {
			xferstat(2,0L,0,"MR3-1: Filename OK");
			cvt_from_fcb(newname,name);	/* fix name, */
			return(OK);
		}
	}
	xferstat(2,0L,0,"MR3-2: Filename error");
	return(ERROR);					/* filename too long */
}

/* Convert a CP/M like filename to a normal ASCIZ name. */

static cvt_from_fcb(inname,outname)
char *inname,*outname;
{
int i;
char c;

	for (i= 8; i > 0; --i) {
		c= fnc(*inname++);
		if (c != ' ') *outname++= c;	/* ignore spaces */
	}					/* but do all 8 */
	*outname++= '.';			/* add the dot, */

	for (i= 3; i > 0; --i) {
		c= fnc(*inname++);
		if (c == ' ') break;
		*outname++= c;
	}
	*outname= NUL;				/* terminate it, */
}

/* Convert a normal asciz string to MSDOS/CPM FCB format. Make the filename
portion 8 characters, extention 3 maximum. Supports wildcards, skips drive
specs. */

static cvt_to_fcb(inname,outname)
char *inname;
char outname[];
{
char c;
int i;

	if (inname[1] == ':') inname= &inname[2];
	for (i= 0; i < 11; i++)
		outname[i]= ' ';		/* clear out name, */
	_cvt2(inname,outname,8);		/* do name portion, */
	while (*inname) {			/* skip to dot, if any */
		if (*inname++ == '.') break;
	}
	_cvt2(inname,&outname[8],3);		/* do extention, */
}
/* Do part of a name. */

static _cvt2(inname,outname,n)
char *inname,*outname;
int n;
{
int i;
	for (i= 0; i < n; i++) {		/* NAME PART */
		if (*inname == '\0')		/* if null, */
			break;			/* quit, */
		else if (*inname == '*')	/* if *, fill with ?, */
			outname[i]= '?';
		else if (*inname == '.')	/* if a dot, */
			break;			/* skip to extention, */
		else {
			outname[i]= toupper(*inname);
			++inname;
		}
	}
}

/* Fix this filename character */

static fnc(c)
char c;
{
	if ((c < ' ') || (c == '\\') || (c == '/')) c= '$';
	return(c);
}
