// --------------------------------------------------------------------------
// Citadel: ZModem.CPP
//
// Source file for Citadel Zmodem.


#include "zmodem.h"

static Bool needcr = FALSE;

static void cdecl z_message(const char *fmt, ...)
	{
	va_list arg_ptr;

	va_start(arg_ptr, fmt);

	if (fmt == NULL)
		{
		return;
		}

	vsprintf(e_input, fmt, arg_ptr);

	doccr();
	cPrintf(pcts, e_input);

	va_end(arg_ptr);

	needcr = TRUE;
	}

static void cdecl z_status(const char *fmt, ...)
	{
	va_list arg_ptr;

	va_start(arg_ptr, fmt);

	if (fmt == NULL)
		{
		return;
		}

	vsprintf(e_input, fmt, arg_ptr);

	if (needcr)
		{
		doccr();
		needcr = FALSE;
		}

	cPrintf(br);
	cPrintf(e_input);

	va_end(arg_ptr);
	}

static void show_loc(ulong l, uint w)
	{
	cPrintf(getzmmsg(1), l, w);
	doccr();
	}

static void dostime(int *hour, int *min, int *sec, int *hdths)
	{
	union REGS r;

	r.h.ah = 0x2c;

	intdos(&r, &r);

	*hour = r.h.ch;
	*min = r.h.cl;
	*sec = r.h.dh;
	*hdths = r.h.dl;
	}

/*
 * This file contains routines to implement a simple multiple alarm system.
 * The routines allow setting any number of alarms, and then checking if any
 * one of them has expired.  It also allows adding time to an alarm.
 */

/*
 * long timerset (t) uint t;
 * 
 * This routine returns a timer variable based on the MS-DOS time.	The variable
 * returned is a long which corresponds to the MS-DOS time at which the timer
 * will expire.  The variable need never be used once set, but to find if the
 * timer has in fact expired, it will be necessary to call the timeup
 * function. The expire time 't' is in hundredths of a second.
 * 
 * Note: This routine as coded had a granularity of one week. I have put the
 * code that implements that inside the flag "HIGH_OVERHEAD". If you want it,
 * go ahead and use it. For BT's purposes, (minute) granularity should
 * suffice.
 * 
 */
static long timerset(uint t)
	{
	long l;

#ifdef HIGH_OVERHEAD
	int l2;
#endif
	int hours, mins, secs, ths;

#ifdef HIGH_OVERHEAD
	extern int week_day();
#endif

	/* Get the DOS time and day of week */
	dostime(&hours, &mins, &secs, &ths);

#ifdef HIGH_OVERHEAD
	l2 = week_day();
#endif

	/* Figure out the hundredths of a second so far this week */
	l =

#ifdef HIGH_OVERHEAD
		l2 * PER_DAY + (hours % 24) * PER_HOUR +
#endif
		(mins % 60) * PER_MINUTE + (secs % 60) * PER_SECOND + ths;

	/* Add in the timer value */
	l += t;

	/* Return the alarm off time */
	return (l);
	}

/*
 * int timeup (t) long t;
 * 
 * This routine returns a 1 if the passed timer variable corresponds to a timer
 * which has expired, or 0 otherwise.
 */
static int timeup(long t)
	{
	long l;

	/* Get current time in hundredths */
	l = timerset(0);

	/* If current is less then set by more than max int, then adjust */
	if (l < (t - 65536L))
		{
#ifdef HIGH_OVERHEAD
		l += PER_WEEK;
#else
		l += PER_HOUR;
#endif
		}

	/* Return whether the current is greater than the timer variable */
	return ((l - t) >= 0L);
	}

/* -------------------------------------------------------------------- */
/*	THROUGHPUT															*/
/*	Print throughput message at end of transfer 						*/
/* -------------------------------------------------------------------- */
static void throughput(int opt, ulong bytes)
	{
	static long started = 0L;
	static long elapsed;
	static long cps;

	if (!opt)
		{
		started = time(NULL);
		}
	else if (started)
		{
		elapsed = time(NULL);

		/* The next line tests for day wrap without the date rolling over */
		if (elapsed < started)
			{
			elapsed += 86400L;
			}

		elapsed -= started;

		if (elapsed == 0L)
			{
			elapsed = 1L;
			}

		cps = (long) (bytes / (ulong) elapsed);
		started = (cps * 1000L) / ((long) baud_rate);

		z_message(getzmmsg(2), cps, bytes, started);
		}
	}

static void big_pause(int secs)
	{
	long timeout;

	timeout = timerset((uint) (secs * 100));
	while (!timeup(timeout))
		{
		if ((*statrs)())
			{
			break;
			}
		}
	}

static void send_can()
	{
	int i;

	(*flushrs)();

	for (i = 0; i < 10; i++)
		{
		(*putrs)(CAN);
		}

	for (i = 0; i < 10; i++)
		{
		(*putrs)(BS);
		}
	}

static void show_debug_name(int index)
	{
	if (debug)
		{
		z_message(getdbmsg(index));
		}
	}

/*--------------------------------------------------------------------------*/
/* UNIQUE_NAME																*/
/* Increments the suffix of a filename as necessary to make the name unique */
/*--------------------------------------------------------------------------*/
static void unique_name(char *fname)
	{
	register char *p;
	register int n;

	if (filexists(fname))
		{			/* If file already exists... */
		p = fname;

		while (*p && *p != '.')
			{
			p++;		/* ...find the extension, if any */
			}

		for (n = 0; n < 4; n++) /* ...fill it out if neccessary */
			{
			if (!*p)
				{
				*p = getzmmsg(48)[n];
				*(++p) = '\0';
				}
			else
				{
				p++;
				}
			}

		while (filexists(fname))
			{		/* ...If 'file.ext' exists suffix++ */
			p = fname + strlen(fname) - 1;

			for (n = 3; n--;)
				{
				if (!isdigit(*p))
					{
					*p = '0';
					}

				if (++(*p) <= '9')
					{
					break;
					}
				else
					{
					*p-- = '0';
					}
				}	/* for */
			}		/* while */
		}			/* if exist */
	}				/* unique_name */

static Bool got_ESC()
	{
	while (KBReady())
		{
		/* ESC pressed? */
		if (KeyboardBuffer.Retrieve() == ESC)
			{
			KeyboardBuffer.Flush();
			return (TRUE);
			}
		}

	return (FALSE);
	}


/*
 * Handle uploads from terminal mode. Note: filepath parameter below will be
 * trashed if it's a wildcard!
 */

int Zmodem_Send_Batch(const char *filepath, uint baud)
	{
	if (!read_zm_messages())
		{
		mPrintf(getmsg(188), getmsg(1395));
		return (0);
		}

	cr3tab = (const ulong *) zmmsgs->next->data;
	crctab = (const uint *) zmmsgs->next->next->data;

	char *p;
	int err = 1;	/* Xmodem, Ymodem, Telink flag */
	int count, i;
	char temp[81];
	char *words[256];

	char currentfilename[81];
	struct find_t c_file;

	strcpy(temp, filepath);
	count = parse_it(words, temp);

	if (count == 0)
		{
		z_message(getzmmsg(3));
		dump_zm_messages();

		return (0);
		}

	baud_rate = baud;		/* kinda icky */

	Txbuf = (byte *) calloc(1, BUFSIZE + 16);

	if (!Txbuf)
		{
		mPrintf(getmsg(188), getmsg(1395));
		dump_zm_messages();
		return (0);
		}

	e_input = (char *) calloc(1, 255);

	if (!e_input)
		{
		free(Txbuf);
		mPrintf(getmsg(188), getmsg(1395));
		dump_zm_messages();
		return (0);
		}

	/* Disambiguate */
	for (i = 0, err = 1; (i < count) && err; i++)
		{
		if (_dos_findfirst(words[i], _A_NORMAL, &c_file))
			{
			continue;
			}

		strcpy(currentfilename, words[i]);

		/*
		* Find the delimiter on the pathspec for use by the various
		* batch protocols.
		*/

		p = strrchr(currentfilename, '\\');

		if (p == NULL)
			{
			p = strrchr(currentfilename, '/');
			}

		if (p == NULL)
			{
			p = strchr(currentfilename, ':');
			}

		if (p == NULL)
			{
			p = currentfilename;
			}
		else
			{
			p++;
			}

		/*
		* At this point *p points to the location in the input string
		* where the prepended path information ends. All we need to do,
		* then, is to keep plugging in the stuff we get from
		* _dos_find(first|next) and transfer files. We already have the
		* first matching filename from the _dos_findfirst we did above,
		* so we use a "do" loop.
		*/

		do
			{
			/* Append the current filename */
			strcpy(p, c_file.name);

			/* Send the file with the proper protocol */
			err = Send_Zmodem(currentfilename, 0);
			} while ((err) && (!_dos_findnext(&c_file)));

		/* Finish the proper protocol if need be */
		};

	if (err)
		{
		Send_Zmodem(NULL, END_BATCH);
		}

	free(Txbuf);
	free(e_input);
	dump_zm_messages();

	return (err);
	}

