/*****************************************************************************
 *	SEAlink - Sliding window file transfer protocol
 *
 *	@(#) sealink.c 2.9 89/03/02 
 *	UNIX SVR2 and BSD versions by Scott Reynolds
 *	uucp: clmqt!scott
 *
 *	additional SysV modifications by Sanford Zelkovitz, without whose
 *	help this couldn't have been accomplished
 *
 *	Based on:
 *	MS-DOS Version 1.20, created on 08/05/87 at 17:51:40
 *   (C)COPYRIGHT 1986, 87 by System Enhancement Associates ALL RIGHTS RESERVED
 *	By:  Thom Henderson
 *
 *	Mr. Henderson had no hand in this UNIX port; please don't bother
 *	him with questions about this program!
 *
 *	Description:
 *
 *	The intent of SEAlink is to provide a file transfer protocol that
 *	does not suffer from propagation delays, such as are introduced
 *	by satellite relays or packet switched networks.
 ****************************************************************************/

/*
 * The following flags are for compiling on different systems.
 */
#define SYSV			/* Compile for SYS V i/o calls	*/
/* #define BSD			/* Compile for BSD i/o calls	*/

/*
 * Define NO_MEM if your system doesn't have a
 * working memset() function
 */
/* #define NO_MEM		/* memset() doesn't work	*/

/*
 * Define NO_NAP if there is no nap() function.
 *
 * If you use nap() be aware that it causes a MUCH greater load
 * on the processor.
 *
 * With NO_NAP defined the XModem compatibility is reduced; if
 * this is not a concern, define this for greater efficiency.
 */
#define NO_NAP			/* nap() doesn't work		*/

/*
 * Define CRCTABLE to use the fast table lookup CRC calculation;
 * a slower calculation-based method can be compiled to reduce the
 * amount of process memory required by commenting this out.
 */
#define	CRCTABLE		/* use CRC lookup table		*/

/*
 * The section of code that is compiled when NAKEOT is defined is in the
 * original MS-DOS version 1.16 code.  Its purpose is to send a NAK when
 * an EOT is received during rcvfile(), apparently to confirm that this is
 * indeed the end of file.  However, in certain (apparently non-standard)
 * versions of the protocol, it is possible that the program will report an
 * error when in fact there isn't one.  Comment this out at your discretion.
 */
#define NAKEOT				/* define to NAK EOT's		*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#ifdef	SYSV			/* use System V I/O control	*/
#include <termio.h>
#endif	/* SYSV */
#ifdef	BSD			/* use BSD I/O control		*/
#include <sgtty.h>
#endif	/* BSD */
#ifndef	NO_MEM
#include <memory.h>
#endif	/* NO_MEM */

/* Various system constants */
#define	WINDOW	6			/* maximum size of window	*/
#define	S_NAK	0			/* NAK condition for sendack()	*/
#define	S_ACK	1			/* ACK condition for sendack()	*/
#define	NONE	0			/* neither send nor receive	*/
#define	SEND	1			/* send mode			*/
#define	RECV	2			/* receive mode			*/
#define	TENYEAR	(time_t) 315532800L	/* GMT offset for 1970 <-> 1980	*/

/* SEAlink block zero data structure */
struct zeros
{   
	long	flen;			/* file length			*/
	time_t	fstamp;			/* file date/time stamp		*/
	char	fnam[17];		/* original file name		*/
	char	prog[15];		/* sending program name		*/
	char	noacks;			/* true if ACKing not required	*/
	char	fill[87];		/* reserved for future use	*/
};

/* ASCII mnemonic values */
#define	ACK	0x06
#define	NAK	0x15
#define	SOH	0x01
#define	EOT	0x04
#define	CAN	0x18

static int	outblk;			/* number of next block to send	*/
static int	ackblk;			/* number of last block ACKed	*/
static int	blksnt;			/* number of last block sent	*/
static int	slide;			/* true if sliding window	*/
static int	ackst;			/* ACK/NAK state		*/
static int	numnak;			/* number of sequential NAKs	*/
static int	chktec;			/* 1 = CRC, 0 = checksum		*/
static int	toterr;			/* total number of errors	*/
static int	ackrep;			/* true when ACK, NAK reported	*/
static int	ackseen;		/* count of sliding ACKs seen	*/
static int	xferdone = 0;		/* done with transfer (recvr)	*/
static int	debug = 0;		/* debugging flag		*/
static int	sld_flag = 1;		/* sliding windows allowed	*/
static int	ackless = 1;		/* true if ACKs not required	*/
static char	outb[133];		/* block to use for output	*/

