#define	__TOWNS__
#define VERSION "3.04 02-20-91"
#define PUBDIR "/usr/spool/uucppublic"

/*
 * rz.c By Chuck Forsberg
 *
 * A program for Unix to receive files and commands from computers running
 * Professional-YAM, PowerCom, YAM, IMP, or programs supporting XMODEM. rz
 * uses Unix buffered input to reduce wasted CPU time.
 *
 *
 * This version implements numerous enhancements including ZMODEM Run Length
 * Encoding and variable length headers.  These features were not funded by
 * the original Telenet development contract.
 *
 * This software may be freely used for non commercial and educational (didactic
 * only) purposes.	This software may also be freely used to support file
 * transfer operations to or from licensed Omen Technology products.  Any
 * programs which use part or all of this software must be provided in source
 * form with this notice intact except by written permission from Omen
 * Technology Incorporated.
 *
 * Use of this software for commercial or administrative purposes except when
 * exclusively limited to interfacing Omen Technology products requires a per
 * port license payment of $20.00 US per port (less in quantity).  Use of
 * this code by inclusion, decompilation, reverse engineering or any other
 * means constitutes agreement to these conditions and acceptance of
 * liability to license the materials and payment of reasonable legal costs
 * necessary to enforce this license agreement.
 *
 *
 * Omen Technology Inc			FAX: 503-621-3745 Post Office Box 4681
 * Portland OR 97208
 *
 * This code is made available in the hope it will be useful, BUT WITHOUT ANY
 * WARRANTY OF ANY KIND OR LIABILITY FOR ANY DAMAGES OF ANY KIND.
 *
 *
 *
 * Iff the program is invoked by rzCOMMAND, output is piped to "COMMAND
 * filename"  (Unix only)
 *
 * Some systems (Venix, Coherent, Regulus) may not support tty raw mode read(2)
 * the same way as Unix. ONEREAD must be defined to force one character reads
 * for these systems. Added 7-01-84 CAF
 *
 * Alarm signal handling changed to work with 4.2 BSD 7-15-84 CAF
 *
 * NFGVMIN Updated 2-18-87 CAF for Xenix systems where c_cc[VMIN] doesn't work
 * properly (even though it compiles without error!),
 *
 * SEGMENTS=n added 2-21-88 as a model for CP/M programs for CP/M-80 systems
 * that cannot overlap modem and disk I/O.
 *
 * -DMD may be added to compiler command line to compile in Directory-creating
 * routines from Public Domain TAR by John Gilmore
 *
 * HOWMANY may be tuned for best performance
 *
 * USG UNIX (3.0) ioctl conventions courtesy  Jeff Martin */


#define LOGFILE "/tmp/rzlog"
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <errno.h>
extern int	errno;

#ifdef	__TOWNS__
#	include	<string.h>
#	include	<stdarg.h>
#	include	<stdlib.h>
#	include	<direct.h>
#	include	<stat.h>
#	include	<sys/types.h>
#	include	<sys/utime.h>
#	include	<io.h>
#	include	<time.h>

#	include	<splib.h>
#	include	<fslib.h>

#	include	"usrlib.h"
#	include	"rsctrl.h"
#	include	"flib.h"
#	include	"msgdat.h"

#	ifdef	__HIGHC__
#		pragma	On(Align_labels);
#	endif

	extern	int	RsPort;
#	define	_FSTAT_IGN
#	define	MD
#endif
#include	"prot.h"
#include	"rz.h"

int 		Zmodem = 0; 		/* ZMODEM protocol requested */
int 		Nozmodem = 0;		/* If invoked as "rb" */
unsigned	Baudrate = 2400;
unsigned	Effbaud = 2400;

#include "rbsb.c"				/* most of the system dependent stuff here */
#include "crctab.c"

FILE	   *fout = NULL;

/*
 * Routine to calculate the free bytes on the current file system ~0 means
 * many free bytes (unknown)
 */
static	long		getfree(void)
{
#ifdef	__TOWNS__
	int		drv;
	int		tf, wf;

	if ( (drv = FS_getdrv()) >= 0 )
	{
		if ( FS_get_dskFree( drv, &tf, &wf) == NORMAL )
			return (long)tf;
	}
#endif
	return (~0L);				/* many free bytes ... */
}

int 		Lastrx;
long		rxbytes;
int 		Crcflg;
int 		Firstsec;
int 		Eofseen;			/* indicates cpm eof (^Z) has been received */
int 		errors;
int 		Restricted = 0; 	/* restricted; no /.. or ../ in filenames */
#ifdef ONEREAD
/* Sorry, Regulus and some others don't work right in raw mode! */
int 		Readnum = 1;		/* Number of bytes to ask for in read() from
								 * modem */
#else
int 		Readnum = HOWMANY;	/* Number of bytes to ask for in read() from
								 * modem */
#endif

#define DEFBYTL 2000000000L 	/* default rx file size */
long		Bytesleft;			/* number of bytes of incoming file left */
long		Modtime;			/* Unix style mod time for incoming file */
int 		Filemode;			/* Unix style mode for incoming file */
long		Totalleft;
long		Filesleft;
char		Pathname[PATHLEN];
char	   *Progname;			/* the name by which we were called */

int 		Batch = 0;
int 		Topipe = 0;
int 		Verbose = 0;
int 		Thisbinary; 		/* current file is to be received in bin mode */
int 		Blklen; 			/* record length of received packets */