/*
 * Handle downloads from terminal mode.
 * 
 * If filespec is NULL and we need a filename, ask for it. Otherwise, assume
 * that the spec we got is OK and use it.
 */

int Zmodem_Receive_Batch(const char *filepath, uint baud)
	{
	if (!read_zm_messages())
		{
		mPrintf(getmsg(188), getmsg(1396));
		return (0);
		}

	cr3tab = (const ulong *) zmmsgs->next->data;
	crctab = (const uint *) zmmsgs->next->next->data;

	int err = 1;

	baud_rate = baud;		/* kinda icky */

	Txbuf = (byte *) calloc(1, BUFSIZE + 16);

	if (!Txbuf)
		{
		mPrintf(getmsg(188), getmsg(1396));
		dump_zm_messages();
		return (0);
		}

	e_input = (char *) calloc(1, 255);

	if (!e_input)
		{
		free(Txbuf);
		mPrintf(getmsg(188), getmsg(1396));
		dump_zm_messages();
		return (0);
		}

	err = get_Zmodem(filepath, NULL);

	free(Txbuf);
	free(e_input);
	dump_zm_messages();

	return (err);
	}


/*
 * utime function for Turbo / Borland C.
 * 
 * We should make this more generic in case some other DOS compiler comes up
 * lacking, but for now the only one we have that needs it happens to be
 * Borland.
 * 
 */

static int cdecl b_utime(const char *name, struct utimbuf * times)
	{
	int handle;
	struct date d;
	struct time t;
	struct ftime ft;

	unixtodos(times->modtime, &d, &t);

	ft.ft_tsec = t.ti_sec / 2;
	ft.ft_min = t.ti_min;
	ft.ft_hour = t.ti_hour;
	ft.ft_day = d.da_day;
	ft.ft_month = d.da_mon;
	ft.ft_year = d.da_year - 1980;

	if ((handle = open(name, O_RDONLY)) == -1)
		{
		return (-1);
		}

	setftime(handle, &ft);
	close(handle);
	return (0);
	}

static int got_error(const char *string1, const char *string2)
	{
	if (errno == 0x18)
		{
		errno = 0;
		}

	if (errno != 0)
		{
		z_message(getzmmsg(4), errno, string1, string2);
		errno = 0;
		return (1);
		}

	return (0);
	}

static long zfree(char *drive)
	{
	union REGS r;

	uchar driveno;
	long stat;

	if (drive[0] != '\0' && drive[1] == ':')
		{
		driveno = (uchar) (islower(*drive) ? toupper(*drive) : *drive);
		driveno = (uchar) (driveno - 'A' + 1);
		} 
	else
		{
		driveno = 0;		/* Default drive	*/
		}

	r.x.ax = 0x3600;		/* get free space	*/
	r.h.dl = driveno;		/* on this drive	*/
	int86(0x21, &r, &r);	/* go do it 		*/

	if (r.x.ax == 0xffff)	/* error return??	*/
		{
		return (0);
		}

	stat = (long) r.x.bx	/* bx = clusters avail	*/
		* (long) r.x.ax 	/* ax = sectors/clust	*/
		* (long) r.x.cx;	/* cx = bytes/sector	*/

	return (stat);
	}

#ifdef MILQ
/*
 * Dst	- Where to put this.  Should use a static buffer, if a destination
 * is not provided
 * 
 * Src	- The filename to be conditioned
 * 
 * TblNm - Name of the file to use for the source of path conditioning entries
 * 
 * Mode -
 */

char *ZMdmFlNmCndtn(char *Dst, char *Src, char *Tbl, int Mode)
	{
	char *p;
	char *q;

	for (p = Src, q = Dst; *p;)
		{
		switch (*p)
			{
			case '/':
			case '\\':
				{
				switch (UsePaths)
					{
					case FALSE:
						{
						q = Dst;
						break;
						}

					default:
						{
						*q++ = '/';
						break;
						}
					} /* end of switch ( UsePaths ) */

				break;
				}

			case ':':
				{
				q = Dst;
				break;
				}

			default:
				{
				*q++ = (char) tolower(*p);
				break;
				}
			}		/* end of for ( p = Src ... ) */

		p++;
		}

	*q++ = '\0';
	return (Dst);
	}
#endif

/*--------------------------------------------------------------------------*/
/* Z GET BYTE																*/
/* Get a byte from the modem;												*/
/* return TIMEOUT if no read within timeout tenths, 						*/
/* return RCDO if carrier lost												*/
/*--------------------------------------------------------------------------*/
static int Z_GetByte(int tenths)
	{
	long timeout;

	if ((*statrs)())
		{
		return ((*getrs)());
		}

	timeout = timerset(tenths * 10);

	do
		{
		if ((*statrs)())
			{
			return (*getrs)();
			}

		if (!(*carrstatrs)())
			{
			return (RCDO);
			}

		if (got_ESC())
			{
			return -1;
			}
		} while (!timeup(timeout));

	return (TIMEOUT);
	}

/*--------------------------------------------------------------------------*/
/* Z PUT STRING 															*/
/* Send a string to the modem, processing for \336 (sleep 1 sec)			*/
/* and \335 (break signal, ignored) 										*/
/*--------------------------------------------------------------------------*/
static void Z_PutString(register uchar *s)
	{
	register uint c;

	while (*s)
		{
		switch (c = *s++)
			{
			case (uint) '\336':
				{
				big_pause(2);
				break;
				}

			case (uint) '\335':
				{
				/* Should send a break on this */
				break;
				}

			default:
				{
				(*putrs) ((uchar) c);
				break;
				}
			}
		}
	}

/*--------------------------------------------------------------------------*/
/* Z SEND HEX HEADER														*/
/* Send ZMODEM HEX header hdr of type type									*/
/*--------------------------------------------------------------------------*/
static void Z_SendHexHeader(uint type, register uchar *hdr)
	{
	register int n;
	register int i;
	register word crc;

	show_debug_name(61);

	(*putrs)(ZPAD);
	(*putrs)(ZPAD);
	(*putrs)(ZDLE);
	(*putrs)(ZHEX);

	Z_PUTHEX(i, type);

	Crc32t = 0;
	crc = Z_UpdateCRC(type, 0);

	for (n = 4; --n >= 0;)
		{
		Z_PUTHEX(i, (*hdr));
		crc = Z_UpdateCRC(((uint) (*hdr++)), crc);
		}

	Z_PUTHEX(i, (crc >> 8));
	Z_PUTHEX(i, crc);

	/* Make it printable on remote machine */
	(*putrs)('\r');
	(*putrs)('\n');

	/* Uncork the remote in case a fake XOFF has stopped data flow */
	if (type != ZFIN && type != ZACK)
		{
		(*putrs)(021);
		}
	}

/*--------------------------------------------------------------------------*/
/* Z LONG TO HEADER 														*/
/* Store long integer pos in Txhdr											*/
/*--------------------------------------------------------------------------*/
static void Z_PutLongIntoHeader(long pos)
	{
#ifndef GENERIC
	*((long *) Txhdr) = pos;
#else
	Txhdr[ZP0] = pos;
	Txhdr[ZP1] = pos >> 8;
	Txhdr[ZP2] = pos >> 16;
	Txhdr[ZP3] = pos >> 24;
#endif
	}

/*--------------------------------------------------------------------------*/
/* Z PULL LONG FROM HEADER													*/
/* Recover a long integer from a header 									*/
/*--------------------------------------------------------------------------*/
static long _Z_PullLongFromHeader(uchar *hdr)
	{
#ifndef GENERIC
	return (*((long *) hdr));	/* PLF Fri	05-05-1989	06:42:41 */
#else

	long l;

	l = hdr[ZP3];
	l = (l << 8) | hdr[ZP2];
	l = (l << 8) | hdr[ZP1];
	l = (l << 8) | hdr[ZP0];
	return (l);
#endif
	}