/* the program name MUST be 14 characters plus a '\0' terminator */
#ifdef	SYSV
char	progname[15] = "SEAlink/SYSV  ";
#endif	/* SYSV */
#ifdef	BSD
char	progname[15] = "SEAlink/BSD   ";
#endif	/* BSD */

/* the debug filename is a local preference */
char	*dbugfile = "/tmp/sealink.log";


#ifdef	NO_NAP
/*
 * Need this to do a (very) rough approximation of nap().
 * Used by alarm() in com_getc()
 */
jmp_buf	tohere;
#endif

/* CRC computation logic */
#ifdef	CRCTABLE
unsigned short crc_tab[256] = 		/* CRC lookup table */
{
	0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 
	0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 
	0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 
	0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 
	0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 
	0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 
	0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 
	0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 
	0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 
	0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 
	0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 
	0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 
	0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 
	0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 
	0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 
	0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 
	0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 
	0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 
	0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 
	0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 
	0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 
	0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 
	0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 
	0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 
	0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 
	0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 
	0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 
	0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 
	0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 
	0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 
	0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 
	0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};

/*
 * crc_update performs CRC calculation using crc_tab[].
 * Note:  Don't need to "flush" with zeroes with this formula.
 */
#define	crc_update(CRC, C)	((CRC << 8) ^ crc_tab[(CRC >> 8)^C])
#define	crc_finish(CRC)		(CRC)

#else	/* otherwise, don't use CRC table */

unsigned short crc_update(crc, c)	/* calculate a CRC value */
register unsigned	crc;
register int	c;
{
	int	count;

	for (count = 8; --count >= 0;) {
		if (crc & 0x8000) {
			crc <<= 1;
			crc += (((c <<= 1) & 0400)   !=  0);
			crc ^ = 0x1021;
		} else {
			crc <<= 1;
			crc += (((c <<= 1) & 0400)   !=  0);
		}
	}
	return crc;
}

/* finish CRC calculation by "flushing" with zeroes */
#define crc_finish(C)	crc_update(crc_update(C, 0), 0)
#endif

unsigned	alarm();
void		sendabort();
void		shipblk();

#ifdef	NO_MEM
char		*memset();
#endif	/* NO_MEM */