#ifdef SEGMENTS
int 		chinseg = 0;		/* Number of characters received in this data seg */
char		secbuf[1 + (SEGMENTS + 1) * 1024];
#else
char		secbuf[1025];
#endif


char		linbuf[HOWMANY];
int 		Lleft = 0;			/* number of characters in linbuf */
time_t		timep[2];
char		zconv;				/* ZMODEM file conversion request */
char		zmanag; 			/* ZMODEM file management request */
char		ztrans; 			/* ZMODEM file transport request */
int 		Zctlesc;			/* Encode control characters */
int 		Zrwindow = 1400;	/* RX window size (controls garbage count) */

jmp_buf 	tohere; 			/* For the interrupt on RX timeout */

#define xsendline(c) sendline(c)

#include "zm.c"

#include "zmr.c"

int 		tryzhdrtype = ZRINIT;		/* Header type to send corresponding
										 * to Last rx close */

#ifdef	__TOWNS__
void	signal_func(int level)
{
}

/* called by signal interrupt or terminate to clean things up */
void	bibi(int n)
{
	if (Zmodem)
		zmputs(Attn);
	canit();
	mode(0);
	USR_fprintf(stderr, "rz: caught signal %d; exiting", n);
	cucheck();
	EXIT(128 + n);
}
#else
void	alrm(int level)
{
	longjmp(tohere, -1);
}

/* called by signal interrupt or terminate to clean things up */
bibi(int n)
{
	if (Zmodem)
		zmputs(Attn);
	canit();
	mode(0);
	fprintf(stderr, "rz: caught signal %d; exiting", n);
	cucheck();
	exit(128 + n);
}
#endif

void	zm_main(int argc, char *argv[])
{
	register char *cp;
	register	npats;
	char	   *virgin, **patts;
	int 		exitcode = 0;

	Rxtimeout = 100;
//	setbuf(stderr, NULL);
#if	0
	if ((cp = getenv("SHELL")) && (substr(cp, "rsh") || substr(cp, "rksh")))
		Restricted = TRUE;	/* 	*/
#endif

	from_cu();
	chkinvok(virgin = argv[0]); /* if called as [-]rzCOMMAND set flag */
	npats = 0;
	while (--argc)
	{
		cp = *++argv;
		if (*cp == '-')
		{
			while (*++cp)
			{
				switch (*cp)
				{
					case '\\':
						cp[1] = toupper(cp[1]);
						continue;
					case 'c':
						Crcflg = TRUE;
						break;
					case 'e':
						Zctlesc = 1;
						break;
					case 't':
						if (--argc < 1)
						{
							usage();
						}
						Rxtimeout = atoi(*++argv);
						if (Rxtimeout < 10 || Rxtimeout > 1000)
							usage();
						break;
					case 'w':
						if (--argc < 1)
						{
							usage();
						}
						Zrwindow = atoi(*++argv);
						break;
					case 'v':
						++Verbose;
						break;
					default:
						usage();
				}
			}
		} else if (!npats && argc > 0)
		{
			if (argv[0][0])
			{
				npats = argc;
				patts = argv;
			}
		}
	}
	if (npats > 1)
		usage();
	if (Batch && npats)
		usage();
#ifndef	__TOWNS__
	if (Verbose)
	{
		if ( freopen(LOGFILE, "a", stderr) == NULL )
		{
			printf("Can't open log file %s\n", LOGFILE);
			exit(0200);
		}
//		setbuf(stderr, NULL);
		USR_fprintf(stderr, "argv[0]=%s Progname=%s\n", virgin, Progname);
	}
#endif
	if (Fromcu)
	{
		if (Verbose == 0)
			Verbose = 2;
	}
#ifdef	__TOWNS__
	USR_fprintf(stderr,"\n");
	USR_fprintf(stderr,"rz ver.%s for TownsOS", MAIN_VER );
	USR_fprintf(stderr,"    orignal rz %s\n\n", VERSION);
#else
	vfile("%s %s for %s\n", Progname, VERSION, OS);
#endif
	mode(1);	/* save old tty stat, set raw mode */
#ifndef	__TOWNS__
	if (signal(SIGINT, bibi) == SIG_IGN)
	{
		signal(SIGINT, SIG_IGN);
		signal(SIGKILL, SIG_IGN);
	} else
	{
		signal(SIGINT, bibi);
		signal(SIGKILL, bibi);
	}
	signal(SIGTERM, bibi);
#endif
	if ( wcreceive(npats, patts) == ERROR )
	{
		exitcode = 0200;
		canit();
	}
	mode(0);
	if (exitcode && !Zmodem)	/* bellow again with all thy might. */
		canit();
	if (exitcode)
		cucheck();
	EXIT(exitcode ? exitcode : 0);
}

#include	"rzhelp.c"

/*
 * Debugging information output interface routine
 */
/* VARARGS1 */
static	void	vfile( CONST char *form, ... )
{
	va_list		arg;
	char		tmp[BUFSIZ];

	if ( Verbose > 2 )
	{
		va_start( arg, form );
		vsprintf( tmp, form, arg );
		va_end(arg);
		USR_fputs(tmp, stderr );
		USR_fputs("\n",stderr);
	}
}

/*
 * Let's receive something already.
 */

#ifndef	__TOWNS__
static	char	*rbmsg =
"%s ready. To begin transfer, type \"%s file ...\" to your modem program\r\n\n";
#endif