/*--------------------------------------------------------------------------*/
/* Z GET HEADER 															*/
/* Read a ZMODEM header to hdr, either binary or hex.						*/
/*	On success, set Zmodem to 1 and return type of header.					*/
/*	Otherwise return negative on error										*/
/*--------------------------------------------------------------------------*/
static int Z_GetHeader(byte *hdr)
	{
	register int c;
	register int n;
	int cancount;

	show_debug_name(62);

	n = baud_rate;		/* Max characters before start of frame */
	cancount = 5;

Again:
	if (got_ESC())
		{
		send_can();
		z_message(getzmmsg(5));
		return (ZCAN);
		}

	Rxframeind = Rxtype = 0;

	switch (c = _Z_TimedRead())
		{
		case ZPAD:
		case ZPAD | 0200:
			{
			/*--------------------------*/
			/* This is what we want.	*/
			/*--------------------------*/
			break;
			}

		case RCDO:
		case TIMEOUT:
			{
			goto Done;
			}

		case CAN:
GotCan:
			if (--cancount <= 0)
				{
				c = ZCAN;
				goto Done;
				}

			switch (c = Z_GetByte(1))
				{
				case TIMEOUT:
					{
					goto Again;
					}

				case ZCRCW:
					c = ERROR;
					/* fallthrough... */

				case RCDO:
					{
					goto Done;
					}

				case CAN:
					{
					if (--cancount <= 0)
						{
						c = ZCAN;
						goto Done;
						}

					goto Again;
					}
				}

			/* fallthrough... */

		default:
			{
Agn2:
			if (--n <= 0)
				{
				z_message(getzmmsg(6));
				return (ERROR);
				}

			if (c != CAN)
				{
				cancount = 5;
				}

			goto Again;
			}
		}			/* switch */

	cancount = 5;

Splat:
	switch (c = _Z_TimedRead())
		{
		case ZDLE:
			{
			/*--------------------------*/
			/* This is what we want.	*/
			/*--------------------------*/
			break;
			}

		case ZPAD:
			{
			goto Splat;
			}

		case RCDO:
		case TIMEOUT:
			{
			goto Done;
			}

		default:
			{
			goto Agn2;
			}
		}			/* switch */

	switch (c = _Z_TimedRead())
		{
		case ZBIN:
			{
			Rxframeind = ZBIN;
			Crc32 = 0;
			c = _Z_GetBinaryHeader(hdr);
			break;
			}

		case ZBIN32:
			{
			Crc32 = Rxframeind = ZBIN32;
			c = _Z_32GetBinaryHeader(hdr);
			break;
			}

		case ZHEX:
			{
			Rxframeind = ZHEX;
			Crc32 = 0;
			c = _Z_GetHexHeader(hdr);
			break;
			}

		case CAN:
			{
			goto GotCan;
			}

		case RCDO:
		case TIMEOUT:
			{
			goto Done;
			}

		default:
			{
			goto Agn2;
			}
		}			/* switch */

	Rxpos = _Z_PullLongFromHeader(hdr);

Done:
	return (c);
	}				/* Z_GetHeader */

/*--------------------------------------------------------------------------*/
/* Z GET BINARY HEADER														*/
/* Receive a binary style header (type and position)						*/
/*--------------------------------------------------------------------------*/
static int _Z_GetBinaryHeader(register uchar *hdr)
	{
	register int c;
	register uint crc;
	register int n;

	show_debug_name(63);

	if ((c = Z_GetZDL()) & ~0xFF)
		{
		return (c);
		}

	Rxtype = c;
	crc = Z_UpdateCRC(c, 0);

	for (n = 4; --n >= 0;)
		{
		if ((c = Z_GetZDL()) & ~0xFF)
			{
			return (c);
			}

		crc = Z_UpdateCRC(c, crc);
		*hdr++ = (uchar) (c & 0xff);
		}

	if ((c = Z_GetZDL()) & ~0xFF)
		{
		return (c);
		}

	crc = Z_UpdateCRC(c, crc);

	if ((c = Z_GetZDL()) & ~0xFF)
		{
		return (c);
		}

	crc = Z_UpdateCRC(c, crc);

	if (crc & 0xFFFF)
		{
		z_message(getzmmsg(7));
		return (ERROR);
		}

	return (Rxtype);
	}				/* _Z_GetBinaryHeader */


/*--------------------------------------------------------------------------*/
/* Z GET BINARY HEADER with 32 bit CRC										*/
/* Receive a binary style header (type and position)						*/
/*--------------------------------------------------------------------------*/
static int _Z_32GetBinaryHeader(register uchar *hdr)
	{
	register int c;
	register ulong crc;
	register int n;

	show_debug_name(64);

	if ((c = Z_GetZDL()) & ~0xFF)
		{
		return (c);
		}

	Rxtype = c;
	crc = 0xFFFFFFFFl;
	crc = Z_32UpdateCRC(c, crc);

	for (n = 4; --n >= 0;)
		{
		if ((c = Z_GetZDL()) & ~0xFF)
			{
			return (c);
			}

		crc = Z_32UpdateCRC(c, crc);
		*hdr++ = (uchar) (c & 0xff);
		}

	for (n = 4; --n >= 0;)
		{
		if ((c = Z_GetZDL()) & ~0xFF)
			{
			return (c);
			}

		crc = Z_32UpdateCRC(c, crc);
		}

	if (crc != 0xDEBB20E3l)
		{
		z_message(getzmmsg(7));
		return (ERROR);
		}

	return (Rxtype);
	}				/* _Z_32GetBinaryHeader */

/*--------------------------------------------------------------------------*/
/* Z GET HEX																*/
/* Decode two lower case hex digits into an 8 bit byte value				*/
/*--------------------------------------------------------------------------*/
static int _Z_GetHex()
	{
	register int c, n;

	show_debug_name(65);

	if ((n = _Z_TimedRead()) < 0)
		{
		return (n);
		}

	n -= '0';
	if (n > 9)
		{
		n -= ('a' - ':');
		}

	if (n & ~0xF)
		{
		return (ERROR);
		}

	if ((c = _Z_TimedRead()) < 0)
		{
		return (c);
		}

	c -= '0';
	if (c > 9)
		{
		c -= ('a' - ':');
		}

	if (c & ~0xF)
		{
		return (ERROR);
		}

	return ((n << 4) | c);
	}

/*--------------------------------------------------------------------------*/
/* Z GET HEX HEADER 														*/
/* Receive a hex style header (type and position)							*/
/*--------------------------------------------------------------------------*/
static int _Z_GetHexHeader(register uchar *hdr)
	{
	register int c;
	register uint crc;
	register int n;

	show_debug_name(66);

	if ((c = _Z_GetHex()) < 0)
		{
		return (c);
		}

	Rxtype = c;
	crc = Z_UpdateCRC(c, 0);

	for (n = 4; --n >= 0;)
		{
		if ((c = _Z_GetHex()) < 0)
			{
			return (c);
			}

		crc = Z_UpdateCRC(c, crc);
		*hdr++ = (uchar) c;
		}

	if ((c = _Z_GetHex()) < 0)
		{
		return (c);
		}

	crc = Z_UpdateCRC(c, crc);
	if ((c = _Z_GetHex()) < 0)
		{
		return (c);
		}

	crc = Z_UpdateCRC(c, crc);
	if (crc & 0xFFFF)
		{
		z_message(getzmmsg(7));
		return (ERROR);
		}

	if (Z_GetByte(1) == '\r')
		{
		Z_GetByte(1);		/* Throw away possible cr/lf */
		}

	return (Rxtype);
	}


/*--------------------------------------------------------------------------*/
/* Z GET ZDL																*/
/* Read a byte, checking for ZMODEM escape encoding 						*/
/* including CAN*5 which represents a quick abort							*/
/*--------------------------------------------------------------------------*/
static int Z_GetZDL()
	{
	register int c;

	if ((c = Z_GetByte(Rxtimeout)) != ZDLE)
		{
		return (c);
		}

	switch (c = Z_GetByte(Rxtimeout))
		{
		case CAN:
			{
			return ((c = Z_GetByte(Rxtimeout)) < 0) ? c :
					((c == CAN) && ((c = Z_GetByte(Rxtimeout)) < 0)) ? c :
					((c == CAN) && ((c = Z_GetByte(Rxtimeout)) < 0)) ? c : (GOTCAN);
			}

		case ZCRCE:
		case ZCRCG:
		case ZCRCQ:
		case ZCRCW:
			{
			return (c | GOTOR);
			}

		case ZRUB0:
			{
			return (0x7F);
			}

		case ZRUB1:
			{
			return (0xFF);
			}

		default:
			{
			return (c < 0) ? c :
					((c & 0x60) == 0x40) ? (c ^ 0x40) : ERROR;
			}
		}
	}

/*--------------------------------------------------------------------------*/
/* Z TIMED READ 															*/
/* Read a character from the modem line with timeout.						*/
/*	Eat parity, XON and XOFF characters.									*/
/*--------------------------------------------------------------------------*/
static int _Z_TimedRead()
	{
	register int c;

	show_debug_name(67);

	for (;;)
		{
		if ((c = Z_GetByte(Rxtimeout)) < 0)
			{
			return (c);
			}

		switch (c &= 0x7F)
			{
			case XON:
			case XOFF:
				{
				continue;
				}

			default:
				{
				if (!(c & 0x60))
					{
					continue;
					}
				}

			case '\r':
			case '\n':
			case ZDLE:
				{
				return (c);
				}
			}
		}
	}