main(argc, argv)
int	argc;
char	*argv[];
{
	int		c;		/* used to get options */
	int		mode = NONE;	/* SEND, RECV files */
	int		noerr;		/* no error in transmission */
	char		*fn;		/* current filename to send/recv */
	int		getopt();
	unsigned	sleep();
	int		xmtfile();
	char		*rcvfile();
	extern int	opterr;		/* used by getopt() */
	extern int	optind;		/*        "         */
	extern char	*optarg;	/*        "         */
#ifdef	SYSV
	struct termio	oldtty, tty;
#endif /* SYSV */
#ifdef	BSD
	struct sgttyb	oldtty, tty;
#endif /* BSD */

	mode = 0;
	fn = NULL;
	opterr = 0;
	while ((c = getopt(argc, argv, "dfors:")) != EOF) {
		switch (c) {
		case 'd':
			debug = 1;	/* use debug file */
			break;
		case 'f':
			sld_flag = 0;	/* no sliding window */
			break;
		case 'o':
			ackless = 0;	/* no overdrive mode */
			break;
		case 's':
			mode = SEND;
			fn = optarg;
			break;
		case 'r':
			mode = RECV;
			break;
		default:
			mode = NONE;
			break;
		}
	}
	switch (mode) {
	case RECV:
		fputs("sealink: ready to receive\n", stderr);
		break;
	case SEND:
		fputs("sealink: ready to send\n", stderr);
		break;
	default:
		if (debug)
			printf("%s 2.9 sealink.c 89/03/02\n\n", progname);
		printf("   SEAlink sliding window file transfer protocol\n");
		printf("v1.20  (C) 1986, 1987 System Enhancement Associates\n");
		printf("  ALL RIGHTS RESERVED   written by Thom Henderson\n");
		printf("      UNIX version written by Scott Reynolds\n\n");
		printf("Usage: sealink -[dfo]s filename...\n");
		printf("       sealink -[dfo]r [filename...]\n");
		printf("Options:\n");
		printf("  -d                Debug output to temporary file\n");
		printf("  -f                Force no sliding window\n");
		printf("  -o                Shut down overdrive mode\n");
		printf("  -s filename...    Send the file(s) specified\n");
		printf("  -r [filename...]  Receive file(s)\n");
		exit(1);
	}
	if (debug) {
		time_t	tim, time();
		char	*ctime();

		printf("Sending debug output to %s\n", dbugfile);
		freopen(dbugfile, "a", stderr); /* open log file */
		setbuf(stderr, NULL);
		(void) time(&tim);
		fputs(ctime(&tim), stderr);
	}
	fflush(stdout);			/* flush output before anything else */

#ifdef	SYSV
	(void) ioctl(0, TCGETA, &oldtty);	/* get terminal parameters */
	if (debug)
		fputs("tty parameters read\n", stderr);
	tty = oldtty;			/* copy them, then set new */
	tty.c_iflag = IGNBRK;		/* No input filter; ignore break */
	tty.c_oflag = 0;			/* Use transparent output */
	tty.c_lflag &= ~(ECHO|ICANON|ISIG);	/* disable echo, signals */
	tty.c_cc[VMIN] = 0;		/* AT&T Sys V: return immediately */
	tty.c_cc[VTIME] = 0;		/*   if no characters can be read */
	tty.c_cflag &= ~PARENB;		/* Leave baud rate, disable parity */
	tty.c_cflag &= ~CSIZE;		/* reset data bits */
	tty.c_cflag |= CS8;		/* set 8 bit data */
	(void) ioctl(0, TCSETAW, &tty);	/* go after setting terminal */
#endif /* SYSV */
#ifdef BSD
	(void) ioctl(0, TIOCGETP, &oldtty);	/* get terminal parameters */
	if (debug)
		fputs("tty parameters read\n", stderr);
	tty = oldtty;			/* copy them, then set new */
	tty.sg_flags = RAW;		/* raw mode (8 bit, no processing */
	(void) ioctl(0, TIOCSETP, &tty);	/* go after setting terminal */
#endif /* BSD */
	if (debug)
		fputs("tty parameters set\n", stderr);

	if (mode == SEND) {
		do {
			if (noerr = xmtfile(fn))
				sleep(2);	/* wait a few before next */
			if (optind < argc)
				fn = argv[optind++];
			else
				fn = NULL;
		} while (noerr && fn  != NULL);
		if (noerr)		/* no errors, send end marker */
			(void) xmtfile("");
	} else {
		do {
			if (optind < argc)	/* if filename given, use it */
				fn = argv[optind++];
			else		/* otherwise get one from remote */
				fn = "";
			if (noerr = (rcvfile(fn)  != NULL))
				sleep(2);	/* wait a few before next */
		} while (noerr);	/* go until done/err */
		noerr = xferdone;	/* set no error if done */
	}

#ifdef	SYSV
	(void) ioctl(0, TCSBRK, 1);	/* Wait for output to drain */
	(void) ioctl(0, TCFLSH, 2);	/* Flush input queue */
	(void) ioctl(0, TCSETAW, &oldtty);	/* Restore original modes */
	(void) ioctl(0, TCXONC, 1);	/* Restart output */
#endif /* SYSV */
#ifdef	BSD
	(void) ioctl(0, TIOCSETP, &oldtty);
#endif /* BSD */
	if (debug) {
		fputs("tty parameters restored\n", stderr);
		(void) fclose(stderr);
	}

	exit(!noerr);			/* and return error status */
	/*NOTREACHED*/
}

/*
 * chkout() returns non-zero if stdout and stderr are sending to
 * different files/devices, zero if the same
 */
int chkout()
{
	struct stat	so, se;

	(void)fstat(1, &so);
	(void)fstat(2, &se);
	return (so.st_rdev  != se.st_rdev);
}

#ifdef	NO_NAP
/*
 * alarmint() is called when an alarm signal is caught.
 */
void alarmint()
{
	longjmp(tohere, EOF);		/* return EOF to indicate timeout */
}
#endif	/* NO_NAP */

/*
 * com_getc(timeout) reads a character from file descriptor 0
 * timeout is in tenths of seconds
 * EOF returned if no character was available to read
 *
 * If timeout is 0, this routine will return immediately regardless of
 * the status of the read.  If timeout > 0, there will be a minimum of
 * one to two seconds before returning if nap() does not work.
 */