static	int		wcreceive(int argc, char **argp)
{
	register	c;

	if (Batch || argc == 0)
	{
		Crcflg = 1;
#ifdef	__TOWNS__
		USR_fprintf(stderr,
		    "rx ready. To begin transfer, type \"%s file ...\" to your modem program\r\n",
		    Nozmodem ? "sb" : "sz" );
#else
		USR_fprintf(stderr, rbmsg, Progname, Nozmodem ? "sb" : "sz");
#endif
		if ( (c = tryz()) != 0 )
		{
			if (c == ZCOMPL)
				return OK;
			if (c == ERROR)
				goto fubar;
			c = rzfiles();
			if (c)
				goto fubar;
		} else
		{
			for (;;)
			{
				if (wcrxpn(secbuf) == ERROR)
					goto fubar;
				if (secbuf[0] == 0)
					return OK;
				if (procheader(secbuf) == ERROR)
					goto fubar;
				if (wcrx() == ERROR)
					goto fubar;
			}
		}
	} else
	{
		Bytesleft = DEFBYTL;
		Filemode = 0;
		Modtime = 0L;

		procheader("");
		strcpy(Pathname, *argp);
		checkpath(Pathname);
		USR_fprintf(stderr, "\nrz: ready to receive %s\r\n", Pathname);
		if ((fout = FM_fopen(Pathname, "wb")) == NULL)
			return ERROR;
		if (wcrx() == ERROR)
			goto fubar;
	}
	return OK;
  fubar:
	canit();
#ifndef	__TOWNS__
	if (Topipe && fout)
	{
		pclose(fout);
		return ERROR;
	}
#endif
	Modtime = 1;
	if (fout)
		FM_fclose(fout);
	if (Restricted)
	{
		unlink(Pathname);
		USR_fprintf(stderr, "\r\nrz: %s removed.\r\n", Pathname);
	}
	return ERROR;
}


/*
 * Fetch a pathname from the other end as a C ctyle ASCIZ string. Length is
 * indeterminate as long as less than Blklen A null string represents no more
 * files (YMODEM)
 */
static	int		wcrxpn(char *rpn)
/* receive a pathname */
{
	register	c;

#ifdef NFGVMIN
	readline(1);
#else
	purgeline();
#endif

  et_tu:
	Firstsec = TRUE;
	Eofseen = FALSE;
	sendline(Crcflg ? WANTCRC : NAK);
	Lleft = 0;					/* Do read next time ... */
	while ((c = wcgetsec(rpn, 100)) != 0)
	{
		if (c == WCEOT)
		{
			zperr("Pathname fetch returned %d", c);
			sendline(ACK);
			Lleft = 0;			/* Do read next time ... */
			readline(1);
			goto et_tu;
		}
		return ERROR;
	}
	sendline(ACK);
	return OK;
}

/*
 * Adapted from CMODEM13.C, written by Jack M. Wierda and Roderick W. Hart
 */

static	int		wcrx(void)
{
	register int sectnum, sectcurr;
	register char sendchar;
	register char *p;
	int 		cblklen;		/* bytes to dump this block */

	Firstsec = TRUE;
	sectnum = 0;
	Eofseen = FALSE;
	sendchar = Crcflg ? WANTCRC : NAK;

	for (;;)
	{
		sendline(sendchar); 	/* send it now, we're ready! */
		Lleft = 0;				/* Do read next time ... */
		sectcurr = wcgetsec(secbuf, (sectnum & 0177) ? 50 : 130);
		report(sectcurr);
		if (sectcurr == (sectnum + 1 & 0377))
		{
			sectnum++;
			cblklen = Bytesleft > Blklen ? Blklen : Bytesleft;
			if (putsec(secbuf, cblklen) == ERROR)
				return ERROR;
			if ((Bytesleft -= cblklen) < 0)
				Bytesleft = 0;
			sendchar = ACK;
		} else if (sectcurr == (sectnum & 0377))
		{
			zperr("Received dup Sector");
			sendchar = ACK;
		} else if (sectcurr == WCEOT)
		{
			if (closeit())
				return ERROR;
			sendline(ACK);
			Lleft = 0;			/* Do read next time ... */
			return OK;
		} else if (sectcurr == ERROR)
			return ERROR;
		else
		{
			zperr("Sync Error");
			return ERROR;
		}
	}
}

/*
 * Wcgetsec fetches a Ward Christensen type sector. Returns sector number
 * encountered or ERROR if valid sector not received, or CAN CAN received or
 * WCEOT if eot sector time is timeout for first char, set to 4 seconds
 * thereafter **************** NO ACK IS SENT IF SECTOR IS RECEIVED OK **************
 * (Caller must do that when he is good and ready to get next sector)
 */