/*--------------------------------------------------------------------------*/
/* SEND ZMODEM (send a file)												*/
/*	returns TRUE (1) for good xfer, FALSE (0) for bad						*/
/*	sends one file per call; 'fsent' flags start and end of batch           */
/*--------------------------------------------------------------------------*/
static int Send_Zmodem(char *fname, int fsent)
	{
	register byte *p;
	register byte *q;
	struct stat f;
	int i;
	int rc = TRUE;
	char j[100];

	show_debug_name(68);

	z_size = 0;
	Infile = NULL;

	switch (fsent)
		{
		case 0:
			Z_PutString((byte *) getzmmsg(0));
			Z_PutLongIntoHeader(0L);
			Z_SendHexHeader(ZRQINIT, (byte *) Txhdr);
			/* Fall through */

		case NOTHING_TO_DO:
			{
			Rxtimeout = 200;
			if (ZS_GetReceiverInfo() == ERROR)
				{
				return (FALSE);
				}

			break;
			}
		}

	Rxtimeout = (int) (614400L / (long) baud_rate);

	if (Rxtimeout < 100)
		{
		Rxtimeout = 100;
		}

	if (fname == NULL)
		{
		goto Done;
		}

	/*------------------------------------------------------------------*/
	/* Prepare the file for transmission.  Just ignore file open errors */
	/* because there may be other files that can be sent.				*/
	/*------------------------------------------------------------------*/
	Filename = fname;
	errno = 0;

	if ((Infile = _fsopen(Filename, FO_RB, SH_DENYWR /* DENY_WRITE */ )) == NULL)
		{
		got_error(getzmmsg(8), Filename);
		rc = OK;
		goto Done;
		}

	if (isatty(fileno(Infile)))
		{
		errno = 1;
		got_error(getzmmsg(9), Filename);
		rc = OK;
		goto Done;
		}

	/*------------------------------------------------------------------*/
	/* Send the file													*/
	/* Display outbound filename, size, and ETA for sysop				*/
	/*------------------------------------------------------------------*/
	stat(Filename, &f);

	i = (int) (f.st_size * 10 / baud_rate + 53) / 54;
	sprintf(j, getzmmsg(10), Filename, f.st_size, i);
	file_length = f.st_size;

	z_message(pcts, j);

	/*------------------------------------------------------------------*/
	/* Get outgoing file name; no directory path, lower case			*/
	/*------------------------------------------------------------------*/

#ifndef NEW_PATH_STUFF
	for (p = (byte *) Filename, q = Txbuf; *p;)
		{
		if ((*p == '/') || (*p == '\\') || (*p == ':'))
			{
			q = Txbuf;
			}
		else
			{
			*q++ = (char) tolower(*p);
			}

		p++;
		}

	*q++ = '\0';
	p = q;
#else
	p = ZMdmFlNmCndtn(Txbuf, Filename, NULL, 0);
	p += strlen(p);
	q = ++p;
#endif

	/*------------------------------------------------------------------*/
	/* Zero out remainder of file header packet 						*/
	/*------------------------------------------------------------------*/
	while (q < (Txbuf + KSIZE))
		{
		*q++ = '\0';
		}

	/*--------------------------------------------------------------------*/
	/* Store filesize, time last modified, and file mode in header packet */
	/*--------------------------------------------------------------------*/
	sprintf((char *) p, getzmmsg(11), f.st_size, f.st_mtime, f.st_mode);

	/*------------------------------------------------------------------*/
	/* Transmit the filename block and { the download					*/
	/*------------------------------------------------------------------*/
	throughput(0, 0L);

	/*------------------------------------------------------------------*/
	/* Check the results												*/
	/*------------------------------------------------------------------*/
	switch (ZS_SendFile(1 + strlen((char *) p) + (int) (p - Txbuf)))
		{
		case ERROR:
			/*------------------------------*/
			/* Something tragic happened	*/
			/*------------------------------*/
			goto Err_Out;

		case OK:
			/*------------------*/
			/* File was sent	*/
			/*------------------*/
			errno = 0;
			fclose(Infile);
			Infile = NULL;

			z_message(getzmmsg(12), getzmmsg(13), Crc32t ? getzmmsg(14) : ns, Filename);
			doccr();

			goto Done;

		case ZSKIP:
			z_message(getzmmsg(15), Filename);
			rc = SPEC_COND; /* Success but don't truncate! */
			goto Done;

		default:
			/*--------------------------------------------------*/
			/* Ignore the problem, get next file, trust other	*/
			/* error handling mechanisms to deal with problems	*/
			/*--------------------------------------------------*/
			goto Done;
		}			/* switch */

Err_Out:
	rc = FALSE;

Done:
	if (Infile)
		{
		fclose(Infile);
		}

	if (fsent < 0)
		{
		ZS_EndSend();
		}

	return (rc);
	}				/* send_Zmodem */

/*--------------------------------------------------------------------------*/
/* ZS SEND BINARY HEADER													*/
/* Send ZMODEM binary header hdr of type type								*/
/*--------------------------------------------------------------------------*/
static void ZS_SendBinaryHeader(uint type, register byte * hdr)
	{
	register uint crc;
	int n;

	show_debug_name(69);

	(*putrs)(ZPAD);
	(*putrs)(ZDLE);

	if ((Crc32t = Txfcs32) != 0)
		{
		ZS_32SendBinaryHeader(type, hdr);
		}
	else
		{
		(*putrs)(ZBIN);
		ZS_SendByte((byte) type);

		crc = Z_UpdateCRC(type, 0);

		for (n = 4; --n >= 0;)
			{
			ZS_SendByte(*hdr);
			crc = Z_UpdateCRC(((uint) (*hdr++)), crc);
			}

		ZS_SendByte((byte) (crc >> 8));
		ZS_SendByte((byte) crc);
		}
	}				/* ZS_SendBinaryHeader */

/*--------------------------------------------------------------------------*/
/* ZS SEND BINARY HEADER													*/
/* Send ZMODEM binary header hdr of type type								*/
/*--------------------------------------------------------------------------*/
static void ZS_32SendBinaryHeader(uint type, register byte * hdr)
	{
	ulong crc;
	int n;

	show_debug_name(70);

	(*putrs)(ZBIN32);
	ZS_SendByte((byte) type);

	crc = 0xFFFFFFFFl;
	crc = Z_32UpdateCRC(type, crc);

	for (n = 4; --n >= 0;)
		{
		ZS_SendByte(*hdr);
		crc = Z_32UpdateCRC(((uint) (*hdr++)), crc);
		}

	crc = ~crc;
	for (n = 4; --n >= 0;)
		{
		ZS_SendByte((byte) crc);
		crc >>= 8;
		}
	}				/* ZS_SendBinaryHeader */

/*--------------------------------------------------------------------------*/
/* ZS SEND DATA 															*/
/* Send binary array buf with ending ZDLE sequence frameend 				*/
/*--------------------------------------------------------------------------*/
static void ZS_SendData(register byte * buf, int length, uint frameend)
	{
	register uint crc;

	show_debug_name(71);

	if (Crc32t)
		{
		ZS_32SendData(buf, length, frameend);
		}
	else
		{
		crc = 0;
		for (; --length >= 0;)
			{
			ZS_SendByte(*buf);
			crc = Z_UpdateCRC(((uint) (*buf++)), crc);
			}

		(*putrs)(ZDLE);
		(*putrs)((uchar) frameend);

		crc = Z_UpdateCRC(frameend, crc);
		ZS_SendByte((byte) (crc >> 8));
		ZS_SendByte((byte) crc);
		}

	if (frameend == ZCRCW)
		{
		(*putrs)(XON);
		}
	}				/* ZS_SendData */

/*--------------------------------------------------------------------------*/
/* ZS SEND DATA with 32 bit CRC 											*/
/* Send binary array buf with ending ZDLE sequence frameend 				*/
/*--------------------------------------------------------------------------*/
static void ZS_32SendData(register byte * buf, int length, uint frameend)
	{
	ulong crc;

	show_debug_name(72);

	crc = 0xFFFFFFFFl;
	for (; --length >= 0; ++buf)
		{
		ZS_SendByte(*buf);
		crc = Z_32UpdateCRC(((uint) (*buf)), crc);
		}

	(*putrs)(ZDLE);
	(*putrs)((uchar) frameend);
	crc = Z_32UpdateCRC(frameend, crc);

	crc = ~crc;

	for (length = 4; --length >= 0;)
		{
		ZS_SendByte((byte) crc);
		crc >>= 8;
		}
	}				/* ZS_SendData */

/*--------------------------------------------------------------------------*/
/* ZS SEND BYTE 															*/
/* Send character c with ZMODEM escape sequence encoding.					*/
/* Escape XON, XOFF. Escape CR following @ (Telenet net escape) 			*/
/*--------------------------------------------------------------------------*/
static void ZS_SendByte(register byte c)
	{
	static byte lastsent;

	switch (c)
		{
		case 015:
		case 0215:
			{
			if ((lastsent & 0x7F) != '@')
				{
				goto SendIt;
				}
			}

		case 020:
		case 021:
		case 023:
		case 0220:
		case 0221:
		case 0223:
		case ZDLE:
			/*--------------------------------------------------*/
			/* Quoted characters								*/
			/*--------------------------------------------------*/
			(*putrs)(ZDLE);
			c ^= 0x40;

		default:
			{
			/*--------------------------------------------------*/
			/* Normal character output							*/
			/*--------------------------------------------------*/
SendIt:
			(*putrs) (lastsent = c);
			}
		}			/* switch */
	}				/* ZS_SendByte */