int com_getc(timeout)
register int	timeout;
{
	static char	byt[2];		/* buffer to read characters into  */

#ifdef	BSD
	if (!timeout) {			/* if no timeout then no alarms	   */
		long		len;	/* number of buffered characters   */

		(void) ioctl(0, FIONREAD, &len);	/* check buffer	   */
					/* read character if available	   */
		if (len > 0L && read(0, byt, 1) == 1)
			return (byt[0] & 0377);	/* return the character	   */
		return EOF;		/* error or none available	   */
	}
#endif /* BSD */
#ifdef	SYSV
	if (!timeout) {			/* if no timeout then no alarms	   */
		if (read(0, byt, 1) == 1)	/* if character was read,  */
			return (byt[0] & 0377);	/* return the character	   */
		return EOF;		/* error or none available	   */
	}
#endif /* SYSV */

#ifdef	NO_NAP
/* There's a timeout value, so now we get to use alarm() */
	timeout = ((timeout-1)/10)+1;	/* round to seconds		   */
	if (timeout == 1)		/* minimum of 2 seconds for alarm, */
		timeout++;		/*   since 1 may not be any delay  */
	if (setjmp(tohere)) {		/* if the alarm went off	   */
		if (debug)		/* timeout message if debugging	   */
			fputs("Read: timeout\n", stderr);
		return EOF;		/* return EOF (longjmp call)	   */
	}
	signal(SIGALRM, alarmint);	/* set up alarm signal catching	   */
	alarm((unsigned)timeout);	/* set alarm time		   */
	while (read(0, byt, 1) != 1)	/* Go until we read a character	   */
		;			/*   (or the alarm goes off!)	   */
	alarm(0);			/* reset alarm			   */
	signal(SIGALRM, SIG_DFL);	/* and turn off signal catching	   */
	return (byt[0] & 0377);		/* return the character		   */
#else	/* NO_NAP undefined -- nap() works */
	do {
		if (read(0, byt, 1) == 1)	/* did we read a char?	   */
			return (byt[0] & 0377);	/* yes, return it	   */
		(void) nap(100L);	/* sleep for a little while	   */
	} while (--timeout);		/* loop until time runs out	   */
	if (debug)			/* timeout message if debugging	   */
		fputs("Read: timeout\n", stderr);
	return EOF;
#endif	/* NO_NAP */
}

/*  File transmitter logic */

int xmtfile(name)			/* transmit a file */
char *name;				/* name of file to send */
{
	FILE	*f, *fopen();		/* file to send */
	int	endblk;			/* block number of EOT */
	struct stat	fst;		/* data about file */
	struct zeros	zero;		/* block zero data */
	char	*basename;		/* base filename */

	if (name && *name) {		/* if sending a file */
		if ((char *)(f = fopen(name, "r")) == NULL) {
			fprintf(stderr, "Can't read %s\n", name);
			return 0;
		}

		memset((char *)&zero, 0, sizeof(zero)); /* clear data block */

		tzset();
		stat(name, &fst);		/* get file information	*/
		zero.flen = (long)fst.st_size;
		zero.fstamp = fst.st_mtime - timezone;	/* adjust f/TZ	*/
		if (daylight)			/* if daylight savings,	*/
			zero.fstamp -= 3600L;	/* subtract an hour	*/
		if (zero.fstamp < 0L)
			zero.fstamp = (time_t) 0;
		if ((basename = strrchr(name, '/')) == NULL) {
			strcpy(zero.fnam, name);
		} else {
			basename++;
			strcpy(zero.fnam, basename);
		}
		if (debug)
			fprintf(stderr, "basename: %s\n", zero.fnam);
		strcpy(zero.prog, progname);
		zero.noacks = ackless;

		endblk = (int)((zero.flen+127L)/128L)+1;
	} else {
		endblk = 0;		/* fake for no file */
		if (debug)
			fputs("send transfer complete\n", stderr);
	}

	outblk = 1;			/* set starting state */
	ackblk = (-1);
	blksnt = slide = ackst = numnak = toterr = ackrep = ackseen = 0;
	chktec = 2;			/* undetermined */

	while (ackblk < endblk) {	/* while not all there yet */
		if (outblk <= ackblk + ((slide && sld_flag)? WINDOW : 1)) {    
			if (outblk < endblk) {    
				if (outblk > 0)
					sendblk(f, outblk);
				else
					shipblk((unsigned char *)&zero, 0);
				if (ackless && slide && sld_flag)
					ackblk = outblk;
			} else if (outblk == endblk) {    
				outb[0] = EOT;
				write(1, outb, 1);
				if (debug)
					fputs("sent EOT\n", stderr);
			}
			outblk++;
		}

		ackchk();
		if (numnak>10)
			goto abort;
	}

	if (endblk)
		(void) fclose(f);
	if (debug && toterr>2)
		fprintf(stderr, "%d errors/%d blocks\n", toterr, blksnt);
	return 1;                          /* exit with good status */

abort:
	if (endblk)
		(void) fclose(f);
	if (debug) {
		fputs("TRANSMIT ABORTED\n", stderr);
		if (toterr)
			fprintf(stderr, "%d errors/%d blocks\n", toterr, blksnt);
	}
	sendabort();
	return 0;                          /* exit with bad status */
}