static	int		wcgetsec(char *rxbuf, int maxtime)
{
	register	checksum, wcj, firstch;
	register unsigned short oldcrc;
	register char *p;
	int 		sectcurr;

	for (Lastrx = errors = 0; errors < RETRYMAX; errors++)
	{

		if ((firstch = readline(maxtime)) == STX)
		{
			Blklen = 1024;
			goto get2;
		}
		if (firstch == SOH)
		{
			Blklen = 128;
		  get2:
			sectcurr = readline(1);
			if ((sectcurr + (oldcrc = readline(1))) == 0377)
			{
				oldcrc = checksum = 0;
				for (p = rxbuf, wcj = Blklen; --wcj >= 0;)
				{
					if ((firstch = readline(1)) < 0)
						goto bilge;
					oldcrc = updcrc(firstch, oldcrc);
					checksum += (*p++ = firstch);
				}
				if ((firstch = readline(1)) < 0)
					goto bilge;
				if (Crcflg)
				{
					oldcrc = updcrc(firstch, oldcrc);
					if ((firstch = readline(1)) < 0)
						goto bilge;
					oldcrc = updcrc(firstch, oldcrc);
					if (oldcrc & 0xFFFF)
						zperr("CRC");
					else
					{
						Firstsec = FALSE;
						return sectcurr;
					}
				} else if (((checksum - firstch) & 0377) == 0)
				{
					Firstsec = FALSE;
					return sectcurr;
				} else
					zperr("Checksum");
			} else
				zperr("Sector number garbled");
		}
		/* make sure eot really is eot and not just mixmash */
#ifdef NFGVMIN
		else if (firstch == EOT && readline(1) == TIMEOUT)
			return WCEOT;
#else
		else if (firstch == EOT && Lleft == 0)
			return WCEOT;
#endif
		else if (firstch == CAN)
		{
			if (Lastrx == CAN)
			{
				zperr("Sender CANcelled");
				return ERROR;
			} else
			{
				Lastrx = CAN;
				continue;
			}
		} else if (firstch == TIMEOUT)
		{
			if (Firstsec)
				goto humbug;
		  bilge:
			zperr("TIMEOUT");
		} else
			zperr("Got 0%o sector header", firstch);

	  humbug:
		Lastrx = 0;
		while (readline(1) != TIMEOUT)
			;
		if (Firstsec)
		{
			sendline(Crcflg ? WANTCRC : NAK);
			Lleft = 0;			/* Do read next time ... */
		} else
		{
			maxtime = 40;
			sendline(NAK);
			Lleft = 0;			/* Do read next time ... */
		}
	}
	/* try to stop the bubble machine. */
	canit();
	return ERROR;
}

/*
 * This version of readline is reasoably well suited for reading many
 * characters. (except, currently, for the Regulus version!)
 *
 * timeout is in tenths of seconds
 */
static	int		readline(int timeout)
{
	register	n;
	static char *cdq;			/* pointer for removing chars from linbuf */

	if ( --Lleft >= 0 )
	{
		if (Verbose > 8)
		{
			USR_fprintf(stderr, "%02x ", *cdq & 0377);
		}
		return (*cdq++ & 0377);
	}

#ifdef	__TOWNS__
	n = timeout;
	if (n < 200 )
		n = 200;
#else
	n = timeout / 10;
	if (n < 2)
		n = 3;
#endif
	if (Verbose > 5)
		USR_fprintf(stderr, "Calling read: alarm=%d  Readnum=%d ",
				n, Readnum);
#ifdef	__TOWNS__
	{
		clock_t	clk;
		clk = H_CLOCK2(0) + n * CLOCKS_PER_SEC / 100;
		int		c;

		do
		{
			if (  (c = RS_chk(RsPort)) > 0 )
			{
				int		i;
				i = 0;
				while ( i < Readnum )
				{
					linbuf[i++] = RS_getc(RsPort);
					if ( RS_chk(RsPort) < 1 )
						break;
				}
				cdq = linbuf;
				Lleft = i;
				break;
			}
		} while ( clk > H_CLOCK2(clk) );

		if ( c < 1 )
		{	/* timeout */
			Lleft = 0;
			if (Verbose > 1)
				USR_fprintf(stderr, "Readline:TIMEOUT\n");
			return TIMEOUT;
		}
	}
#else
	if (setjmp(tohere))
	{
#ifdef TIOCFLUSH
		/* ioctl(0, TIOCFLUSH, 0); */
#endif
		Lleft = 0;
		if (Verbose > 1)
			fprintf(stderr, "Readline:TIMEOUT\n");
		return TIMEOUT;
	}
	signal(SIGALRM, alrm);
	alarm(n);
	Lleft = read(0, cdq = linbuf, Readnum);
	alarm(0);
#endif
	if (Verbose > 5)
	{
		USR_fprintf(stderr, "Read returned %d bytes\n", Lleft);
	}
	if (Lleft < 1)
		return TIMEOUT;
	--Lleft;
	if (Verbose > 8)
	{
		USR_fprintf(stderr, "%02X ", *cdq & 0377);
	}
	return (*cdq++ & 0377);
}



/*
 * Purge the modem input queue of all characters
 */
static	void	purgeline(void)
{
	Lleft = 0;
#ifndef	__TOWNS__
#	ifdef USG
		ioctl(0, TCFLSH, 0);
#	else
		lseek(0, 0L, 2);
#	endif
#endif
}


/*
 * Process incoming file information header
 */