/*--------------------------------------------------------------------------*/
/* ZS GET RECEIVER INFO 													*/
/* Get the receiver's init parameters                                       */
/*--------------------------------------------------------------------------*/
static int ZS_GetReceiverInfo()
	{
	int n;

	show_debug_name(73);

	for (n = 10; --n >= 0;)
		{
		switch (Z_GetHeader((byte *) Rxhdr))
			{
			case ZCHALLENGE:
				{
				/*----------------------------------*/
				/* Echo receiver's challenge number */
				/*----------------------------------*/
				Z_PutLongIntoHeader(Rxpos);
				Z_SendHexHeader(ZACK, (byte *) Txhdr);
				continue;
				}

			case ZCOMMAND:
				{
				/*------------------------------*/
				/* They didn't see our ZRQINIT  */
				/*------------------------------*/
				Z_PutLongIntoHeader(0L);
				Z_SendHexHeader(ZRQINIT, (byte *) Txhdr);
				continue;
				}

			case ZRINIT:
				{
				/*------------------------------*/
				/*								*/
				/*------------------------------*/
				Rxflags = 0377 & Rxhdr[ZF0];
				Rxbuflen = ((word) Rxhdr[ZP1] << 8) | Rxhdr[ZP0];
				Txfcs32 = Rxflags & CANFC32;
				return (OK);
				}

			case ZCAN:
			case RCDO:
			case TIMEOUT:
				{
				return (ERROR);
				}

			case ZRQINIT:
				{
				if (Rxhdr[ZF0] == ZCOMMAND)
					{
					continue;
					}
				}

			default:
				{
				Z_SendHexHeader(ZNAK, (byte *) Txhdr);
				continue;
				}
			}		/* switch */
		}			/* for */

	return (ERROR);
	}				/* ZS_GetReceiverInfo */

/*--------------------------------------------------------------------------*/
/* ZS SEND FILE 															*/
/* Send ZFILE frame and begin sending ZDATA frame							*/
/*--------------------------------------------------------------------------*/
static int ZS_SendFile(int blen)
	{
	register int c;

	show_debug_name(74);

	for (;;)
		{
		if (got_ESC())
			{
			send_can(); /* transmit at least 10 cans */
			z_message(getzmmsg(5));
			return (ERROR);
			}
		else if (!(*carrstatrs)())
			{
			return (ERROR);
			}

		Txhdr[ZF0] = LZCONV;/* Default file conversion mode */
		Txhdr[ZF1] = LZMANAG;	/* Default file management mode */
		Txhdr[ZF2] = LZTRANS;	/* Default file transport mode */
		Txhdr[ZF3] = 0;
		ZS_SendBinaryHeader(ZFILE, (byte *) Txhdr);
		ZS_SendData(Txbuf, blen, ZCRCW);

Again:
		switch (c = Z_GetHeader((byte *) Rxhdr))
			{
			case ZRINIT:
				while ((c = Z_GetByte(50)) > 0)
					{
					if (c == ZPAD)
						{
						goto Again;
						}
					}
				/* Fall thru to */

			default:
				continue;

			case ZCAN:
			case RCDO:
			case TIMEOUT:
			case ZFIN:
			case ZABORT:
				{
				return (ERROR);
				}

			case ZSKIP:
				{
				/*--------------------------------------*/
				/* Other system wants to skip this file */
				/*--------------------------------------*/
				return (c);
				}

			case ZRPOS:
				{
				/*------------------------------*/
				/* Resend from this position... */
				/*------------------------------*/
				fseek(Infile, Rxpos, SEEK_SET);

				if (Rxpos != 0L)
					{
					z_message(getzmmsg(16), Rxpos);
					(*putrs)(XON);	/* Send XON to remote */
					}

				LastZRpos = Strtpos = Txpos = Rxpos;
				ZRPosCount = 10;
				(*flushrs)();
				return (ZS_SendFileData());
				}
			}
		}
	}

/*--------------------------------------------------------------------------*/
/* ZS SEND FILE DATA														*/
/* Send the data in the file												*/
/*--------------------------------------------------------------------------*/
static int ZS_SendFileData(void)
	{
	register int c, e;
	int i;
	uint rate;
	word newcnt;
	word blklen;
	word maxblklen;
	word goodblks = 0;
	word goodneeded = 1;

	show_debug_name(75);

	rate = baud_rate;

	maxblklen = (rate < 300) ? 128 : rate / 300 * 256;

	if (maxblklen > BUFSIZE)
		{
		maxblklen = BUFSIZE;
		}

	if (maxblklen > KSIZE)
		{
		maxblklen = KSIZE;
		}

	if (Rxbuflen && maxblklen > (unsigned) Rxbuflen)
		{
		maxblklen = Rxbuflen;
		}

	blklen = (fstblklen != 0) ? fstblklen : maxblklen;
	goodneeded = (fstblklen != 0) ? 8 : 1;

SomeMore:
	if ((*statrs)())
		{
WaitAck:
		switch (c = ZS_SyncWithReceiver(1))
			{
			case ZSKIP:
				{
				/*------------------*/
				/* Skip this file	*/
				/*------------------*/
				return (c);
				}

			case ZACK:
				{
				break;
				}

			case ZRPOS:
				{
				/*--------------------------*/
				/* Resume at this position	*/
				/*--------------------------*/
				blklen = ((blklen >> 2) > 64) ? blklen >> 2 : 64;
				goodblks = 0;
				goodneeded = ((goodneeded << 1) > 16) ? 16 : goodneeded << 1;
				break;
				}

			case ZRINIT:
				{
				/*--------------*/
				/* Receive init */
				/*--------------*/
				throughput(1, Txpos - Strtpos);
				return (OK);
				}

			case TIMEOUT:
				{
				/*--------------------------------------*/
				/* Timed out on message from other side */
				/*--------------------------------------*/
				break;
				}

			default:
				{
				z_message(getzmmsg(17));
				fclose(Infile);
				return (ERROR);
				}
			}		/* switch */

		/*
		* Noise probably got us here. Odds of surviving are not good.
		* But we have to get unstuck in any event.
		* 
		*/

		(*putrs)(XON);	/* Send an XON to release other side */

		while ((*statrs)())
			{
			switch ((*getrs)())
				{
				case CAN:
				case RCDO:
				case ZPAD:
					{
					goto WaitAck;
					}
				}	/* switch */
			}		/* while */
		}			/* while */

	newcnt = Rxbuflen;
	Z_PutLongIntoHeader(Txpos);
	ZS_SendBinaryHeader(ZDATA, (byte *) Txhdr);

	do
		{
		if (got_ESC())
			{
			send_can(); /* transmit at least 10 cans */
			z_message(getzmmsg(5));
			goto oops;
			}

		if (!(*carrstatrs)())
			{
			goto oops;
			}

		if ((unsigned) (c = fread(Txbuf, 1, blklen, Infile)) != z_size)
			{
			z_size = c;
			/* z_message("Subpacket length %u", c); */
			}

		if ((unsigned) c < blklen)
			{
			e = ZCRCE;
			}
		else if (Rxbuflen && (newcnt -= c) <= 0)
			{
			e = ZCRCW;
			}
		else
			{
			e = ZCRCG;
			}

		ZS_SendData(Txbuf, c, e);

		i = (int) ((file_length - Txpos) * 10 / rate + 53) / 54;

		z_status(getzmmsg(18), Txpos, i);

		Txpos += c;

		if (blklen < maxblklen && ++goodblks > goodneeded)
			{
			blklen = ((blklen << 1) < maxblklen) ? blklen << 1 : maxblklen;
			goodblks = 0;
			}

		if (e == ZCRCW)
			{
			goto WaitAck;
			}

		while ((*statrs)())
			{
			switch ((*getrs)())
				{
				case CAN:
				case RCDO:
				case ZPAD:
					{
					/*--------------------------------------*/
					/* Interruption detected;				*/
					/* stop sending and process complaint	*/
					/*--------------------------------------*/
					z_message(getzmmsg(19));
					ZS_SendData(Txbuf, 0, ZCRCE);
					goto WaitAck;
					}
				}	/* switch */
			}		/* while */
		} while (e == ZCRCG);

	for (;;)
		{
		Z_PutLongIntoHeader(Txpos);
		ZS_SendBinaryHeader(ZEOF, (byte *) Txhdr);

		switch (ZS_SyncWithReceiver(7))
			{
			case ZACK:
				{
				continue;
				}

			case ZRPOS:
				{
				/*------------------------------*/
				/* Resume at this position...	*/
				/*------------------------------*/
				goto SomeMore;
				}

			case ZRINIT:
				{
				/*--------------*/
				/* Receive init */
				/*--------------*/
				throughput(1, Txpos - Strtpos);
				return (OK);
				}

			case ZSKIP:
				{
				/*----------------------------------*/
				/* Request to skip the current file */
				/*----------------------------------*/
				z_message(getzmmsg(20));
				errno = 0;
				fclose(Infile);
				return (c);
				}

			default:
				{
oops:
				z_message(getzmmsg(17));
				fclose(Infile);
				return (ERROR);
				}
			}		/* switch */
		}			/* while */
	}				/* ZS_SendFileData */