/*
 * The various ACK/NAK states are:
 *  0:   Ground state, ACK or NAK expected.
 *  1:   ACK received
 *  2:   NAK received
 *  3:   ACK, block# received
 *  4:   NAK, block# received
 *  5:   Returning to ground state
 */
static ackchk()				/* check for ACK or NAK */
{
	int	c;			/* one byte of data */
	static int	rawblk;		/* raw block number */

	ackrep = 0;			/* nothing reported yet */

	while ((c = com_getc(0))  != EOF) {
		if (c == CAN) {				/* CANcel received? */
			if ((c = com_getc(20)) == CAN) {	/* get two  */
				numnak = 11;		/* OK, let's abort! */
				ackst = 0;
				if (debug)
					fputs("received cancel\n", stderr);
			}
			return;				/* break out of here */
		}
		if (ackst == 3 || ackst == 4) {		/* Windowed ACK/NAK */
			slide = 0;	/* assume this will fail */
					/* see if we believe the number */
			if (rawblk == (c^0xff)) {
				rawblk = outblk - ((outblk-rawblk)&0xff);
				if (rawblk >= 0 && rawblk <= outblk
						&& rawblk > outblk-128) {
					/* we have sliding window! */
					if (ackst == 3) {
						ackblk = ackblk > rawblk ? ackblk : rawblk;
						slide = 1;
						if (ackless && ++ackseen>10) {
							ackless = 0;
							if (debug)
								fputs("- Overdrive disengaged\n", stderr);
						}
					} else {    
						outblk = rawblk<0? 0 : rawblk;
						slide = numnak<4;
					}
					if (debug)
						fprintf(stderr, "%s %d == \n", ackst == 3?"ACK":"NAK", rawblk);
					ackrep = 1;  /* we reported something */
				}
			}
			ackst = 5;	/* return to ground state */
		}

		if (ackst == 1 || ackst == 2) {    
			rawblk = c;
			ackst += 2;
		}

		if (!slide || ackst == 0) {    
			if (c == ACK) {    
				if (!slide) {    
					ackblk++;
					if (debug)
						fprintf(stderr, "ACK %d --\n", ackblk);
					ackrep = 1;	/* reported an ACK */
				}
				ackst = 1;
				numnak = 0;
			} else if (c == 'C' || c == NAK) {    
				/* if method not determined yet */
				if (chktec>1)	/* then do what rcver wants */
					chktec = (c == 'C');
#ifdef	SYSV
				(void) ioctl(0, TCFLSH, 1); /* purge output */
#endif /* SYSV */
#ifdef	BSD
/* for now this code is commented out.  It causes more complications to have
 * it installed -- my wish list would include for a BSD ioctl() to purge only
 * the output, but at the moment things will just have to stay this way. */
/*				(void) ioctl(0, TIOCFLUSH, 0); /* purge i/o */
#endif /* BSD */
				if (!slide) {    
					outblk = ackblk+1;
					if (debug)
						fprintf(stderr, "NAK %d --\n", ackblk+1);
					ackrep = 1;	/* reported a NAK */
				}
				ackst = 2;
				numnak++;
				if (blksnt)
					toterr++;
			}
		}
		if (ackst == 5)
			ackst = 0;
	}
}

static sendblk(f, blknum)		/* send one block */
FILE *f;				/* file to read from */
int blknum;                            	/* block to send */
{
	long		blkloc;		/* address of start of block */
	unsigned char	buf[128];	/* one block of data */

	if (blknum  != blksnt+1) {	/* if jumping */
		blkloc = (long)(blknum-1) * 128L;
		fseek(f, blkloc, 0);	/* move where to */
	}
	blksnt = blknum;

	memset(buf, 26, 128);		/* fill buffer with control Zs */
	fread(buf, 1, 128, f);		/* read in some data */
	shipblk(buf, blknum);		/* pump it out to the receiver */
}

static void shipblk(blk, blknum)	/* physically ship a block */
unsigned char	*blk;			/* data to be shipped */
int	blknum;				/* number of block */
{
	register unsigned short	crc = 0;	/* CRC check value */
	register int	n;		/* index */
	unsigned char	*b = blk;	/* data pointer */

	outb[0] = SOH;			/* block header */
	outb[1] = blknum;			/* block number */
	outb[2] = blknum^0xff;		/* block number check value */

	for(n = 0;n < 128;n++) {	/* ship the data */
		if (chktec)
			crc = crc_update(crc, *b);
		else
			crc += *b;
		outb[n+3] = (*b++);
	}
	crc = crc_finish(crc);

	if (chktec) {			/* send proper check value */
		outb[131] = crc>>8;
		outb[132] = crc&0xff;
		write(1, outb, 133);
	} else {
		outb[131] = crc&0xff;
		write(1, outb, 132);
	}

	if (debug)
		fprintf(stderr, "sent block %d\n", blknum);

	return;
}