static	int		procheader(char *name)
{
	register char *openmode, *p, **pp;
	static		dummy;
	struct stat f;

	/* set default parameters and overrides */
	openmode = "wb";

	/*
	 * Process ZMODEM remote file management requests
	 */
	Thisbinary = (zconv != ZCNL);		/* Remote ASCII override */
	if (zmanag == ZMAPND)
		openmode = "ab";

	Bytesleft = DEFBYTL;
	Filemode = 0;
	Modtime = 0L;

	p = name + 1 + strlen(name);
	if (*p)
	{							/* file coming from Unix or DOS system */
		sscanf(p, "%ld%lo%o%lo%d%ld%d%d",
			   &Bytesleft, &Modtime, &Filemode,
			   &dummy, &Filesleft, &Totalleft, &dummy, &dummy);
		if (Filemode & UNIXFILE)
			++Thisbinary;
		if (Verbose)
		{
			USR_fprintf(stderr, "\nIncoming: %s %ld %lo %o\n",
					name, Bytesleft, Modtime, Filemode);
			USR_fprintf(stderr, "YMODEM header: %s\n", p);
		}
	} else
	{
#ifdef	__TOWNS__
#else
		/* File coming from CP/M system */
		for (p = name; *p; ++p) /* change / to _ */
			if (*p == '/')
				*p = '_';

		if (*--p == '.')		/* zap trailing period */
			*p = 0;
#endif
	}

	strcpy(Pathname, name);
	checkpath(name);

	if (*name && stat(name, &f) != -1)
	{
		zmanag &= ZMMASK;
		vfile("Current %s is %ld %lo", name, f.st_size, f.st_mtime);
		if (Thisbinary && zconv == ZCRESUM)
		{
			rxbytes = f.st_size & ~511;
			openit(name, "r+b");
			if (!fout)
				return ZFERR;
			if (Bytesleft < rxbytes)
				rxbytes = 0;
			if (fseek(fout, rxbytes, 0))
			{
				closeit();
				return ZFERR;
			}
			vfile("Crash recovery at %ld", rxbytes);
			return 0;
		} else if ((zmanag == ZMNEW) ||
				   ((zmanag == ZMNEWL) && Bytesleft <= f.st_size))
		{
			if ((f.st_mtime + 1) >= Modtime)
				goto skipfile;
			goto doopen;
		} else if (zmanag == ZMPROT)
			goto skipfile;
		else if ((zmanag & ZMMASK) != ZMCLOB)
			goto skipfile;
		goto doopen;
	} else if (zmanag & ZMSKNOLOC)
	{
	  skipfile:
		vfile("Skipping %s", name);
		return ZSKIP;
	}
  doopen:
	openit(name, openmode);
#ifdef MD
	if (!fout)
		if (make_dirs(name))
			openit(name, openmode);
#endif
	if (!fout)
		return ZFERR;
	return 0;
}

static	void	openit(char *name, char *openmode)
{
#ifdef	DEBUG
	USR_fprintf(stderr,"\nfopen(\x22%s\x22,\x22%s\x22)\n", name, openmode);
#endif
	fout = FM_fopen(name, openmode);
}

#ifdef MD
/*
 * Directory-creating routines from Public Domain TAR by John Gilmore
 */

/*
 * After a file/link/symlink/dir creation has failed, see if it's because
 * some required directory was not present, and if so, create all required
 * dirs.
 */
static	int		make_dirs(register char *pathname)
{
	register char *p;			/* Points into path */
	int 		madeone = 0;	/* Did we do anything yet? */
	int 		save_errno = errno; 	/* Remember caller's errno */

	if (errno != ENOENT)
		return 0;				/* Not our problem */

	for (p = strchr(pathname, '/'); p != NULL; p = strchr(p + 1, '/'))
	{
		/* Avoid mkdir of empty string, if leading or double '/' */
		if (p == pathname || p[-1] == '/')
			continue;
		/* Avoid mkdir where last part of path is '.' */
		if (p[-1] == '.' && (p == pathname + 1 || p[-2] == '/'))
			continue;
		*p = 0; 				/* Truncate the path there */
#ifdef	__TOWNS__
		if (!_mkdir(pathname))
#else
		if (!mkdir(pathname, 0777))
#endif
		{						/* Try to create it as a dir */
			vfile("Made directory %s\n", pathname);
			madeone++;			/* Remember if we made one */
			*p = '/';
			continue;
		}
		*p = '/';
		if (errno == EEXIST)	/* Directory already exists */
			continue;
		/*
		 * Some other error in the mkdir.  We return to the caller.
		 */
		break;
	}
	errno = save_errno; 		/* Restore caller's errno */
	return madeone; 			/* Tell them to retry if we made one */
}

#ifndef	__TOWNS__
#if (MD != 2)
#define TERM_SIGNAL(status) 	((status) & 0x7F)
#define TERM_COREDUMP(status)	(((status) & 0x80) != 0)
#define TERM_VALUE(status)		((status) >> 8)
/*
 * Make a directory.  Compatible with the mkdir() system call on 4.2BSD.
 */
static	int		mkdir(char *dpath, int dmode)
{
	int 		cpid, status;
	struct stat statbuf;

	if (stat(dpath, &statbuf) == 0)
	{
		errno = EEXIST; 		/* Stat worked, so it already exists */
		return -1;
	}
	/* If stat fails for a reason other than non-existence, return error */
	if (errno != ENOENT)
		return -1;

	switch (cpid = fork())
	{

		case -1:				/* Error in fork() */
			return (-1);		/* Errno is set already */

		case 0: 		/* Child process */
			/*
			 * Cheap hack to set mode of new directory.  Since this child
			 * process is going away anyway, we zap its umask. FIXME, this
			 * won't suffice to set SUID, SGID, etc. on this directory.  Does
			 * anybody care?
			 */
			status = umask(0);	/* Get current umask */
			status = umask(status | (0777 & ~dmode));	/* Set for mkdir */
			execl("/bin/mkdir", "mkdir", dpath, (char *) 0);
			_exit(-1);			/* Can't exec /bin/mkdir */

		default:				/* Parent process */
			while (cpid != wait(&status));		/* Wait for kid to finish */
	}

	if (TERM_SIGNAL(status) != 0 || TERM_VALUE(status) != 0)
	{
		errno = EIO;			/* We don't know why, but */
		return -1;				/* /bin/mkdir failed */
	}
	return 0;
}
#endif							/* MD != 2 */
#endif							/* endof ifndef __TOWNS__ */
#endif							/* MD */