/*--------------------------------------------------------------------------*/
/* ZS SYNC WITH RECEIVER													*/
/* Respond to receiver's complaint, get back in sync with receiver          */
/*--------------------------------------------------------------------------*/
static int ZS_SyncWithReceiver(int num_errs)
	{
	register int c;
	char j[50];

	show_debug_name(76);

	for (;;)
		{
		c = Z_GetHeader((byte *) Rxhdr);
		(*flushrs)();

		switch (c)
			{
			case TIMEOUT:
				z_message(getzmmsg(21));
				if ((num_errs--) >= 0)
					{
					break;
					}

			case ZCAN:
			case ZABORT:
			case ZFIN:
			case RCDO:
				{
				z_message(getzmmsg(22));
				return (ERROR);
				}

			case ZRPOS:
				{
				if (Rxpos == LastZRpos)
					{	/* Same as last time? */
					if (!(--ZRPosCount))	/* Yup, 10 times yet?	*/
						{
						return (ERROR); 	/* Too many, get out	*/
						}
					} 
				else
					{
					ZRPosCount = 10;		/* Reset repeat count	*/
					}

				LastZRpos = Rxpos;			/* Keep track of this	*/

				rewind(Infile); /* In case file EOF seen */
				fseek(Infile, Rxpos, SEEK_SET);
				Txpos = Rxpos;
				sprintf(j, getzmmsg(23),
						ultoa(((ulong) (Txpos)), e_input, 10));
				z_message(j);
				return (c);
				}

			case ZSKIP:
				z_message(getzmmsg(20));

			case ZRINIT:
				{
				errno = 0;
				fclose(Infile);
				return (c);
				}

			case ZACK:
				{
				z_message(NULL);
				return (c);
				}

			default:
				{
				z_message(getzmmsg(46));
				ZS_SendBinaryHeader(ZNAK, (byte *) Txhdr);
				continue;
				}
			}		/* switch */
		}			/* while */
	}				/* ZS_SyncWithReceiver */

/*--------------------------------------------------------------------------*/
/* ZS END SEND																*/
/* Say BIBI to the receiver, try to do it cleanly							*/
/*--------------------------------------------------------------------------*/
static void ZS_EndSend()
	{
	show_debug_name(77);

	for (;;)
		{
		Z_PutLongIntoHeader(0L);
		ZS_SendBinaryHeader(ZFIN, (byte *) Txhdr);

		switch (Z_GetHeader((byte *) Rxhdr))
			{
			case ZFIN:
				(*putrs)('O');
				(*putrs)('O');
				/* fallthrough... */

			case ZCAN:
			case RCDO:
			case TIMEOUT:
				{
				return;
				}
			}
		}
	}

/*--------------------------------------------------------------------------*/
/* GET ZMODEM																*/
/* Receive a batch of files.												*/
/* returns TRUE (1) for good xfer, FALSE (0) for bad						*/
/* can be called from f_upload or to get mail from a WaZOO Opus 			*/
/*--------------------------------------------------------------------------*/
static int get_Zmodem(const char *rcvpath, FILE * xferinfo)
	{
	char namebuf[PATHLEN];
	int i;
	char *p;

	show_debug_name(78);

	filetime = 0;

	/* Secbuf = NULL; */
	Outfile = NULL;
	z_size = 0;

	Rxtimeout = 100;
	Tryzhdrtype = ZRINIT;

	strcpy(namebuf, rcvpath);
	Filename = namebuf;

	strcpy(Upload_path, rcvpath);
	p = Upload_path + strlen(Upload_path) - 1;

	while (p >= Upload_path && *p != '\\')
		{
		--p;
		}

	*(++p) = '\0';

	DiskAvail = zfree(Upload_path);

	if (((i = RZ_InitReceiver()) == ZCOMPL) ||
			((i == ZFILE) && ((RZ_ReceiveBatch(xferinfo)) == OK)))
		{
		return (1);
		}

	send_can(); 		/* transmit at least 10 cans */

	if (Outfile)
		{
		fclose(Outfile);
		}

	return (0);
	}

/*--------------------------------------------------------------------------*/
/* RZ RECEIVE DATA															*/
/* Receive array buf of max length with ending ZDLE sequence				*/
/* and CRC.  Returns the ending character or error code.					*/
/*--------------------------------------------------------------------------*/
static int RZ_ReceiveData(register byte * buf, register int length)
	{
	register int c;
	register word crc;
	char *endpos;
	int d;

	show_debug_name(79);

	if (Rxframeind == ZBIN32)
		{
		return (RZ_32ReceiveData(buf, length));
		}

	crc = RxCount = 0;
	buf[0] = buf[1] = 0;
	endpos = (char *) buf + length;

	while ((char *) buf <= endpos)
		{
		if ((c = Z_GetZDL()) & ~0xFF)
			{
CRCfoo:
			switch (c)
				{
				case GOTCRCE:
				case GOTCRCG:
				case GOTCRCQ:
				case GOTCRCW:
					{
					/*----------*/
					/* C R C s	*/
					/*----------*/
					crc = Z_UpdateCRC(((d = c) & 0xFF), crc);

					if ((c = Z_GetZDL()) & ~0xFF)
						{
						goto CRCfoo;
						}

					crc = Z_UpdateCRC(c, crc);
					if ((c = Z_GetZDL()) & ~0xFF)
						{
						goto CRCfoo;
						}

					crc = Z_UpdateCRC(c, crc);
					if (crc & 0xFFFF)
						{
						z_message(getzmmsg(7));
						return (ERROR);
						}

					RxCount = length - (int) (endpos - (char *) buf);
					return (d);
					}

				case GOTCAN:
					{
					/*----------*/
					/* Cancel	*/
					/*----------*/
					z_message(getzmmsg(17));
					return (ZCAN);
					}

				case TIMEOUT:
					{
					/*----------*/
					/* Timeout	*/
					/*----------*/
					z_message(getzmmsg(21));
					return (c);
					}

				case RCDO:
					{
					/*--------------*/
					/* No carrier	*/
					/*--------------*/
					z_message(getzmmsg(24));
					(*flushrs)();
					return (c);
					}

				default:
					{
					/*----------------------*/
					/* Something bizarre	*/
					/*----------------------*/
					z_message(getzmmsg(25));
					(*flushrs)();
					return (c);
					}
				}
			}

		*buf++ = (uchar) c;
		crc = Z_UpdateCRC(c, crc);
		}

	z_message(getzmmsg(26));
	return (ERROR);
	}

/*--------------------------------------------------------------------------*/
/* RZ RECEIVE DATA with 32 bit CRC											*/
/* Receive array buf of max length with ending ZDLE sequence				*/
/* and CRC.  Returns the ending character or error code.					*/
/*--------------------------------------------------------------------------*/
static int RZ_32ReceiveData(register byte * buf, register int length)
	{
	register int c;
	ulong crc;
	char *endpos;
	int d;

	show_debug_name(80);

	crc = 0xFFFFFFFFl;
	RxCount = 0;
	buf[0] = buf[1] = 0;
	endpos = (char *) buf + length;

	while ((char *) buf <= endpos)
		{
		if ((c = Z_GetZDL()) & ~0xFF)
			{
CRCfoo:
			switch (c)
				{
				case GOTCRCE:
				case GOTCRCG:
				case GOTCRCQ:
				case GOTCRCW:
					{
					/*----------*/
					/* C R C s	*/
					/*----------*/
					d = c;
					c &= 0377;

					crc = Z_32UpdateCRC(c, crc);
					if ((c = Z_GetZDL()) & ~0xFF)
						{
						goto CRCfoo;
						}

					crc = Z_32UpdateCRC(c, crc);
					if ((c = Z_GetZDL()) & ~0xFF)
						{
						goto CRCfoo;
						}

					crc = Z_32UpdateCRC(c, crc);
					if ((c = Z_GetZDL()) & ~0xFF)
						{
						goto CRCfoo;
						}

					crc = Z_32UpdateCRC(c, crc);
					if ((c = Z_GetZDL()) & ~0xFF)
						{
						goto CRCfoo;
						}

					crc = Z_32UpdateCRC(c, crc);
					if (crc != 0xDEBB20E3l)
						{
						z_message(getzmmsg(7));
						return (ERROR);
						}

					RxCount = length - (int) (endpos - (char *) buf);
					return (d);
					}

				case GOTCAN:
					{
					/*----------*/
					/* Cancel	*/
					/*----------*/
					z_message(getzmmsg(17));
					return (ZCAN);
					}

				case TIMEOUT:
					{
					/*----------*/
					/* Timeout	*/
					/*----------*/
					z_message(getzmmsg(21));
					return (c);
					}

				case RCDO:
					{
					/*--------------*/
					/* No carrier	*/
					/*--------------*/
					z_message(getzmmsg(24));
					(*flushrs)();
					return (c);
					}

				default:
					{
					/*----------------------*/
					/* Something bizarre	*/
					/*----------------------*/
					z_message(getzmmsg(25));
					(*flushrs)();
					return (c);
					}
				}
			}
		*buf++ = (uchar) c;
		crc = Z_32UpdateCRC(c, crc);
		}

	z_message(getzmmsg(26));
	return (ERROR);
	}