/*  File receiver logic */

char *rcvfile(name)			/* receive file */
char	*name;				/* name of file */
{
	int	c;			/* received character		*/
	int	tries;			/* retry counter		*/
	int	blknum;			/* desired block number		*/
	int	inblk;			/* this block number		*/
	FILE	*f;			/* file to receive to		*/
	char	buf[128];		/* data buffer			*/
	char	tmpname[100];		/* name of temporary file	*/
	static char	outname[100];	/* name of final file		*/
	struct zeros	zero;		/* file header data storage	*/
	int	endblk;			/* block number of EOT if known	*/
	long	left;			/* bytes left to output		*/
	int	getblock();		/* block receiver, status	*/
	int	fcopy();		/* copy source file to dest	*/
	void	setstamp();		/* set date/time stamp of file	*/
	int	cnvrt;			/* flag -- convert filename?	*/
	char	*onp;			/* use to convert filename to l/c */

	if (name && *name) {		/* figure out a name to use	 */
		strcpy(outname, name);	/* user supplied one		 */
		cnvrt = 0;		/* no conversion should be done	 */
	} else {    
		*outname = '\0';	/* get name from transmitter	 */
		cnvrt = 1;		/* convert to local is necessary */
	}

	strcpy(tmpname, ".sl.rcv.XXXXXX");	/* template for mktemp() */
	mktemp(tmpname);		/* use a unique temp filename	 */
	if (debug)
		fprintf(stderr, "tmpname: %s\n", tmpname);

	if (*outname && (f = fopen(outname, "r"))) {	/* open output file */
		(void) fclose(f);
		if (!(f = fopen(outname, "r+"))) {
			if (debug)
				fprintf(stderr, "Cannot write %s\n", tmpname);
			sendabort();
			return NULL;
		} else {
			(void) fclose(f);
		}
	}
	if (!(f = fopen(tmpname, "w"))) {	/* open temporary file */
		if (debug)
			fprintf(stderr, "Cannot create %s\n", tmpname);
		sendabort();
		return NULL;
	}

	blknum = *outname ? 1 : 0;	/* first block we must get */
	tries = -10;			/* kludge for first time around */
	chktec = 1;			/* try for CRC error checking */
	toterr = 0;			/* no errors yet */
	endblk = 0;			/* we don't know the size yet */
	ackless = 0;			/* we don't know about this yet */
	memset((char *)&zero, 0, sizeof(zero));	/* or much of anything else */

/*
	if (com_scan() == EOF)		/+ kludge for adaptive Modem7 +/
		goto nextblock;
*/
nakblock:				/* we got a bad block */
	if (blknum>1)
		toterr++;
	if (++tries>10)
		goto abort;
	if (tries == 0)			/* if CRC isn't going */
		chktec = 0;		/* then give checksum a try */

	sendack(S_NAK, blknum);		/* send the NAK */
	if (ackless && toterr > 20) {	/* if ackless mode isn't working */
		ackless = 0;		/* then shut it off */
		if (debug)
			fputs("- Overdrive disengaged\n", stderr);
	}
	goto nextblock;

ackblock:				/* we got a good block */
nextblock:				/* start of "get a block" */
	while ((c = com_getc(30))  != EOF) {
		if (c == CAN)
			if ((c = com_getc(30)) == CAN) {
				sendabort();
				return NULL;
			} else
				break;
		if (c == EOT) {    
			if (!endblk || endblk == blknum)
				goto endrcv;
		} else if (c == SOH) {    
			if ((inblk = com_getc(5)) == EOF)
				goto nakblock;
			if (com_getc(5) == (inblk^0xff)) { 
				if (debug)
					fprintf(stderr, "received #%d\n", inblk);
				goto blockstart;	/* we found a start */
			}
		}
	}
	goto nakblock;

blockstart:				/* start of block detected */
	c = blknum&0xff;
	if (inblk == 0 && blknum <= 1) {	/* if this is the header */
		if (!getblock((char *)&zero)) {    
			sendack(S_ACK, inblk);	/* ack the header */
			if (!*name)	/* given name takes precedence */
				strcpy(outname, zero.fnam);
			if (left = zero.flen)	/* length to transfer */
				endblk = (int)((left+127L)/128L)+1;
			if (ackless != zero.noacks && debug)
				fprintf(stderr, "+ Overdrive %sengaged\n", 
					zero.noacks?"":"dis");
			ackless = zero.noacks;

			blknum = 1;	/* now we want first data block */
			goto ackblock;
		} else {
			goto nakblock;		/* bad header block */
		}
	} else if (inblk == c) {		/* if this is the one we want */
		if (!getblock(buf)) {		/* else if we get it okay */
			if (!ackless)		/* if we're sending ACKs */
				sendack(S_ACK, inblk);	/* then ACK the data */
	/*
	 * if file size is not known or more than 128 bytes
	 * to go, write one block (128 bytes) to the file
	 */
			if (!endblk || left >= 128L) {
				if (fwrite(buf, 1, 128, f) != 128) {
					if (debug)
						fputs("- WRITE ERROR\n", stderr);
					goto abort;
				}
				left -= 128L;	/* 128 less to do... */
	/*
	 * we know the size of the file and there are less than
	 * 128 bytes to write out; only do the necessary amount
	 */
			} else if (left > 0) {
				if (fwrite(buf, 1, (unsigned)left, f) != (int)left) {
					if (debug)
						fputs("- WRITE ERROR\n", stderr);
					goto abort;
				}
				left = 0;	/* and then there were none */
			}
			tries = 0;		/* reset try count */
			blknum++;		/* we want the next block */
			goto ackblock;
		} else {
			goto nakblock;		/* ask for a resend */
		}
	} else if (inblk < c || inblk > (c + 100)) {	/* if we have it, */
		(void) getblock(buf);		/* ignore it */
		sendack(S_ACK, inblk);		/* but ack it */
		goto ackblock;
	} else
		goto nextblock;			/* else if running ahead */

endrcv:
#ifdef	NAKEOT
	sendack(S_NAK, blknum);			/* NAK the EOT, make sure */
	if (com_getc(20) != EOT)		/* we're all done */
		goto nakblock;
#endif /* NAKEOT */
	sendack(S_ACK, blknum);			/* ACK it and clean up */
	if (debug)
		fputs("received EOT\n", stderr);
	if (blknum>1) {				/* if we really got anything */
		if (debug && toterr>2)
			fprintf(stderr, "%d errors/%d blocks\n", toterr, blknum-1);
		(void) fclose(f);
		(void) unlink(outname);		/* rename temp to proper name */

		for (onp = outname;cnvrt && *onp;onp++)
						/* find out if there's lower */
			if (islower(*onp))	/* case letters in filename  */
				cnvrt = 0;	/*  there are, don't convert */
		if (cnvrt)			/* if there aren't, make all */
			for (onp = outname;*onp;onp++)	/* into lowercase */
				*onp = tolower(*onp);
		if (link(tmpname, outname) == 0 || fcopy(tmpname, outname) == 0)
			(void) unlink(tmpname);
		else if (debug)
			fputs("can't rename or copy file\n", stderr);
		if (zero.fstamp)		/* set stamp, if known */
			setstamp(outname, zero.fstamp);
		if (debug)
			fprintf(stderr, "received file: %s\n", outname);
		return outname;			/* signal what file we got */
	} else {				/* else no real file */
		(void) fclose(f);
		(void) unlink(tmpname);		/* discard empty file */
		if (debug)
			fputs("received end\n", stderr);
		xferdone = 1;			/* signal end of transfer */
		return NULL;
	}

abort:
	if (debug) {
		fputs("RECEIVE ABORTED\n", stderr);
		if (toterr)
			fprintf(stderr, "%d errors/%d blocks\n", toterr, blknum-1);
	}
	(void) fclose(f);
	xferdone = 1;				/* signal end of transfer */
	sendabort();
	return NULL;
}

