/*
 * A version of Ward Christensen's file transfer protocol for
 * Unix System V or 4.2 bsd.
 *
 *        Emmet P. Gray, ..!ihnp4!uiucuxc!fthood!egray, 16 Aug 85
 *
 * Modified by Sanford Zelkovitz   08/18/86
 * Last modification date: 05/20/87
 */

#define SV
#undef  BSD

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef SV
#include <termio.h>
#endif
#ifdef BSD
#include <sgtty.h>
#endif

#define MAXERRORS 10			/* max number of times to retry */
#define SECSIZE	128			/* CP/M sector, transmission block */
#define CPMEOF	26			/* End Of File (for CP/M) */
#define SOH	1			/* Start Of Header */
#define EOT	4			/* End Of Transmission */
#define ACK	6			/* ACKnowledge */
#define NAK	21			/* Negative AcKnowledge */
#define CAN	24			/* CANcel */

int synchron;
int exit_return;
unsigned char crc1, crc2;
#ifdef SV
struct termio ttyhold;
#endif
#ifdef BSD
struct sgttyb ttyhold;
#endif

main(argc, argv)
int argc;
char *argv[];
{
	int msgstat;
	char *tty, *ttyname();
	struct stat stbuf;
	exit_return=0;
	if (argc != 3) {
		usage();
		exit(1);
	}
	tty = ttyname(1);
	stat(tty, &stbuf); 
	msgstat = (stbuf.st_mode & 0777);
	chmod(tty, 0600);			/* mesg n */
#ifdef SV
	ioctl(0, TCGETA, &ttyhold);		/* get current settings */
#endif
#ifdef BSD
	ioctl(0, TIOCGETP, &ttyhold);
#endif
	switch (*argv[1]) {
		case 'r':
			recvfile(argv[2]);
			break;
		case 's':
			sendfile(argv[2]);
			break;
		default:
			usage();
	}
#ifdef SV
	ioctl(0, TCSETAF, &ttyhold);		/* restore settings */
#endif
#ifdef BSD
	ioctl(0, TIOCSETP, &ttyhold);
#endif
	chmod(tty, msgstat);			/* restore mesg status */
	exit(exit_return);
}

/* send a file to the remote */
sendfile(tfile)
char *tfile;
{
	FILE *fp;
	unsigned char chr, checksum, block, sector[SECSIZE];
	int i, mode, nbytes, errcount, size, speed;
	long min, sec;
	static int baud[16] = {0, 50, 75, 110, 134, 150, 200,
	300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400};
	struct stat sbuf;

	if (!(fp = fopen(tfile, "r"))) {
		fprintf(stderr, "xmodem: Can't open '%s' for read\r\n", tfile);
		exit_return=1;
		return;
	}
	stat(tfile, &sbuf);
	size = (sbuf.st_size / 128) + 1;
#ifdef SV
	speed = baud[ttyhold.c_cflag & 017];
#endif
#ifdef BSD
	speed = baud[ttyhold.sg_ispeed];
#endif
	sec = size;
	sec = sec * 128L * 11L / speed;
	min = sec / 60L;
	sec = sec - min * 60L;
	printf("File open: %d records\r\n", size);
	printf("Send time: %ld min, %ld sec at %d baud\r\n", min, sec, speed);
	printf("To cancel: use CTRL-X numerous times\r\n");
	printf("Waiting ready signal\r\n");

	rawmode();
	errcount = 0;
	mode = 0;
	block = 1;
	while (errcount < MAXERRORS) {
		chr = getchar_t();
		if (chr == NAK)			/* checksum mode */
			break;
		if (chr == 'C') {		/* CRC mode */
			mode = 1;
			break;
		}
		errcount++;
	}
	if (errcount == MAXERRORS) {
		sleep(3);
		fprintf(stderr, "xmodem: Timed out on acknowledge\r\n");
		exit_return=1;
		return;
	}
	while (nbytes = fread(sector, sizeof(sector[0]), SECSIZE, fp)) {
		if (nbytes < SECSIZE) {		/* fill short sector */
			for (i=nbytes; i < SECSIZE; i++)
				sector[i] = CPMEOF;
		}
		errcount = 0;
		while (errcount < MAXERRORS) {
			putchar(SOH);		/* the header */
			putchar(block);		/* the block number */
			chr = ~block;
			putchar(chr);		/* it's complement */
			checksum = 0;
			crc1 = 0;
			crc2 = 0;
			for (i=0; i < SECSIZE; i++) {
				putchar(sector[i]);
				if (mode)
					update_crc(sector[i]);
				else
					checksum += sector[i];
			}
			if (mode) {
				update_crc(0);
				update_crc(0);
				putchar(crc1);
				putchar(crc2);
			}
			else
				putchar(checksum);
rec_loop:
			chr = getchar_t();
			if (chr == CAN) {
				sleep(3);
				exit_return=1;
				fprintf(stderr,"\r\nxmodem: Abort request received\r\n");
				return;
			}
			if (chr == ACK)
				break;		/* got it! */
			if (chr != NAK) goto rec_loop;  /* Noise on line? */
			errcount++;
		}
		if (errcount == MAXERRORS) {
			error();
			exit_return=1;
			return;
		}
		block++;
	}
	errcount = 0;
	exit_return=1;
	while (errcount < MAXERRORS) {
		putchar(EOT);
		if (getchar_t() == ACK)
			{
			exit_return=0;
			break;
			}
		errcount++;
	}
	return;
}