/*--------------------------------------------------------------------------*/
/* RZ INIT RECEIVER 														*/
/* Initialize for Zmodem receive attempt, try to activate Zmodem sender 	*/
/* Handles ZSINIT, ZFREECNT, and ZCOMMAND frames							*/
/*																			*/
/* Return codes:															*/
/*	ZFILE .... Zmodem filename received 									*/
/*	ZCOMPL ... transaction finished 										*/
/*	ERROR .... any other condition											*/
/*--------------------------------------------------------------------------*/
static int RZ_InitReceiver()
	{
	register int n;
	int errors = 0;
	const char *sptr;


	show_debug_name(81);

	for (n = 12; --n >= 0;)
		{
		/*--------------------------------------------------------------*/
		/* Set buffer length (0=unlimited, don't wait).                 */
		/* Also set capability flags									*/
		/*--------------------------------------------------------------*/

		Z_PutLongIntoHeader(0L);
		Txhdr[ZF0] = CANFC32 | CANFDX | CANOVIO;
		Z_SendHexHeader(Tryzhdrtype, (byte *) Txhdr);

		if (Tryzhdrtype == ZSKIP)
			{
			Tryzhdrtype = ZRINIT;
			}

AGAIN:

		switch (Z_GetHeader((byte *) Rxhdr))
			{
			case ZFILE:
				{
				Zconv = Rxhdr[ZF0];
				Tryzhdrtype = ZRINIT;
				if (RZ_ReceiveData(Txbuf, BUFSIZE) == GOTCRCW)
					{
					return (ZFILE);
					}

				Z_SendHexHeader(ZNAK, (byte *) Txhdr);
				if (--n < 0)
					{
					sptr = getzmmsg(27);
					goto Err;
					}

				goto AGAIN;
				}

			case ZSINIT:
				{
				if (RZ_ReceiveData((byte *) Attn, ZATTNLEN) == GOTCRCW)
					{
					Z_PutLongIntoHeader(1L);
					Z_SendHexHeader(ZACK, (byte *) Txhdr);
					}
				else
					{
					Z_SendHexHeader(ZNAK, (byte *) Txhdr);
					}

				if (--n < 0)
					{
					sptr = getzmmsg(28);
					goto Err;
					}

				goto AGAIN;
				}

			case ZFREECNT:
				{
				Z_PutLongIntoHeader(DiskAvail);
				Z_SendHexHeader(ZACK, (byte *) Txhdr);
				goto AGAIN;
				}

			case ZCOMMAND:
				{
				/*------------------------------------------*/
				/* Paranoia is good for you...				*/
				/* Ignore command from remote, but lie and	*/
				/* say we did the command ok.				*/
				/*------------------------------------------*/
				if (RZ_ReceiveData(Txbuf, BUFSIZE) == GOTCRCW)
					{
					z_message(getzmmsg(29), Txbuf);
					Z_PutLongIntoHeader(0L);

					do
						{
						Z_SendHexHeader(ZCOMPL, (byte *) Txhdr);
						} while (++errors < 10 && Z_GetHeader((byte *) Rxhdr) != ZFIN);

					RZ_AckBibi();
					return (ZCOMPL);
					}
				else
					{
					Z_SendHexHeader(ZNAK, (byte *) Txhdr);
					}

				if (--n < 0)
					{
					sptr = getzmmsg(30);
					goto Err;
					}

				goto AGAIN;
				}

			case ZCOMPL:
				{
				if (--n < 0)
					{
					sptr = getzmmsg(31);
					goto Err;
					}

				goto AGAIN;
				}

			case ZFIN:
				{
				RZ_AckBibi();
				return (ZCOMPL);
				}

			case ZCAN:
				{
				sptr = getzmmsg(17);
				goto Err;
				}

			case RCDO:
				{
				sptr = getzmmsg(24);
				(*flushrs)();
				goto Err;
				}
			}
		}

	sptr = getzmmsg(21);

Err:
	sprintf(e_input, getzmmsg(32), sptr);
	z_message(e_input);

	return (ERROR);
	}

/*--------------------------------------------------------------------------*/
/* RZFILES																	*/
/* Receive a batch of files using ZMODEM protocol							*/
/*--------------------------------------------------------------------------*/
static int RZ_ReceiveBatch(FILE * xferinfo)
	{
	register int c;

	show_debug_name(82);

	for (;;)
		{
		switch (c = RZ_ReceiveFile(xferinfo))
			{
			case ZEOF:
				/* fallthrough */

			case ZSKIP:
				{
				switch (RZ_InitReceiver())
					{
					case ZCOMPL:
						{
						return (OK);
						}

					default:
						{
						return (ERROR);
						}

					case ZFILE:
						{
						break;
						}
					}	/* switch */
				break;
				}

			default:
				{
				fclose(Outfile);
				Outfile = NULL;
				/* else */ unlink(Filename);
				return (c);
				}
			}
		}
	}

/*--------------------------------------------------------------------------*/
/* RZ RECEIVE FILE															*/
/*	Receive one file; assumes file name frame is preloaded in Txbuf 		*/
/*--------------------------------------------------------------------------*/
static int RZ_ReceiveFile(FILE * xferinfo)
	{
	register int c;
	int n;
	long rxbytes;
	const char *sptr;
	struct utimbuf utimes;
	char j[50];


	show_debug_name(83);

	EOFseen = FALSE;
	c = RZ_GetHeader();

	if (c == ERROR || c == ZSKIP)
		{
		return (Tryzhdrtype = ZSKIP);
		}

	n = 10;
	rxbytes = Filestart;

	for (;;)
		{
		Z_PutLongIntoHeader(rxbytes);
		Z_SendHexHeader(ZRPOS, (byte *) Txhdr);

NxtHdr:
		switch (c = Z_GetHeader((byte *) Rxhdr))
			{
			case ZDATA:
				{
				/*--------------*/
				/* Data Packet	*/
				/*--------------*/

				if (Rxpos != rxbytes)
					{
					if (--n < 0)
						{
						sptr = getzmmsg(6);
						goto Err;
						}

					sprintf(j, getzmmsg(33), getzmmsg(34), rxbytes, Rxpos);
					z_message(j);
					Z_PutString((byte *) Attn);
					continue;
					}

MoreData:
				switch (c = RZ_ReceiveData(Txbuf, BUFSIZE))
					{
					case ZCAN:
						{
						sptr = getzmmsg(17);
						goto Err;
						}

					case RCDO:
						{
						sptr = getzmmsg(24);
						(*flushrs)();
						goto Err;
						}

					case ERROR:
						{
						/*--------------*/
						/* CRC error	*/
						/*--------------*/
						if (--n < 0)
							{
							sptr = getzmmsg(6);
							goto Err;
							}

						show_loc(rxbytes, n);
						Z_PutString((byte *) Attn);
						continue;
						}

					case TIMEOUT:
						{
						if (--n < 0)
							{
							sptr = getzmmsg(21);
							goto Err;
							}

						show_loc(rxbytes, n);
						continue;
						}

					case GOTCRCW:
						{
						/*--------------*/
						/* End of frame */
						/*--------------*/

						n = 10;
						if (RZ_SaveToDisk(&rxbytes) == ERROR)
							{
							return (ERROR);
							}

						Z_PutLongIntoHeader(rxbytes);
						Z_SendHexHeader(ZACK, (byte *) Txhdr);
						goto NxtHdr;
						}

					case GOTCRCQ:
						{
						/*------------------*/
						/* Zack expected	*/
						/*------------------*/

						n = 10;
						if (RZ_SaveToDisk(&rxbytes) == ERROR)
							{
							return (ERROR);
							}

						Z_PutLongIntoHeader(rxbytes);
						Z_SendHexHeader(ZACK, (byte *) Txhdr);
						goto MoreData;
						}

					case GOTCRCG:
						{
						/*----------*/
						/* Non-stop */
						/*----------*/

						n = 10;
						if (RZ_SaveToDisk(&rxbytes) == ERROR)
							{
							return (ERROR);
							}

						goto MoreData;
						}

					case GOTCRCE:
						{
						/*------------------*/
						/* Header to follow */
						/*------------------*/

						n = 10;
						if (RZ_SaveToDisk(&rxbytes) == ERROR)
							{
							return (ERROR);
							}

						goto NxtHdr;
						}
					}	/* switch */
				}

			case ZNAK:
			case TIMEOUT:
				{
				/*------------------------------*/
				/* Packet was probably garbled	*/
				/*------------------------------*/
				if (--n < 0)
					{
					sptr = getzmmsg(35);
					goto Err;
					}

				show_loc(rxbytes, n);
				continue;
				}

			case ZFILE:
				{
				/*----------------------------------*/
				/* Sender didn't see our ZRPOS yet  */
				/*----------------------------------*/
				RZ_ReceiveData(Txbuf, BUFSIZE);
				continue;
				}

			case ZEOF:
				{
				/*------------------------------------------*/
				/* End of the file							*/
				/* Ignore EOF if it's at wrong place; force */
				/* a timeout because the eof might have 	*/
				/* gone out before we sent our ZRPOS		*/
				/*------------------------------------------*/
				if (Rxpos != rxbytes)
					{
					goto NxtHdr;
					}

				throughput(2, rxbytes - Filestart);

				fclose(Outfile);

				z_message(getzmmsg(12), getzmmsg(36), Crc32 ? getzmmsg(14) : ns, realname);
				doccr();

				if (filetime)
					{
					utimes.UT_ACTIME = filetime;
					utimes.modtime = filetime;
					b_utime(Filename, (UTIMBUF *) & utimes);
					}

				Outfile = NULL;
				if (xferinfo != NULL)
					{
					fprintf(xferinfo, getmsg(1294), Filename, bn);
					}
				return (c);
				}

			case ERROR:
				{
				/*-----------------------------------------*/
				/* Too much garbage in header search error */
				/*-----------------------------------------*/
				if (--n < 0)
					{
					sptr = getzmmsg(35);
					goto Err;
					}

				show_loc(rxbytes, n);
				Z_PutString((byte *) Attn);
				continue;
				}

			case ZSKIP:
				{
				return (c);
				}

			default:
				{
				sptr = getzmmsg(46);
				(*flushrs)();
				goto Err;
				}
			}		/* switch */
		}			/* while */

Err:
	sprintf(e_input, getzmmsg(37), sptr);
	z_message(e_input);
	return (ERROR);
	}