int fcopy(file1, file2)		/* copy 'file1' to 'file2'	*/
char	*file1;			/* source filename		*/
char	*file2;			/* destination filename		*/
{
	FILE		*f, *t;	/* files from and to		*/
	long		size;	/* bytes to copy		*/
	char		*buf;	/* buffer pointer		*/
	char		*malloc();	/* buffer allocator	*/
	unsigned int	bufl;	/* buffer length		*/
	unsigned int	cpy;	/* bytes being copied		*/
	struct stat	fst;	/* data about file		*/

	if (stat(file1, &fst))	/* get file information	*/
		return EOF;
	size = (long)fst.st_size;

	if ((char *)(f = fopen(file1, "r")) == NULL)
		return EOF;
	if ((char *)(t = fopen(file2, "w")) == NULL) {
		(void) fclose(f);
		return EOF;
	}

	bufl = 32760;
	if (bufl > size)
		bufl = size;	/* don't waste space */

	while (bufl >= 128 && (buf = malloc(bufl)) == NULL)
		bufl >>= 1;	/* keep trying until it's hopeless	*/
	if (buf == NULL) {	/* if we can't get a buffer, clean up	*/
		(void) fclose(f);
		(void) fclose(t);
		(void) unlink(file2);
		return EOF;	/* return an error indication		*/
	}

	while (size > 0) {
		cpy = fread(buf, sizeof(char),
			bufl < size ? bufl : (unsigned short) size, f);
		if (fwrite(buf, sizeof(char), cpy, t) != cpy)
			break;
		size -= cpy;
	}

	free(buf);
	(void) fclose(f);
	(void) fclose(t);
	return (size > 0);
}