/* receive a file from the remote */
recvfile(tfile)
char *tfile;
{
	FILE *fp;
	unsigned char hdr, blk, cblk, tmp, cksum;
	unsigned char c1, c2, sum, block, sector[SECSIZE];
	int i, stop = 0, mode, errcount;
	void resync();
	long true_end;
	char ans[40];

	if (!access(tfile, 00)) {
		while (1) {
			printf("File already exists \r\n");
				return;
		}
	}

	if (!(fp = fopen(tfile, "w"))) {
		fprintf(stderr, "xmodem: Can't open '%s' for write\r\n", tfile);
		return;
	}
	printf("File open - ready to receive\r\n");
	rawmode();
	errcount = 0;
	block = 1;
	
	sleep(10);
	while (errcount < MAXERRORS) {
		if (errcount < (MAXERRORS / 2)) {
			putchar(NAK);		/* try checksum mode first */
			mode = 0;
		}
		else {
			putchar('C');		/* then crc */
			mode = 1;
		}
		if ((hdr = getchar_t()) == SOH) {
			ungetc(SOH, stdin);
			break;
		}
		errcount++;
	}
	if (errcount == MAXERRORS) {
		sleep(3);
		fprintf(stderr, "\r\nxmodem: Timed out on acknowledge\r\n");
		return;
	}
	errcount = 0;

	while (errcount < MAXERRORS) {
		hdr = getchar_t();
		if (hdr == CAN) {
			sleep(3);
			fprintf(stderr, "\r\nxmodem: Abort request received\r\n");
			return;
		}
		if (hdr == EOT)			/* done! */
			break;
		if (hdr != SOH) {		/* read in junk for 6 seconds */
			synchron = 0;		/*  to re-synchronized block */
			signal(SIGALRM, resync);
			alarm(6);
			while(synchron == 0)
				hdr = getchar();
			goto nak;
		}
		blk = getchar_t();
		cblk = getchar_t();
		crc1 = 0;
		crc2 = 0;
		sum = 0;
		for (i=0; i < SECSIZE; i++) {
			sector[i] = getchar_t();
			if (mode)
				update_crc(sector[i]);
			else
				sum += sector[i];
		}
		if (mode) {
			c1 = getchar_t();
			c2 = getchar_t();
		}
		else
			cksum = getchar_t();
		if (blk != block && blk != (block - 1))
			goto nak;
		tmp = ~blk;
		if (cblk != tmp)
			goto nak;
		if (mode) {
			update_crc(0);
			update_crc(0);
			if (c1 != crc1 || c2 != crc2)
				goto nak;
		}
		else {
			if (cksum != sum)
				goto nak;
		}
		if (block == blk) {
			fflush(fp);
			fwrite(sector, sizeof(sector[0]), SECSIZE, fp);
		}
		block = blk + 1;
		putchar(ACK);			/* got it! */
		errcount = 0;
		continue;

	nak:	putchar(NAK);			/* do it over */
		errcount++;
	}
	if (errcount == MAXERRORS) {
		error();
		return;
	}
	putchar(ACK);
	for (i = SECSIZE -1; i >= 0; i--) {	/* find true EOF */
		if (sector[i] != CPMEOF) {
			stop = i;
			break;
		}
	}
/*
 * Some CPM systems don't pad the end of the file with ^Z's so the file may
 * have junk at the end.  A conservative approach had to be taken in order
 * for Unix object code (where ^Z's may be valid data) to transfer properly.
 */
	true_end = ftell(fp) - SECSIZE + stop +1;
	fclose(fp);
	truncate(tfile, true_end);
	return;
}