/*--------------------------------------------------------------------------*/
/* RZ GET HEADER															*/
/* Process incoming file information header 								*/
/*--------------------------------------------------------------------------*/
static int RZ_GetHeader()
	{
	register byte *p;
	struct stat f;
	char *ourname;
	char *theirname;
	long filesize;
	char *fileinfo;
	char j[80];

	show_debug_name(84);

	/*--------------------------*/
	/* Setup the transfer mode	*/
	/*--------------------------*/
	isBinary = (char) ((!RXBINARY && Zconv == ZCNL) ? 0 : 1);

	/*------------------------------------------*/
	/* Extract and verify filesize, if given.	*/
	/* Reject file if not at least 10K free 	*/
	/*------------------------------------------*/
	filesize = 0L;
	filetime = 0L;
	fileinfo = (char *) Txbuf + 1 + strlen((char *) Txbuf);

	if (*fileinfo)
		{
		sscanf(fileinfo, getzmmsg(38), &filesize, &filetime);
		}

	if (filesize + 10240 > DiskAvail)
		{
		z_message(getzmmsg(39));
		return (ERROR);
		}

	/*----------------------------------------------*/
	/* Get and/or fix filename for uploaded file	*/
	/*----------------------------------------------*/
	p = (byte *) (Filename + strlen(Filename) - 1); /* Find end of upload
							 * path */
	while ((char *) p >= Filename && *p != '\\')
		{
		p--;
		}

	ourname = (char *) ++p;

	p = Txbuf + strlen((char *) Txbuf) - 1; /* Find transmitted simple
						 * filename */
	while (p >= Txbuf && *p != '\\' && *p != '/' && *p != ':')
		{
		p--;
		}
	theirname = (char *) ++p;

	strcpy(ourname, theirname); /* Start w/ our path & their name */
	strcpy(realname, Filename);

	/*------------------------------------------------------*/
	/* Open either the old or a new file, as appropriate	*/
	/*------------------------------------------------------*/

	strcpy(ourname, theirname);
	/*--------------------------------------------------------------*/
	/* If the file already exists:									*/
	/* 1) And the new file has the same time and size, return ZSKIP */
	/* 2) And OVERWRITE is turned on, delete the old copy			*/
	/* 3) Else create a unique file name in which to store new data */
	/*--------------------------------------------------------------*/
	if (filexists(Filename)) /* If file already exists... */
		{			
		if ((Outfile = fopen(Filename, FO_RB)) == NULL)
			{
			got_error(getzmmsg(8), Filename);
			return (ERROR);
			}

		fstat(fileno(Outfile), &f);
		fclose(Outfile);
		if (filesize == f.st_size && filetime == f.st_mtime)
			{
			z_message(getzmmsg(40), Filename);
			return (ZSKIP);
			}

		if ((!overwrite) /* || (is_arcmail (Filename, i)) */ )
			{
			unique_name(Filename);
			}
		else
			{
			unlink(Filename);
			}
		}			/* if exist */

	if (strcmp(ourname, theirname))
		{
		z_message(getzmmsg(41), ourname);
		}

	p = (byte *) FO_WB;
	if ((Outfile = fopen(Filename, (char *) p)) == NULL)
		{
		got_error(getzmmsg(8), Filename);
		return (ERROR);
		}

	if (isatty(fileno(Outfile)))
		{
		errno = 1;
		got_error(getzmmsg(9), Filename);
		fclose(Outfile);
		return (ERROR);
		}

	Filestart = /* (Resume_WaZOO) ? filelength (fileno
			(Outfile)) : */ 0L;

	fseek(Outfile, Filestart, SEEK_SET);

	p = NULL;

	sprintf(j, getzmmsg(42), 
			(p != NULL) ? (char *) p : getzmmsg(43),
			realname, (isBinary) ? ns : getzmmsg(44), filesize,
			(int) ((filesize - Filestart) * 10 / baud_rate + 53) / 54);

	file_length = filesize;

	z_message(pcts, j);

	throughput(0, 0L);

	return (OK);
	}

/*--------------------------------------------------------------------------*/
/* RZ SAVE TO DISK															*/
/* Writes the received file data to the output file.						*/
/* If in ASCII mode, stops writing at first ^Z, and converts all			*/
/*	solo CR's or LF's to CR/LF pairs.                                       */
/*--------------------------------------------------------------------------*/
static int RZ_SaveToDisk(long *rxbytes)
	{
	static byte lastsent;

	register byte *p;
	register uint count;
	int i;

	show_debug_name(85);

	count = RxCount;

	if (got_ESC())
		{
		send_can(); 	/* Cancel file */
		while ((i = Z_GetByte(20)) != TIMEOUT && i != RCDO) /* Wait for line to
								 * clear */
			{
			(*flushrs)();
			}

		send_can(); 	/* and Cancel Batch */
		z_message(getzmmsg(5));
		return (ERROR);
		}

	if (count != z_size)
		{
		z_size = count;
		/* z_message("Subpacket length %u", count); */
		}

	if (isBinary)
		{
		if (fwrite(Txbuf, 1, count, Outfile) != count)
			{
			goto oops;
			}
		}
	else
		{
		if (EOFseen)
			{
			return (OK);
			}

		for (p = Txbuf; count > 0; count--)
			{
			if (*p == CPMEOF)
				{
				EOFseen = TRUE;
				return (OK);
				}

			if (*p == '\n')
				{
				if (lastsent != '\r' && putc('\r', Outfile) == EOF)
					{
					goto oops;
					}
				}
			else
				{
				if (lastsent == '\r' && putc('\n', Outfile) == EOF)
					{
					goto oops;
					}
				}

			if (putc((lastsent = *p++), Outfile) == EOF)
				{
				goto oops;
				}
			}
		}

	*rxbytes += RxCount;
	i = (int) ((file_length - *rxbytes) * 10 / baud_rate + 53) / 54;

	z_status(getzmmsg(18), *rxbytes, i);

	return (OK);

oops:
	got_error(getzmmsg(45), Filename);
	return (ERROR);
	}

/*--------------------------------------------------------------------------*/
/* RZ ACK BIBI																*/
/* Ack a ZFIN packet, let byegones be byegones								*/
/*--------------------------------------------------------------------------*/
static void RZ_AckBibi()
	{
	register int n;

	show_debug_name(86);

	Z_PutLongIntoHeader(0L);

	for (n = 4; --n;)
		{
		Z_SendHexHeader(ZFIN, (byte *) Txhdr);
		switch (Z_GetByte(100))
			{
			case 'O':
				Z_GetByte(1);	/* Discard 2nd 'O' */

			case TIMEOUT:
			case RCDO:
				{
				return;
				}
			}
		}
	}