/*
 * Putsec writes the n characters of buf to receive file fout. If not in
 * binary mode, carriage returns, and all characters starting with CPMEOF are
 * discarded.
 */
static	int		putsec(char *buf, register n)
{
	register char *p;

	if (n == 0)
		return OK;
	if (Thisbinary)
	{
		for (p = buf; --n >= 0;)
		{
			putc(*p, fout);
			++p;
		}
	} else
	{
		if (Eofseen)
			return OK;
		for (p = buf; --n >= 0; ++p)
		{
			if (*p == '\r')
				continue;
			if (*p == CPMEOF)
			{
				Eofseen = TRUE;
				return OK;
			}
			putc(*p, fout);
		}
	}
	return OK;
}

/*
 * Send a character to modem.  Small is beautiful.
 */
static	void	sendline(int c)
{
	c &= 0xFF;
	if (Verbose > 6)
		USR_fprintf(stderr, "Sendline: %x\n", c);
#ifdef	__TOWNS__
	RS_putc( RsPort, c );
#else
	char		d;
	d = c;
	write(1, &d, 1);
#endif
}

static	void	flushmo(void)
{
}
static	void	flushmoc(void)
{
}

/*
 * substr(string, token) searches for token in string s returns pointer to
 * token within string if found, NULL otherwise
 */
static	char	   *substr(register char *s, register char *t)
{
	register char *ss, *tt;
	/* search for first char of token */
	for (ss = s; *s; s++)
		if (*s == *t)
			/* compare token with substring */
			for (ss = s, tt = t;;)
			{
				if (*tt == 0)
					return s;
				if (*ss++ != *tt++)
					break;
			}
	return NULL;
}

/*
 * Log an error
 */
/* VARARGS1 */
static	void	zperr(char *form, ... )
{
	va_list		arg;
	char		tmp[BUFSIZ];

	if (Verbose <= 0)
		return;
	USR_fprintf(stderr, "Retry %d: ", errors);
	va_start( arg, form );
	vsprintf( tmp, form, arg );
	va_end(arg);
	USR_fputs(tmp, stderr );
	USR_fputs("\n",stderr);
}

/* send cancel string to get the other end to shut up */
static	void	canit(void)
{
	static char canistr[] =
	{
	 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0
	};

	RS_puts(RsPort,canistr);
	Lleft = 0;					/* Do read next time ... */
}


static	void	report(int sct)
{
	if (Verbose > 1)
		USR_fprintf(stderr, "%03d%c", sct, sct % 10 ? ' ' : '\r');
}

/*
 * If called as [-][dir/../]vrzCOMMAND set Verbose to 1 If called as
 * [-][dir/../]rzCOMMAND set the pipe flag If called as rb use YMODEM
 * protocol
 */
static	void	chkinvok(char *s)
{
	register char *p;

	p = s;
	while (*p == '-')
		s = ++p;
	while (*p)
		if (*p++ == '/')
			s = p;
	if (*s == 'v')
	{
		Verbose = 1;
		++s;
	}
	Progname = s;
	if (s[0] == 'r' && s[1] == 'z')
		Batch = TRUE;
	if (s[0] == 'r' && s[1] == 'b')
		Batch = Nozmodem = TRUE;
#ifndef	__TOWNS__
	if (s[2] && s[0] == 'r' && s[1] == 'b')
		Topipe = 1;
	if (s[2] && s[0] == 'r' && s[1] == 'z')
		Topipe = 1;
#endif
}

/*
 * Totalitarian Communist pathname processing
 */
static	void	checkpath(char *name)
{
	if (Restricted)
	{
		FILE	*fp;
		
		if ( (fp = FM_fopen(name, "rb")) != NULL)
		{
			canit();
			USR_fprintf(stderr, "\r\nrz: %s exists\n", name);
			FM_fclose(fp);
			bibi(-1);
		}
		/* restrict pathnames to current tree or uucppublic */
		if (substr(name, "../")
			|| (name[0] == '/' && strncmp(name, PUBDIR, strlen(PUBDIR))))
		{
			canit();
			USR_fprintf(stderr, "\r\nrz:\tSecurity Violation\r\n");
			bibi(-1);
		}
	}
}

/*
 * Initialize for Zmodem receive attempt, try to activate Zmodem sender
 * Handles ZSINIT frame Return ZFILE if Zmodem filename received, -1 on
 * error, ZCOMPL if transaction finished,  else 0
 */