/* give minimal usage message */
usage()
{
	fprintf(stderr, "Usage: xmodem [ s | r ] filename\r\n");
	fprintf(stderr, "       options are 's' for send or 'r' for receive\r\n");
	return;
}

/* exceeded the maximum number of retry's */
error()
{
	putchar(CAN);
	putchar(CAN);
	putchar(CAN);
	putchar(CAN);
	sleep(3);
	fprintf(stderr, "\r\nxmodem: Exceeded error limit...aborting\r\n");
	return;
}

/* update the CRC bytes */
update_crc(c)
unsigned char c;
{
	int i, temp;
	unsigned char carry, c_crc1, c_crc2;
	for (i=0; i < 8; i++) {
		temp = c * 2;
		c = temp;			/* rotate left */
		carry = ((temp > 255) ? 1 : 0);
		temp = crc2 * 2;
		crc2 = temp;
		crc2 |= carry;			/* rotate with carry */
		c_crc2 = ((temp > 255) ? 1 : 0);
		temp = crc1 * 2;
		crc1 = temp;
		crc1 |= c_crc2;
		c_crc1 = ((temp > 255) ? 1 : 0);
		if (c_crc1) {
			crc2 ^= 0x21;
			crc1 ^= 0x10;
		}
	}
	return;
}

/* getchar with a 10 sec time out */
getchar_t()
{
	void force_it();
	unsigned char c;
	signal(SIGALRM, force_it);
	alarm(10);				/* only have 10 sec... */
	c = getchar();
	alarm(0);
	return(c);
}

/*
 * This code (and the resync() below) is the most machine dependent part
 * of the program.  The action of the signal SIGALRM during a read system
 * call is not well defined.  Some systems return the stack to the point
 * outside the system call, others inside the call itself.  Have fun...
 */
force_it()
{
	unsigned char c;
	c = CPMEOF;				/* arbitrary default char */
#ifdef SV
	ungetc(c, stdin);
#endif
#ifdef BSD
	ioctl(0, TIOCSTI, &c);
#endif
	return;
}

/* truncate file to given length */
truncate(path, length)
char *path;
long length;
{
	FILE *fp, *tempfp;
	long i;
	char c, string[80], *tempfile, *mktemp();
	if (!(fp = fopen(path, "r"))) {
		fprintf(stderr, "xmodem: Can't open '%s' for read\r\n", path);
		return;
	}
	tempfile = mktemp("/tmp/trunXXXXXX");
	if (!(tempfp = fopen(tempfile, "w"))) {
		fprintf(stderr, "xmodem: Can't open temporary file\r\n");
		return;
	}
	for (i=0; i < length; i++) {
		c = fgetc(fp);
		fputc(c, tempfp);
	}
	fclose(fp);
	fclose(tempfp);
	sprintf(string, "mv %s %s", tempfile, path);
	system(string);
	return;
}

/* put the stdin/stdout in the "raw" mode */
rawmode()
{
#ifdef SV
	struct termio tbuf;
	ioctl(0, TCGETA, &tbuf);
	tbuf.c_cc[4] = 1;			/* VMIN */
	tbuf.c_cc[5] = 0;			/* VTIME */
	tbuf.c_iflag = 0;
	tbuf.c_oflag = 0;
	tbuf.c_lflag = 0;
	tbuf.c_cflag &= ~CSIZE;
	tbuf.c_cflag |= CS8;
	tbuf.c_cflag &= ~PARENB;
	ioctl(0, TCSETAF, &tbuf);
	return;
#endif
#ifdef BSD
	struct sgttyb sgbuf;
	ioctl(0, TIOCGETP, &sgbuf);
	sgbuf.sg_flags |= RAW;
	sgbuf.sg_flags &= ~ECHO;
	ioctl(0, TIOCSETP, &sgbuf);
	return;
#endif
}

/*  after 6 seconds of reading junk data... */
resync()
{
	char c;
	synchron = 1;				/* set the flag */
	c = SOH;
#ifdef SV
	ungetc(c, stdin);
#endif
#ifdef BSD
	ioctl(0, TIOCSTI, &c);
#endif
	return;
}