static void setstamp(f_name, f_time)	/* set a file's date/time stamp	*/
char	*f_name;			/* file to set stamp on		*/
long	f_time;				/* desired date/time		*/
{
	void	tzset();		/* library time zone function	*/
	time_t	times[2], time();

	times[0] = time((time_t *) 0);
	tzset();
	times[1] = f_time + TENYEAR + timezone;	/* convert time		*/
	if (daylight)				/* if daylight savings,	*/
		times[1] += 3600L;		/* add an hour		*/
	utime(f_name, times);
}

static int sendack(acknak, blknum)	/* send an ACK or a NAK */
register int	acknak;			/* 1 = ACK, 0 = NAK */
register int	blknum;			/* block number */
{
	if (debug)
		fprintf(stderr, "%s %d\n", acknak?"ACK":"NAK", blknum);
	if (acknak)			/* send the right signal */
		outb[0] = ACK;
	else if (chktec)
		outb[0] = 'C';
	else
		outb[0] = NAK;

	outb[1] = blknum;		/* block number */
	outb[2] = blknum^0xff;		/* block number check */
	write(1, outb, 3);
}

static int getblock(buf)		/* read a block of data */
char	*buf;				/* data buffer */
{
	register unsigned short	ourcrc = 0;	/* remote CRC check value */
	unsigned short	hiscrc;			/* remote CRC check value */
	register int	c;			/* one byte of data */
	int	n;				/* index */
	int	timeout = ackless ? 200 : 5;	/* short block timeout */

	for(n = 0; n<128; n++) {    
		if ((c = com_getc(timeout)) == EOF) {
			if (debug)
				fputs("block received -- short\n", stderr);
			return 1;
		}
		if (chktec)
			ourcrc = crc_update(ourcrc, (unsigned char) c);
		else
			ourcrc += c;
		*buf++ = (unsigned char) c;
	}

	if (chktec) {    
		ourcrc = crc_finish(ourcrc);
		c = com_getc(timeout);
		hiscrc = (c << 8) | com_getc(timeout);
	} else {    
		ourcrc &= 0xff;
		hiscrc = com_getc(timeout) & 0xff;
	}

	if (debug) {
		if (ourcrc == hiscrc)
			fputs("block received -- good\n", stderr);
		else {
			fprintf(stderr, "block received -- bad %s\n", chktec?"CRC":"checksum");
			fprintf(stderr, "his = 0x%x  ours = 0x%x\n", hiscrc, ourcrc);
		}
	}
	if (ourcrc == hiscrc)
		return 0;		/* block is good */
	else
		return 1;		/* error in checksum or CRC */
}

void sendabort()
{
#ifdef	SYSV
	(void) ioctl(0, TCFLSH, 1);	/* purge output */
#endif /* SYSV */
#ifdef	BSD
	(void) ioctl(0, TIOCFLUSH, 0);	/* purge all i/o */
#endif /* BSD */
	strcpy(outb, "\030\030\030\030\030\030\030\030\b\b\b\b\b\b\b\b");
	strcat(outb, "\030\030\030\030\b\b\b\b");	/* set up cancel seq */
	write(1, outb, (unsigned)strlen(outb));	/* write it out */
}

#ifdef	NO_MEM
/*
 *	This routine replicates the function found in the
 *	System V memory.h module for systems without a proper
 *	or with no implementation of this function available.
 */
char *memset(m, n, c)
char	*m;
int	n;
char	c;
{
	int	i;
	for (i = 0;i < n;i++)
		*(m+i) = c;
	return m;
}
#endif /* NO_MEM */