static	int		tryz(void)
{
	register	c, n;
	register	cmdzack1flg;

	if (Nozmodem)				/* Check for "rb" program name */
		return 0;


	for (n = Zmodem ? 15 : 5; --n >= 0;)
	{
		/* Set buffer length (0) and capability flags */
#ifdef SEGMENTS
		stohdr(SEGMENTS * 1024L);
#else
		stohdr(0L);
#endif
#ifdef CANBREAK
		Txhdr[ZF0] = CANFC32 | CANFDX | CANOVIO | CANBRK;
#else
		Txhdr[ZF0] = CANFC32 | CANFDX | CANOVIO;
#endif
		if (Zctlesc)
			Txhdr[ZF0] |= TESCCTL;
		Txhdr[ZF0] |= CANRLE;
		Txhdr[ZF1] = CANVHDR;
		/* tryzhdrtype may == ZRINIT */
		zshhdr(4, tryzhdrtype, Txhdr);
		if (tryzhdrtype == ZSKIP)		/* Don't skip too far */
			tryzhdrtype = ZRINIT;		/* CAF 8-21-87 */
	  again:
		switch (zgethdr(Rxhdr, 0))
		{
			case ZRQINIT:
				if (Rxhdr[ZF3] & 0x80)
					Usevhdrs = 1;		/* we can var header */
				continue;
			case ZEOF:
				continue;
			case TIMEOUT:
				continue;
			case ZFILE:
				zconv = Rxhdr[ZF0];
				zmanag = Rxhdr[ZF1];
				ztrans = Rxhdr[ZF2];
				if (Rxhdr[ZF3] & ZCANVHDR)
					Usevhdrs = TRUE;
				tryzhdrtype = ZRINIT;
				c = zrdata(secbuf, 1024);
				mode(3);
				if (c == GOTCRCW)
					return ZFILE;
				zshhdr(4, ZNAK, Txhdr);
				goto again;
			case ZSINIT:
				Zctlesc = TESCCTL & Rxhdr[ZF0];
				if (zrdata(Attn, ZATTNLEN) == GOTCRCW)
				{
					stohdr(1L);
					zshhdr(4, ZACK, Txhdr);
					goto again;
				}
				zshhdr(4, ZNAK, Txhdr);
				goto again;
			case ZFREECNT:
				stohdr(getfree());
				zshhdr(4, ZACK, Txhdr);
				goto again;
			case ZCOMMAND:
				cmdzack1flg = Rxhdr[ZF0];
				if (zrdata(secbuf, 1024) == GOTCRCW)
				{
					if (cmdzack1flg & ZCACK1)
						stohdr(0L);
					else
						stohdr((long) sys2(secbuf));
					purgeline();/* dump impatient questions */
					do
					{
						zshhdr(4, ZCOMPL, Txhdr);
					}
					while (++errors < 20 && zgethdr(Rxhdr, 1) != ZFIN);
					ackbibi();
					if (cmdzack1flg & ZCACK1)
						exec2(secbuf);
					return ZCOMPL;
				}
				zshhdr(4, ZNAK, Txhdr);
				goto again;
			case ZCOMPL:
				goto again;
			default:
				continue;
			case ZFIN:
				ackbibi();
				return ZCOMPL;
			case ZCAN:
				return ERROR;
		}
	}
	return 0;
}

/*
 * Receive 1 or more files with ZMODEM protocol
 */
static	int		rzfiles(void)
{
	register	c;

	for (;;)
	{
		switch (c = rzfile())
		{
			case ZEOF:
			case ZSKIP:
				switch (tryz())
				{
					case ZCOMPL:
						return OK;
					default:
						return ERROR;
					case ZFILE:
						break;
				}
				continue;
			default:
				return c;
			case ERROR:
				return ERROR;
		}
	}
}

/*
 * Receive a file with ZMODEM protocol Assumes file name frame is in secbuf
 */
static	int		rzfile(void)
{
	register	c, n;
	long		rxbytes;

	Eofseen = FALSE;
	if (procheader(secbuf) == ERROR)
	{
		return (tryzhdrtype = ZSKIP);
	}
	n = 20;
	rxbytes = 0l;

	for (;;)
	{
#ifdef SEGMENTS
		chinseg = 0;
#endif
		stohdr(rxbytes);
		zshhdr(4, ZRPOS, Txhdr);
	  nxthdr:
		switch (c = zgethdr(Rxhdr, 0))
		{
			default:
				vfile("rzfile: zgethdr returned %d", c);
				return ERROR;
			case ZNAK:
			case TIMEOUT:
#ifdef SEGMENTS
				putsec(secbuf, chinseg);
				chinseg = 0;
#endif
				if (--n < 0)
				{
					vfile("rzfile: zgethdr returned %d", c);
					return ERROR;
				}
			case ZFILE:
				zrdata(secbuf, 1024);
				continue;
			case ZEOF:
#ifdef SEGMENTS
				putsec(secbuf, chinseg);
				chinseg = 0;
#endif
				if (rclhdr(Rxhdr) != rxbytes)
				{
					/*
					 * Ignore eof if it's at wrong place - force a timeout
					 * because the eof might have gone out before we sent our
					 * zrpos.
					 */
					errors = 0;
					goto nxthdr;
				}
				if (closeit())
				{
					tryzhdrtype = ZFERR;
					vfile("rzfile: closeit returned <> 0");
					return ERROR;
				}
				vfile("rzfile: normal EOF");
				return c;
			case ERROR: /* Too much garbage in header search error */
#ifdef SEGMENTS
				putsec(secbuf, chinseg);
				chinseg = 0;
#endif
				if (--n < 0)
				{
					vfile("rzfile: zgethdr returned %d", c );
					return ERROR;
				}
				zmputs(Attn);
				continue;
			case ZSKIP:
#ifdef SEGMENTS
				putsec(secbuf, chinseg);
				chinseg = 0;
#endif
				Modtime = 1;
				closeit();
				vfile("rzfile: Sender SKIPPED file");
				return c;
			case ZDATA:
				if (rclhdr(Rxhdr) != rxbytes)
				{
					if (--n < 0)
					{
						return ERROR;
					}
#ifdef SEGMENTS
					putsec(secbuf, chinseg);
					chinseg = 0;
#endif
					zmputs(Attn);
					continue;
				}
			  moredata:
				if (Verbose > 1)
					USR_fprintf(stderr, "\r%7ld ZMODEM%s    ",
							rxbytes, Crc32r ? " CRC-32" : "");
#ifdef SEGMENTS
				if (chinseg >= (1024 * SEGMENTS))
				{
					putsec(secbuf, chinseg);
					chinseg = 0;
				}
				switch (c = zrdata(secbuf + chinseg, 1024))
#else
				switch (c = zrdata(secbuf, 1024))
#endif
				{
					case ZCAN:
#ifdef SEGMENTS
						putsec(secbuf, chinseg);
						chinseg = 0;
#endif
						vfile("rzfile: zgethdr returned %d", c);
						return ERROR;
					case ERROR:/* CRC error */
#ifdef SEGMENTS
						putsec(secbuf, chinseg);
						chinseg = 0;
#endif
						if (--n < 0)
						{
							vfile("rzfile: zgethdr returned %d", c);
							return ERROR;
						}
						zmputs(Attn);
						continue;
					case TIMEOUT:
#ifdef SEGMENTS
						putsec(secbuf, chinseg);
						chinseg = 0;
#endif
						if (--n < 0)
						{
							vfile("rzfile: zgethdr returned %d", c);
							return ERROR;
						}
						continue;
					case GOTCRCW:
						n = 20;
#ifdef SEGMENTS
						chinseg += Rxcount;
						putsec(secbuf, chinseg);
						chinseg = 0;
#else
						putsec(secbuf, Rxcount);
#endif
						rxbytes += Rxcount;
						stohdr(rxbytes);
						zshhdr(4, ZACK, Txhdr);
						sendline(XON);
						goto nxthdr;
					case GOTCRCQ:
						n = 20;
#ifdef SEGMENTS
						chinseg += Rxcount;
#else
						putsec(secbuf, Rxcount);
#endif
						rxbytes += Rxcount;
						stohdr(rxbytes);
						zshhdr(4, ZACK, Txhdr);
						goto moredata;
					case GOTCRCG:
						n = 20;
#ifdef SEGMENTS
						chinseg += Rxcount;
#else
						putsec(secbuf, Rxcount);
#endif
						rxbytes += Rxcount;
						goto moredata;
					case GOTCRCE:
						n = 20;
#ifdef SEGMENTS
						chinseg += Rxcount;
#else
						putsec(secbuf, Rxcount);
#endif
						rxbytes += Rxcount;
						goto nxthdr;
				}
		}
	}
}

/*
 * Send a string to the modem, processing for \336 (sleep 1 sec) and \335
 * (break signal)
 */
static	void	zmputs(char *s)
{
	register	c;

	while (*s)
	{
		switch (c = *s++)
		{
			case '\336':
#ifdef	__TOWNS__
				{
					clock_t	clk;
					clk = H_CLOCK2(0) + 1 * CLOCKS_PER_SEC;
					while ( clk > H_CLOCK2(clk) )
						;
				}
#else
				sleep(1);
#endif
				continue;
			case '\335':
				sendbrk();
				continue;
			default:
				sendline(c);
		}
	}
}

/*
 * Close the receive dataset, return OK or ERROR
 */
static	int		closeit(void)
{
#ifndef	__TOWNS__
	if (Topipe)
	{
		if (pclose(fout))
		{
			return ERROR;
		}
		return OK;
	}
#endif
	if (FM_fclose(fout) == ERROR)
	{
		USR_fprintf(stderr, "file close ERROR\n");
		return ERROR;
	}
	if (Modtime)
	{
#ifdef	__TOWNS__
		struct utimbuf	timbuf;
		timbuf.actime  = time(NULL);
		timbuf.modtime = Modtime;
		_utime(Pathname, &timbuf);
#else
		timep[0] = time(NULL);
		timep[1] = Modtime;
		utime(Pathname, timep);
#endif
	}
	if ((Filemode & S_IFMT) == S_IFREG)
#ifdef	__TOWNS__
		_chmod(Pathname, (07777 & Filemode));
#else
		chmod(Pathname, (07777 & Filemode));
#endif
	return OK;
}

/*
 * Ack a ZFIN packet, let byegones be byegones
 */
static	void	ackbibi(void)
{
	register	n;

	vfile("ackbibi:");
	Readnum = 1;
	stohdr(0L);
	for (n = 3; --n >= 0;)
	{
		purgeline();
		zshhdr(4, ZFIN, Txhdr);
		switch (readline(100))
		{
			case 'O':
				readline(1);	/* Discard 2nd 'O' */
				vfile("ackbibi complete");
				return;
			case RCDO:
				return;
			case TIMEOUT:
			default:
				break;
		}
	}
}



/*
 * Local console output simulation
 */
static	void	bttyout(int c)
{
	if (Verbose || Fromcu)
		USR_fputc(c, stderr);
}

/*
 * Strip leading ! if present, do shell escape.
 */
static	int		sys2(register char *s)
{
	if (*s == '!')
		++s;
#ifndef	__TOWNS__
	return system(s);
#else
	return (1);
#endif
}
/*
 * Strip leading ! if present, do exec.
 */
static	void	exec2(register char *s)
{
	if (*s == '!')
		++s;
	mode(0);
#ifndef	__TOWNS__
	execl("/bin/sh", "sh", "-c", s);
#endif
}
/* End of rz.c */
