[fa.info-vax] XMODEM and x-assemblers. Info. request.

info-vax@ucbvax.ARPA (04/02/85)

From: J.R.COWIE%edinburgh@ucl-cs.arpa

I would be grateful for information on the following:-

1/ Where can I obtain a copy of a program which will handle the XMODEM
file transfer protocol to run on UNIX 4.2?

2/ Is there a set of integrated tools available to handle micro-development
work under UNIX? I am thinking of cross assemblers and compilers for several
micro-processors, a common linker, library management, debugger and down-
loading facilities.

This could be either a commercial package or public domain. 

Thanks in advance.
Jim Cowie,
University of Strathclyde,
Glasgow,
Scotland.

info-vax@ucbvax.ARPA (04/03/85)

From: ihnp4!houxm!whuxl!whuxlm!jph@BERKELEY

Here is a version of UMODEM for BSD 4.1
===============cut here=================
/*
 *  UMODEM -- Implements the "CP/M User's Group XMODEM" protocol, 
 *	      the TERM II File Transfer Protocol (FTP) Number 1,
 *	      and the TERM II File Transfer Protocol Number 4 for
 *            packetized file up/downloading.    
 *
 *            NOTE: This program is a modified version of the
 *		    the UMODEM31.c program which was obtained from
 *		    a RCP/M. The original program was written by
 *		    R. Conn and ran under Version 7 unix. This
 *		    version was modified by J. Antrosiglio (BTL-HO)
 *		    to run under 4.0 UNIX and future releases.
 *		    The changes made were to upgrade the code to
 * 		    use the termio structure rather than the old
 *		    sgttyb structure, and use the new ioctl calling
 * 		    parameters.
 *
 *	VERSION 4.0 - first release
 *	changed on 09/82 : gettype()
 *				this function called error() to close 
 *				the logfile which is not open while checking
 *				input parameters.
 *
 *	VERSION 4.1
 *	changed on 01/83 : argument parser
 *				the default case called error() to close
 *				the logfile which is not open at this time.
 *
 *		    
 *		    ANYONE INTERESTED IN THE VERSION 7 PROGRAM
 *		    MAY OBTAIN A COPY FROM J. C. ANTROSIGLIO
 *                  HO 3G-321 Ext. 2374
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <termio.h>
#include <signal.h>

#define	     VERSION	41	/* Version Number */
#define	     TRUE	1		
#define      FALSE      0

/*  ASCII Constants  */
#define      SOH  	001 
#define	     STX	002
#define	     ETX	003
#define      EOT	004
#define	     ENQ	005
#define      ACK  	006
#define	     LF		012   /* Unix LF/NL */
#define	     CR		015  
#define      NAK  	025
#define	     SYN	026
#define	     CAN	030
#define	     ESC	033
#define	     CTRLZ	032   /* CP/M EOF for text (usually!) */

/*  UMODEM Constants  */
#define      TIMEOUT  	-1
#define      ERRORMAX  	10    /* maximum errors tolerated */
#define      RETRYMAX  	10    /* maximum retries to be made */
#define	     BBUFSIZ	128   /* buffer size -- do not change! */

#define      CREATMODE	0644  /* mode for created files */

/*  ARPA Net Constants  */
/*	The following constants are used to communicate with the ARPA
 *	Net SERVER TELNET and TIP programs.  These constants are defined
 *	as follows:
 *		IAC			<-- Is A Command; indicates that
 *						a command follows
 *		WILL/WONT		<-- Command issued to SERVER TELNET
 *						(Host); WILL issues command
 *						and WONT issues negative of
 *						the command
 *		DO/DONT			<-- Command issued to TIP; DO issues
 *						command and DONT issues
 *						negative of the command
 *		TRBIN			<-- Transmit Binary Command
 *	Examples:
 *		IAC WILL TRBIN	<-- Host is configured to transmit Binary
 *		IAC WONT TRBIN	<-- Host is configured NOT to transmit binary
 *		IAC DO TRBIN	<-- TIP is configured to transmit Binary
 *		IAC DONT TRBIN	<-- TIP is configured NOT to transmit binary
 */
#define	     IAC	0377	/* Is A Command */
#define	     DO		0375	/* Command to TIP */
#define	     DONT	0376	/* Negative of Command to TIP */
#define	     WILL	0373	/* Command to SERVER TELNET (Host) */
#define	     WONT	0374	/* Negative of Command to SERVER TELNET */
#define	     TRBIN	0	/* Transmit Binary Command */

struct termio  ttys, ttysnew, ttystemp;    /* for stty terminal mode calls */

struct stat statbuf;  	/* for terminal message on/off control */
FILE *LOGFP, *fopen();
char buff[BBUFSIZ];

int pagelen;
char *ttyname();  /* forward declaration for C */

char *tty;
char XMITTYPE;
int ARPA, RECVFLAG, SENDFLAG, FTP1, PMSG, DELFLAG, LOGFLAG, MUNGMODE;
int STATDISP, BIT7, BITMASK;
int delay;

long tloc;	/* holds the current time */

alarmfunc();

main(argc, argv)
int argc;
char **argv;
{
	char *logfile;
	int index;
	char flag;

	logfile ="umodem.log";		/* name of log file */

	printf("\nUMODEM Version %d.%d", VERSION/10, VERSION%10);
	printf(" -- UNIX-Based Remote File Transfer Facility\n");

	if (argc < 3 || *argv[1] != '-')
	{  printf("\nUsage:  \n\tumodem ");
		printf("-[options]");
		printf(" filename\n");
	   printf("\n\tOption available:\n");
	   printf("\n");
	   printf("\n\trb <-- Receive Binary");
	   printf("\n\trt <-- Receive Text");
	   printf("\n\tsb <-- Send Binary");
	   printf("\n\tst <-- Send Text");
	   printf("\n\tp  <-- Turn ON Parameter Display");
	   printf("\n\tl  <-- (ell) Turn OFF LOG File Entries");
	   printf("\n\t1  <-- (one) Employ TERM II FTP 1");
	   printf("\n\ta  <-- Turn ON ARPA Net Flag");
	   printf("\n\tm  <-- Allow file overwiting on receive");
	   printf("\n\td  <-- Delete umodem.log File before starting");
	   printf("\n\ty  <-- Display file status (size) information only");
	   printf("\n\t7  <-- Enable 7-bit transfer mask");
	   printf("\n\t4  <-- Enable TERM II FTP 4");
	   printf("\n");
		exit(-1);
	}

	index = 1;  /* set index for loop */
	delay = 3;  /* assume FTP 3 delay */
	PMSG = FALSE;  /* turn off flags */
	FTP1 = FALSE;  /* assume FTP 3 (CP/M UG XMODEM2) */
	RECVFLAG = FALSE;  /* not receive */
	SENDFLAG = FALSE;  /* not send either */
	XMITTYPE = 't';  /* assume text */
	DELFLAG = FALSE;  /* do NOT delete log file before starting */
	LOGFLAG = TRUE;  /* assume log messages */
	ARPA = FALSE;  /* assume not on ARPA Net */
	MUNGMODE = FALSE; /* protect files from overwriting */
	STATDISP = FALSE;  /* assume not a status display */
	BIT7 = FALSE;  /* assume 8-bit communication */
	while ((flag = argv[1][index++]) != '\0')
	    switch (flag) {
		case 'a' : ARPA = TRUE;  /* set ARPA Net */
			   break;
		case 'p' : PMSG = TRUE;  /* print all messages */
			   break;
		case '1' : FTP1 = TRUE;  /* select FTP 1 */
			   delay = 5;  /* FTP 1 delay constant */
			   printf("\nUMODEM:  TERM II FTP 1 Selected\n");
			   break;
		case 'd' : /*DELFLAG = TRUE;   delete log file first */
			   break;
		case 'l' : /*LOGFLAG = FALSE;   turn off log report */
			   break;
		case 'r' : RECVFLAG = TRUE;  /* receive file */
			   XMITTYPE = gettype(argv[1][index++]);  /* get t/b */
			   break;
		case 's' : SENDFLAG = TRUE;  /* send file */
			   XMITTYPE = gettype(argv[1][index++]);
			   break;
		case 'm' : MUNGMODE = TRUE; /* allow overwriting of files */
			   break;
		case 'y' : STATDISP = TRUE;  /* display file status */
			   break;
		case '7' : BIT7 = TRUE;  /* transfer only 7 bits */
			   break;
		case '4' : FTP1 = TRUE;  /* select FTP 1 (varient) */
			   BIT7 = TRUE;  /* transfer only 7 bits */
			   delay = 5;  /* FTP 1 delay constant */
			   printf("\nUMODEM:  TERM II FTP 4 Selected\n");
			   break;
		default  : printf("Invalid Flag\n");
			   exit(-1);
		}

	if (BIT7 && (XMITTYPE == 'b'))
	{  printf("\nUMODEM:  Fatal Error -- Both 7-Bit Transfer and ");
	   printf("Binary Transfer Selected");
	   exit(-1);  /* error exit to UNIX */
	}

	if (BIT7)  /* set MASK value */
	   BITMASK = 0177;  /* 7 significant bits */
	else
	   BITMASK = 0377;  /* 8 significant bits */

	if (PMSG)
	   { printf("\nSupported File Transfer Protocols:");
	     printf("\n\tTERM II FTP 1");
	     printf("\n\tCP/M UG XMODEM2 (TERM II FTP 3)");
	     printf("\n\tTERM II FTP 4");
	     printf("\n\n");
	   }

	if (LOGFLAG)
	   { if (!DELFLAG)
		LOGFP = fopen(logfile, "a");  /* append to LOG file */
	     else
		LOGFP = fopen(logfile, "w");  /* new LOG file */
	     fprintf(LOGFP,"\n\n++++++++\n");
	     fprintf(LOGFP,"\nUMODEM Version %d.%d\n", VERSION/10, VERSION%10);
	     time(&tloc); /* get current time */
	     fprintf(LOGFP,"\n%sLogged user is %s \n",ctime(&tloc),getlogin());
	     printf("\nUMODEM:  LOG File %s is Open\n",logfile);
	   }

	if (STATDISP) yfile(argv[2]);  /* status of a file */

	if (RECVFLAG && SENDFLAG)
		error("Both Send and Receive Functions Specified", FALSE);
	if (!RECVFLAG && !SENDFLAG)
		error("Neither Send nor Receive Functions Specified", FALSE);

	if (RECVFLAG)
	{  if(open(argv[2], 0) != -1)  /* possible abort if file exists */
	   {	printf("\nUMODEM:  Warning -- Target File Exists\n");
		if( MUNGMODE == FALSE )
			error("Fatal - Can't overwrite file\n",FALSE);
		printf("UMODEM:  Overwriting Target File\n");
	   }
	   rfile(argv[2]);  /* receive file */
	}
	else
		sfile(argv[2]);  /* send file */

}

gettype(ichar)
char ichar;
{
	if (ichar == 't') return(ichar);
	if (ichar == 'b') return(ichar);
	printf("UMODEM:  Invalid Send/Receive Parameter - missing r or t\n");
	exit(-1);
}

/* set tty modes for UMODEM transfers */
setmodes()
{
	char count;
	if (ioctl(0,TCGETA,&ttys)<0)  /* get present tty modes and save them */
				      /* ttys structure */

		error("Can't get TTY Parameters", TRUE);
	tty = ttyname(0);  /* identify current tty */
	
	/* init ttysnew structure */

	ttysnew.c_iflag = 0;		/* clear all input modes */
	ttysnew.c_oflag = 0;		/* clear all output modes */
	ttysnew.c_cflag = (ttys.c_cflag&CBAUD)|CREAD; /* save baud rate */
	ttysnew.c_lflag = 0;		/* clear line discipline modes */
 	ttysnew.c_line  = ttys.c_line;	/* line discipline */

	for(count=0;count < NCC;count++)
		ttysnew.c_cc[count] = ttys.c_cc[count];

	/* set raw mode */

	ttysnew.c_cc[VMIN] = 1;
	ttysnew.c_cc[VTIME] = 1;
	ttysnew.c_cflag |= CS8;
	
	/* set new paramters */
	if (ioctl(0,TCSETAW,&ttysnew) < 0)
		error("Can't set new TTY Parameters", TRUE);

	if (stat(tty, &statbuf) < 0)  /* get tty status */ 
		error("Can't get your TTY Status", TRUE);

	if (PMSG)
		{ printf("\r\nUMODEM:  TTY Device Parameters Altered");
		  ttyparams();  /* print tty params */
		}

	if (ARPA)  /* set 8-bit on ARPA Net */
		setarpa();

	return;
}

/*  set ARPA Net for 8-bit transfers  */
setarpa()
{
	sendbyte(IAC);	/* Is A Command */
	sendbyte(WILL);	/* Command to SERVER TELNET (Host) */
	sendbyte(TRBIN);	/* Command is:  Transmit Binary */

	sendbyte(IAC);	/* Is A Command */
	sendbyte(DO);	/* Command to TIP */
	sendbyte(TRBIN);	/* Command is:  Transmit Binary */

	sleep(3);  /* wait for TIP to configure */

	return;
}

/* restore normal tty modes */
restoremodes(errcall)
int errcall;
{
	if (ARPA)  /* if ARPA Net, reconfigure */
		resetarpa();

	if (ioctl(0,TCSETAW,&ttys) < 0)
		{ if (!errcall)
		   error("RESET - Can't restore normal TTY Params", FALSE);
		else
		   { printf("UMODEM:  ");
		     printf("RESET - Can't restore normal TTY Params\n");
		   }
		}

	if (PMSG)
		{ printf("UMODEM:  TTY Device Parameters Restored\n");
		  ttyparams();  /* print tty params */
		}

	return;
}

/* reset the ARPA Net */
resetarpa()
{
	sendbyte(IAC);	/* Is A Command */
	sendbyte(WONT);	/* Negative Command to SERVER TELNET (Host) */
	sendbyte(TRBIN);	/* Command is:  Don't Transmit Binary */

	sendbyte(IAC);	/* Is A Command */
	sendbyte(DONT);	/* Negative Command to TIP */
	sendbyte(TRBIN);	/* Command is:  Don't Transmit Binary */

	return;
}

/* print error message and exit; if mode == TRUE, restore normal tty modes */
error(msg, mode)
char *msg;
int mode;
{
	if (mode)
		restoremodes(TRUE);  /* put back normal tty modes */
	printf("UMODEM:  %s\n", msg);
	if (LOGFLAG)
	{   fprintf(LOGFP, "UMODEM Fatal Error:  %s\n", msg);
	    fclose(LOGFP);
	}
	exit(-1);
}

/**  print status (size) of a file  **/
yfile(name)
char *name;
{
	printf("UMODEM File Status Display for %s\n", name);
	if (LOGFLAG) fprintf(LOGFP,"UMODEM File Status Display for %s\n",
	  name);

	if (open(name,0) < 0)
	{  printf("File %s does not exist\n", name);
	   if (LOGFLAG) fprintf(LOGFP,"File %s does not exist\n", name);
	   exit(-1);  /* error exit to UNIX */
	}

	prfilestat(name);  /* print status */
	printf("\n");
	if (LOGFLAG)
	{  fprintf(LOGFP,"\n");
	   fclose(LOGFP);
	}

	exit(0);  /* exit to UNIX -- no error */
}

/**  receive a file  **/
rfile(name)
char *name;
{
	char mode;
	int fd, j, firstchar, sectnum, sectcurr, tmode;
	int sectcomp, errors, errorflag, recfin;
	register int bufctr, checksum;
	register int c;
	int errorchar, fatalerror, startstx, inchecksum, endetx, endenq;
	long recvsectcnt;

	mode = XMITTYPE;  /* set t/b mode */
	if ((fd = creat(name, CREATMODE)) < 0)
	  	error("Can't create file for receive", FALSE);
	setmodes();  /* setup tty modes for xfer */
	printf("\r\nUMODEM:  File Name: %s", name);
	if (LOGFLAG)
	{    fprintf(LOGFP, "\n----\nUMODEM Receive Function\n");
	     fprintf(LOGFP, "File Name: %s\n", name);
	     if (FTP1)
		if (!BIT7)
	         fprintf(LOGFP, "TERM II File Transfer Protocol 1 Selected\n");
		else
		 fprintf(LOGFP, "TERM II File Transfer Protocol 4 Selected\n");
	     else
		fprintf(LOGFP,
		  "TERM II File Transfer Protocol 3 (CP/M UG) Selected\n");
	     if (BIT7)
		fprintf(LOGFP, "7-Bit Transmission Enabled\n");
	     else
		fprintf(LOGFP, "8-Bit Transmission Enabled\n");
	}
	printf("\r\nUMODEM:  ");
	if (BIT7)
		printf("7-Bit");
	else
		printf("8-Bit");
	printf(" Transmission Enabled");
	printf("\r\nUMODEM:  Ready to RECEIVE File\r\n");

	recfin = FALSE;
	sectnum = errors = 0;
	fatalerror = FALSE;  /* NO fatal errors */
	recvsectcnt = 0;  /* number of received sectors */

	if (mode == 't')
		tmode = TRUE;
	else
		tmode = FALSE;

	if (FTP1)
	{
	  while (readbyte(4) != SYN);
	  sendbyte(ACK);  /* FTP 1 Sync */
	}
	else sendbyte(NAK);  /* FTP 3 Sync */

        do
        {   errorflag = FALSE;
            do {
                  firstchar = readbyte(6);
            } while ((firstchar != SOH) && (firstchar != EOT) && (firstchar 
		     != TIMEOUT));
            if (firstchar == TIMEOUT)
	    {  if (LOGFLAG)
		fprintf(LOGFP, "Timeout on Sector %d\n", sectnum);
               errorflag = TRUE;
	    }

            if (firstchar == SOH)
	    {  if (FTP1) readbyte(5);  /* discard leading zero */
               sectcurr = readbyte(delay);
               sectcomp = readbyte(delay);
	       if (FTP1) startstx = readbyte(delay);  /* get leading STX */
               if ((sectcurr + sectcomp) == BITMASK)
               {  if (sectcurr == ((sectnum+1)&BITMASK))
		  {  checksum = 0;
		     for (j = bufctr = 0; j < BBUFSIZ; j++)
	      	     {  buff[bufctr] = c = readbyte(delay);
		        checksum = ((checksum+c)&BITMASK);
			if (!tmode)  /* binary mode */
			{  bufctr++;
		           continue;
		        }
			if (c == CR)
			   continue;  /* skip CR's */
			if (c == CTRLZ)  /* skip CP/M EOF char */
			{  recfin = TRUE;  /* flag EOF */
		           continue;
		        }
		        if (!recfin)
			   bufctr++;
		     }
		     if (FTP1) endetx = readbyte(delay);  /* get ending ETX */
		     inchecksum = readbyte(delay);  /* get checksum */
		     if (FTP1) endenq = readbyte(delay); /* get ENQ */
		     if (checksum == inchecksum)  /* good checksum */
		     {  errors = 0;
			recvsectcnt++;
		        sectnum = sectcurr;  /* update sector counter */
			if (write(fd, buff, bufctr) < 0)
			   error("File Write Error", TRUE);
		        else
			{  if (FTP1) sendbyte(ESC);  /* FTP 1 requires <ESC> */
			   sendbyte(ACK);
			}
		     }
		     else
		     {  if (LOGFLAG)
				fprintf(LOGFP, "Checksum Error on Sector %d\n",
				sectnum);
		        errorflag = TRUE;
		     }
                  }
                  else
                  { if (sectcurr == sectnum)
                    {  while(readbyte(3) != TIMEOUT);
		       if (FTP1) sendbyte(ESC);  /* FTP 1 requires <ESC> */
            	       sendbyte(ACK);
                    }
                    else
		    {  if (LOGFLAG)
			{ fprintf(LOGFP, "Phase Error - Received Sector is ");
			  fprintf(LOGFP, "%d while Expected Sector is %d\n",
			   sectcurr, ((sectnum+1)&BITMASK));
			}
			errorflag = TRUE;
			fatalerror = TRUE;
			if (FTP1) sendbyte(ESC);  /* FTP 1 requires <ESC> */
			sendbyte(CAN);
		    }
	          }
           }
           else
	   {  if (LOGFLAG)
		fprintf(LOGFP, "Header Sector Number Error on Sector %d\n",
		   sectnum);
               errorflag = TRUE;
	   }
        }
	if (FTP1 && !errorflag)
	{  if (startstx != STX)
	   {  errorflag = TRUE;  /* FTP 1 STX missing */
	      errorchar = STX;
	   }
	   if (endetx != ETX)
	   {  errorflag = TRUE;  /* FTP 1 ETX missing */
	      errorchar = ETX;
	   }
	   if (endenq != ENQ)
	   {  errorflag = TRUE;  /* FTP 1 ENQ missing */
	      errorchar = ENQ;
	   }
	   if (errorflag && LOGFLAG)
	   {  fprintf(LOGFP, "Invalid Packet-Control Character:  ");
	      switch (errorchar) {
		case STX : fprintf(LOGFP, "STX"); break;
		case ETX : fprintf(LOGFP, "ETX"); break;
		case ENQ : fprintf(LOGFP, "ENQ"); break;
		default  : fprintf(LOGFP, "Error"); break;
	      }
	      fprintf(LOGFP, "\n");
	   }
	}
        if (errorflag == TRUE)
        {  errors++;
	   while (readbyte(3) != TIMEOUT);
           sendbyte(NAK);
        }
  }
  while ((firstchar != EOT) && (errors != ERRORMAX) && !fatalerror);
  if ((firstchar == EOT) && (errors < ERRORMAX))
  {  if (!FTP1) sendbyte(ACK);
     close(fd);
     restoremodes(FALSE);  /* restore normal tty modes */
     if (FTP1)
	while (readbyte(3) != TIMEOUT);  /* flush EOT's */
     sleep(3);  /* give other side time to return to terminal mode */
     if (LOGFLAG)
     {  fprintf(LOGFP, "\nReceive Complete\n");
	fprintf(LOGFP,"Number of Received CP/M Records is %ld\n", recvsectcnt);
        fclose(LOGFP);
     }
     printf("\n");
     exit(0);
  }
  else
  {  if (LOGFLAG && FTP1 && fatalerror) fprintf(LOGFP,
	"Synchronization Error");
     error("TIMEOUT -- Too Many Errors", TRUE);
  }
}

/**  send a file  **/
sfile(name)
char *name;
{
	char mode;
	int fd, charval, attempts;
	int nlflag, sendfin, tmode;
	register int bufctr, checksum, sectnum;
	char c;
	int sendresp;  /* response char to sent block */

	mode = XMITTYPE;  /* set t/b mode */
	if ((fd = open(name, 0)) < 0)
	{  if (LOGFLAG) fprintf(LOGFP, "Can't Open File\n");
     	   error("Can't open file for send", FALSE);
	}
	setmodes();  /* setup tty modes for xfer */	
	printf("\r\nUMODEM:  File Name: %s", name);
	if (LOGFLAG)
	{   fprintf(LOGFP, "\n----\nUMODEM Send Function\n");
	    fprintf(LOGFP, "File Name: %s\n", name);
	}
	prfilestat(name);  /* print file size statistics */
	if (LOGFLAG)
	{  if (FTP1)
	      if (!BIT7)
		fprintf(LOGFP, "TERM II File Transfer Protocol 1 Selected\n");
	      else
		fprintf(LOGFP, "TERM II File Transfer Protocol 4 Selected\n");
	   else
		fprintf(LOGFP,
		   "TERM II File Transfer Protocol 3 (CP/M UG) Selected\n");
	   if (BIT7)
		fprintf(LOGFP, "7-Bit Transmission Enabled\n");
	   else
		fprintf(LOGFP, "8-Bit Transmission Enabled\n");
	}
	printf("\r\nUMODEM:  ");
	if (BIT7)
		printf("7-Bit");
	else
		printf("8-Bit");
	printf(" Transmission Enabled");
	printf("\r\nUMODEM:  Ready to SEND File\r\n");

	if (mode == 't')
	   tmode = TRUE;
	else
	   tmode = FALSE;

        sendfin = nlflag = FALSE;
  	attempts = 0;

	if (FTP1)
	{  sendbyte(SYN);  /* FTP 1 Synchronize with Receiver */
	   while (readbyte(5) != ACK)
	   {  if(++attempts > RETRYMAX*6) error("Remote System Not Responding",
		TRUE);
	      sendbyte(SYN);
	   }
	}
	else
	{  while (readbyte(30) != NAK)  /* FTP 3 Synchronize with Receiver */
	   if (++attempts > RETRYMAX) error("Remote System Not Responding",
		TRUE);
	}

	sectnum = 1;  /* first sector number */
	attempts = 0;

        do 
	{   for (bufctr=0; bufctr < BBUFSIZ;)
	    {   if (nlflag)
	        {  buff[bufctr++] = LF;  /* leftover newline */
	           nlflag = FALSE;
	        }
	        if ((charval = read(fd, &c, 1)) < 0)
		   error("File Read Error", TRUE);
		if (charval == 0)  /* EOF for read */	
		{  sendfin = TRUE;  /* this is the last sector */
		   if (!bufctr)  /* if EOF on sector boundary */
		      break;  /* avoid sending extra sector */
		   if (tmode)
		      buff[bufctr++] = CTRLZ;  /* Control-Z for CP/M EOF */
	           else
		      bufctr++;
		   continue;
	        }
		if (tmode && c == LF)  /* text mode & Unix newline? */
	    	{  if (c == LF)  /* Unix newline? */
		   {  buff[bufctr++] = CR;  /* insert carriage return */
		      if (bufctr < BBUFSIZ)
	                 buff[bufctr++] = LF;  /* insert Unix newline */
	 	      else
		         nlflag = TRUE;  /* insert newline on next sector */
		   }
		   continue;
	   	}	
		buff[bufctr++] = c;  /* copy the char without change */
	    }
            attempts = 0;
	
	    if (!bufctr)  /* if EOF on sector boundary */
   	       break;  /* avoid sending empty sector */

            do
            {   sendbyte(SOH);  /* send start of packet header */
		if (FTP1) sendbyte(0);  /* FTP 1 Type 0 Packet */
                sendbyte(sectnum);  /* send current sector number */
                sendbyte(-sectnum-1);  /* and its complement */
		if (FTP1) sendbyte(STX);  /* send STX */
                checksum = 0;  /* init checksum */
                for (bufctr=0; bufctr < BBUFSIZ; bufctr++)
                {  sendbyte(buff[bufctr]);  /* send the byte */
		   if (ARPA && (buff[bufctr]==0xff))  /* ARPA Net FFH esc */
			sendbyte(buff[bufctr]);  /* send 2 FFH's for one */
                   checksum = ((checksum+buff[bufctr])&BITMASK);
	        }
/*		while (readbyte(3) != TIMEOUT);   flush chars from line */
		if (FTP1) sendbyte(ETX);  /* send ETX */
                sendbyte(checksum);  /* send the checksum */
		if (FTP1) sendbyte(ENQ);  /* send ENQ */
                attempts++;
		if (FTP1)
		{  sendresp = NAK;  /* prepare for NAK */
		   if (readbyte(10) == ESC) sendresp = readbyte(10);
		}
		else
		   sendresp = readbyte(10);  /* get response */
		if ((sendresp != ACK) && LOGFLAG)
		   { fprintf(LOGFP, "%x Non-ACK Received on Sector %d\n"
		      ,sendresp,sectnum);
		     if (sendresp == TIMEOUT)
			fprintf(LOGFP, "This Non-ACK was a TIMEOUT\n");
		   }
            }   while((sendresp != ACK) && (attempts != RETRYMAX));
            sectnum++;  /* increment to next sector number */
    }  while (!sendfin && (attempts != RETRYMAX));

    if (attempts == RETRYMAX)
	error("Remote System Not Responding", TRUE);

    attempts = 0;
    if (FTP1)
	while (attempts++ < 10) sendbyte(EOT);
    else
    {	sendbyte(EOT);  /* send 1st EOT */
	while ((readbyte(15) != ACK) && (attempts++ < RETRYMAX))
	   sendbyte(EOT);
	if (attempts >= RETRYMAX)
	   error("Remote System Not Responding on Completion", TRUE);
    }

    close(fd);
    restoremodes(FALSE);  
    sleep(5);  /* give other side time to return to terminal mode */
    if (LOGFLAG)
    {  fprintf(LOGFP, "\nSend Complete\n");
       fclose(LOGFP);
    }
    printf("\n");
    exit(0);

}

/*  print file size status information  */
prfilestat(name)
char *name;
{
	struct stat filestatbuf; /* file status info */

	stat(name, &filestatbuf);  /* get file status bytes */
	printf("\r\nUMODEM:  Estimated File Size %ldK, %ld Records, %ld Bytes",
	  (filestatbuf.st_size/1024)+1, (filestatbuf.st_size/128)+1,
	  filestatbuf.st_size);
	if (LOGFLAG)
	  fprintf(LOGFP,"Estimated File Size %ldK, %ld Records, %ld Bytes\n",
	  (filestatbuf.st_size/1024)+1, (filestatbuf.st_size/128)+1,
	  filestatbuf.st_size);
	return;
}

/* get a byte from data stream -- timeout if "seconds" elapses */
readbyte(seconds)
unsigned seconds;
{
	int c;
	
	signal(SIGALRM,alarmfunc);  /* catch alarms */	
	alarm(seconds);  /* set the alarm clock */
	if (read(0, &c, 1) < 0)  /* get a char; error means we timed out */
	  {
	     return(TIMEOUT);
	  }
	alarm(0);  /* turn off the alarm */
	return((c&BITMASK));  /* return the char */
}

/* send a byte to data stream */
sendbyte(data)
char data;
{
	char dataout;
	dataout = (data&BITMASK);  /* maskyr 7 or 8 bits */
	write(1, &dataout, 1);  /* write the byte */
	return;
}

/* function for alarm clock timeouts */
alarmfunc()
{
	return;  /* this is basically a dummy function to force error */
		 /* status return on the "read" call in "readbyte"    */
}

/* print data on TTY setting */
ttyparams()
{
	ioctl(0,TCGETA,&ttystemp);

	tty = ttyname(0);
	stat(tty, &statbuf);

	printf("\r\n\nTTY Device Parameter Display");
	  printf("\r\n\tTTY Device Name is %s\r\n\n", tty);
	  printf("\tEcho Enabled       "); pryn(ECHO,ttystemp.c_lflag);
	  printf("\tAny Parity Allowed "); pryn(PARENB,ttystemp.c_cflag);
	  printf("\tEven Parity Allowed"); pryn(PARODD,ttystemp.c_cflag);
	  printf("\tLower Case Map     "); pryn(OLCUC,ttystemp.c_oflag);
	  printf("\tTabs Expanded      "); pryn(TAB3,ttystemp.c_oflag);
	  printf("\tCR to NL(input)    "); pryn(ICRNL,ttystemp.c_iflag);
	  printf("\tNL to CR/NL(output)"); pryn(ONLCR,ttystemp.c_oflag);
	  printf("\tRaw Input Disabled "); pryn(ISIG,ttystemp.c_lflag);
	  printf("\tRaw Output Disabled"); pryn(OPOST,ttystemp.c_oflag);
	  printf("\tTTY Input Rate     :   ");
	    prbaud(ttystemp.c_cflag&CBAUD);
	  printf("\tTTY Output Rate    :   ");
	    prbaud(ttystemp.c_cflag&CBAUD);
}

pryn(iflag,pointer)
int iflag;
int pointer;
{

	if (pointer&iflag)
	   printf(":   Yes\r\n");
	else
	   printf(":   No\r\n");
}

prbaud(speed)
char speed;
{
	switch (speed) {

		case B50 : printf("50"); break;
		case B75 : printf("75"); break;
		case B110 : printf("110"); break;
		case B134 : printf("134.5"); break;
		case B150 : printf("150"); break;
		case B200 : printf("200"); break;
		case B300 : printf("300"); break;
		case B600 : printf("600"); break;
		case B1200 : printf("1200"); break;
		case B1800 : printf("1800"); break;
		case B2400 : printf("2400"); break;
		case B4800 : printf("4800"); break;
		case B9600 : printf("9600"); break;
		case EXTA : printf("External A"); break;
		case EXTB : printf("External B"); break;
		default    : printf("Error"); break;
	}
	printf(" Baud\r\n");
}

info-vax@ucbvax.ARPA (04/05/85)

From: dual!mordor!seismo!harvard!gcc-bill!brad@BERKELEY (Brad Parker)


I have some very simple code, which does uploads only from unix.
After doing this, I discovered macput.c does xmodem with some xtra
stuff.
I would grab a copy of macput.c (unix program for uploading files to 
a mac) and hack it a bit.
If you want my upload code, I'd be glad to mail it to you. 

If I were you, I'd find a copy of Kermit - all the work should be
done, and there are versions available for most popular micro's.


J Bradford Parker
uucp: seismo!harvard!gcc-bill!brad

"I've seen this happen in other people's lives... 
	and now it's happening in mine."		-The Smiths

info-vax@ucbvax.ARPA (04/05/85)

From: dual!mordor!seismo!harvard!bu-cs!root@BERKELEY (BostonU SysMgr)

Hope this helps...

--------------------modem.c--------------------
/*
 * a version of Ward Christensen's MODEM program for
 * UNIX v7, 4.1bsd
 *
 * by Andrew Scott Beals
 * (SJOBRG.ANDY%MIT-OZ@MIT-MC)
 *
 *	features added by BZS (root%bostonu@csnet-relay)
 *
 *		sub options
 *			a	ascii (cr/lf->\n etc.)
 *			b	binary (good luck)
 *			q	send to our laser printer
 *	changes by BZS
 *		removed requirement of '-' for first arg
 *		so essentially same as TOPS-20 version
 *		(especially from our dandelion)
 */

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sgtty.h>

#define uchar   unsigned char

#define SLEEP   30
#define ACKTIMO	30	/* longest we will wait for an ACK	*/

/* Protocol characters used */

#define SOH     1       /* Start Of Header */
#define EOT     4       /* End Of Transmission */
#define ACK     6       /* ACKnowlege */
#define NAK     0x15    /* Negative AcKnowlege */

short           ttyhold;
struct sgttyb   ttymode;
char *prog ;		/* BZS argv[0]		*/
int rflag ;
int sflag ;
int aflag = 1 ;		/* default is ASCII	*/
int qflag ;		/* send to Qms printer Queue with -q flag */
int pflag ;		/* third arg is a command to pipe to	*/
int Sflag ;		/* login server		*/
char *tmpf = "/tmp/MODXXXXXX" ;
#define SERVER	"-MODEM"
#define PRCMD	"qpr -q %s"
#undef tolower
#define tolower(x) (isupper(x) ? (x) - ' ' : x)
/*
 *	collect all error messages here for later logging etc.
 */
eprint(fmt,a,b,c,d) char *fmt, *a, *b, *c, *d ;
{
	fprintf(stderr,fmt,a,b,c,d) ;
}
usage()
{
	eprint("Usage: %s [-]<option><suboption> filename\n",prog) ;
	eprint("\tWhere option can be either r for receive\n") ;
	eprint("\tor s for send and suboptions are:\n") ;
	eprint("\t\ta\tASCII (text) file\n") ;
	eprint("\t\tb\tBINARY file\n") ;
	eprint("\t\tq\tLASER PRINT FILE (implies ASCII)\n") ;
	eprint("\t\t\t(no file name for print files)\n") ;
	eprint("\t\tp\tpipe received data, 2nd arg is UNIX command\n") ;
	exit(1) ;
}
main(argc,argv)
int     argc;
char    **argv;
{
	register uchar  checksum,index,blocknumber,errorcount,
	character;
	uchar           sector[128];
	int             foo,timeout();
	FILE *fout ;
	char *ap ;
	char *fname, *pcmd ;
	static char buf[BUFSIZ+1] ;

	prog = *argv ;
	if(strcmp(prog,SERVER) == 0)
	{
		++qflag ;
		++Sflag ;
		++rflag ;
		goto serv1 ;
	}
	if(argc < 2)
		usage() ;
	/*
	 * skip but don't require initial dash in option list
	 */
	ap = argv[1] ;
	if(*ap == '-') ++ap ;
	/*
	 *	options
	 *		accept either case, twenex people expect this
	 */
	if(tolower(*ap) == 'r') ++rflag ;
	else if(tolower(*ap) == 's') ++sflag ;
	else usage() ;
	++ap ;
	/*
	 *	suboptions
	 */
	switch(tolower(*ap)) {

		case '\0' : break ;
		case 'a' :  break ;	/* DEFAULT	*/
		case 'p' : ++pflag ; break ;
		case 'q' : ++qflag ; break ;
		case 'b' : aflag = 0 ; break ;

		case 'h' :		/* FALL THRU */
		default  : usage() ;
	}

	/*
	 *	check for other absurdities
	 */
	if(*++ap) usage() ;
	if(sflag && (qflag || pflag))
	{
		eprint("%s: only use 'a' or 'b' with 's'\n",prog) ;
		exit(1) ;
	}
	/*
	 * 	The file name
	 */
serv1:
	if(qflag || pflag)
	{
		mktemp(tmpf) ;
		fname = tmpf ;
	}
	else if(argc != 3) usage() ;
	else fname = argv[2] ;
	if(pflag) pcmd = argv[2] ;

	gtty(0,&ttymode);
	ttyhold=ttymode.sg_flags;
	ttymode.sg_flags |= RAW ;
	ttymode.sg_flags& = ~ECHO;

	if(rflag) goto rec ;
	/* (else)	*/
	/* send a file to the remote */

	stty(0,&ttymode);
	if((foo=open(fname,0))==-1)
	{
		fprintf(stderr,"can't open %s for send!\7\n",fname);
		die(1);
	}
	
	fprintf(stderr,"file open, ready to send\r\n");
	fflush(stderr);
	fflush(stdout);
	errorcount=0;
	blocknumber=1;
	signal(SIGALRM,timeout);
	alarm(80);
	
	while((getchar()!=NAK)&&(errorcount<10)) ++errorcount;
	alarm(0);
#ifdef DEBUG
	fprintf(stderr,"transmission beginning\r\n");
	fflush(stderr);
#endif
	if(errorcount==10)
	{
error:          
		fprintf(stderr,"too many errors...aborting\7\7\7\r\n");
		die(1);
	}
	
	while(read(foo,sector,sizeof sector) > 0)
	{
		errorcount=0;
		while(errorcount<10)
		{
#ifdef DEBUG
			fprintf(stderr,"{%d} ",blocknumber);
			fflush(stderr);
#endif
			putchar(SOH); /* here is our header */
			putchar(blocknumber);   /* the block number */
			putchar(~blocknumber);  /* & its complement */
			checksum=0;
			for(index=0;index<128;index++)
			{
				putchar(sector[index]);
				checksum+=sector[index];
			}
			putchar(checksum); /* tell our checksum */
			fflush(stdout);
			alarm(ACKTIMO) ;
			if(getchar() != ACK) ++errorcount;
			else break;
			alarm(0) ;
		}
		alarm(0) ;
		if(errorcount==10)goto error;
		++blocknumber;
	}
	index=1;
	while(index)
	{
		putchar(EOT);
		fflush(stdout);
		index=getchar()==ACK;
	}
	fprintf(stderr,"Transmission complete.\r\n");
	fflush(stderr);
	die(0);

rec:    /* receive a file */

	if(access(fname,0) == 0)
	{
		    fprintf(stderr,
		    	"%s exists; you have 10 seconds to abort (^C)\r\n",
				fname);
		fflush(stderr);
		sleep(10);
		fprintf(stderr,"Too late!\r\n");
		fflush(stderr);
	}
	stty(0,&ttymode);
	if((fout = fopen(fname,"w")) == NULL) {
		perror(fname);
		die(1);
	}
	printf("you have 30 seconds to start transmission...");
	fflush(stdout);
	sleep(SLEEP);   /* wait for the user to get his act together */
#ifdef DEBUG
	fprintf(stderr,"Starting...\r\n");
	fflush(stderr);
#endif
	putchar(NAK);
	fflush(stdout);
	errorcount=0;
	blocknumber=1;
	while((character=getchar())!=EOT)
	{
		register uchar not_ch;
		
		    if(character!=SOH)
		    {
#ifdef DEBUG
			fprintf(stderr,"Not SOH\r\n");
			fflush(stderr);
#endif
			if(++errorcount<10)goto nakit;
			    else goto error;
			
		}
		character=getchar();
		not_ch=~getchar();
#ifdef DEBUG
		fprintf(stderr,"[%d] ",character);
		fflush(stderr);
#endif
		if(character!=not_ch)
		{
#ifdef DEBUG
			fprintf(stderr,"Blockcounts not ~\r\n");
			fflush(stderr);
#endif
			++errorcount;
			goto nakit;
		}
		if(character!=blocknumber)
		{
#ifdef DEBUG
			fprintf(stderr,"Wrong blocknumber\r\n");
			fflush(stderr);
#endif
			++errorcount;
			goto nakit;
			
		}
		
		checksum=0;
		
		for(index=0;index<128;index++)
		{
			sector[index]=getchar();
			checksum+=sector[index];
		}
		if(checksum!=getchar())
		{
#ifdef DEBUG
			fprintf(stderr,"Bad checksum\r\n");
			fflush(stderr);
#endif
			errorcount++;
			goto nakit;
			
		}
		putchar(ACK);
		fflush(stdout);
		blocknumber++;
		if(aflag || qflag) decrlf(fout,sector) ;
		else fwrite(sector,sizeof sector[0],sizeof sector,fout);
		if(!errorcount) continue;
nakit:
		putchar(NAK);
		fflush(stdout);
	}
	
	fclose(fout);
	putchar(ACK);
		/* tell the modem on the other end we accepted his EOT */
	putchar(ACK);
	putchar(ACK);
	fflush(stdout);
	/*
	 *	special hack for stuff going directly to
	 *	our laser printer but an interesting idea
	 *	in general, no? (eg. this can be a shell)
	 *	BZS
	 */
	if(qflag)
	{
		sprintf(buf,PRCMD,fname) ;
		system(buf) ;
		unlink(fname) ;
	}
	else if(pflag)
	{
		sprintf(buf,"cat %s | %s",fname,pcmd) ;
		system(buf) ;
		unlink(fname) ;
	}
	fprintf(stderr,"Completed.\r\n");
	fflush(stderr);
	die(0);
}

timeout()
{
	fprintf(stderr,
	"Timed out waiting for NAK from remote system\7\7\7\r\n");
	die(1);
}

die(how)
register int how;
{
	ttymode.sg_flags=ttyhold;
	stty(0,&ttymode);
	exit(how);
}
/*
 *	typical nuisance is CR/LF pairs in UNIX files
 *	and CR only
 *	this is really the only need for 'ASCII' mode
 */
decrlf(fp,cp) FILE *fp ; register char *cp ;
{
	register int i ;

	for(i = 128 ; i > 0 ; i--,cp++)
	{
		switch(*cp) {

		case '\r' :
			putc('\n',fp) ;
			++cp ;
			if(i && (*cp == '\n')) --i ;
			break ;
		/*
		 *	Any ctrl chars that should pass thru go here
		 */
		case '\t' :	/* TAB	*/
		case 014  :	/* FF	*/
		case 033  :	/* ESC	*/
		case '\b' :	/* BS	*/
		case '\n' :	/* NL	*/
			putc(*cp,fp) ;
			break ;
		default :
			if(*cp >= ' ') putc(*cp,fp) ;
			break ;
		}
	}
}
--------------------modem.proto--------------------

                 MODEM/XMODEM Protocol Explained
                by Kelly Smith, CP/M-Net "SYSOP"
January 8,1980
Edited April 28, 1981 - FJW

     I  thought  that it may be of some interest to those of  you
who use the MODEM/XMODEM file transfer capability to get a little
insight  as  to the communications  protocol  (i.e.  "handshaking
method") used by the system.

     Herein lie the details of a very good (but not perfect) data
communications  protocol that has become the "de facto"  standard
for  various  remote CP/M systems (RCPM's) which  are  accessible
across  the country.   (Refer to RCPMLSTx.DOC on all  RCPM's  for
access  numbers and note that the "x" in that list changes as new
system  are  listed).   I  also  wish  to  give  credit  to  Ward
Christensen  (the "original" CBBS) for writing  MODEM.ASM  (CPMUG
Volume 25.11) and Keith Petersen,  Bruce Ratoff,  Dave Hardy, Rod
Hart,  Tom  "C"  (we  know who you are  Tom!),  and  others,  for
enhancements to Ward's original program which we now call  XMODEM
(external modem).

     Data is sent in 128-byte sequentially numbered blocks,  with
a single checksum byte appended to the end of each block.  As the
receiving  computer acquires the incoming data,  it performs  its
own  checksum  and upon each completion of a block,  it  compares
its  checksum result with that of the sending computers.   If the
receiving computer matches the checksum of the sending  computer,
it   transmits   an  ACK  (ASCII  code  protocol  character   for
ACKNOWLEDGE  (04 Hex,  Control-F)) back to the sending  computer.
The  ACK  therefore  means "all's well on  this  end,  send  some
more...".

Notice in the following example,  that the sending computer  will
transmit  an "initial NAK" (ASCII protocol character for NEGATIVE
ACKNOWLEDGE (15 Hex,  Control-U))...or, "that wasn't quite right,
please send again".

Due  to the asynchronous nature of the initial "hook-up"  between
the two computers, the receiving computer will "time-out" looking
for data,  and send the NAK as the "cue" for the sending computer
to  begin  transmission.   The sending computer  knows  that  the
receiving computer will "time-out", and uses this fact to "get in
sync"...  The sending computer responds to the "initial NAK" with
a  SOH  (ASCII code protocol character for START OF  HEADING  (01
Hex,  Control-A)),  sends  the first block number,  sends the 1's
complement  of the block number (VERY important,  I will  discuss
this later...),  sends 128 bytes of 8 bit data (that's why we can
transfer  ".COM"  files),  and  finally  a  checksum,  where  the
checksum is calculated by summing the SOH,  the block number, the
block number 1's complement, and the 128 bytes of data.

Receiving Computer:

----/NAK/------------------------/ACK/----------------------
    15H                           06H

Sending Computer:

--------/SOH/BLK#/BLK#/DATA/CSUM/---/SOH/BLK#/BLK#/DATA/etc.
         01H 001H 0FEH 8bit 8bit     01H 002H 0FDH 8bit ....

     This  process continues,  with the next 128  bytes,  IF  the
block  was  ACK'ed by the receiving computer,  and then the  next
sequential block number and its 1's complement, etc.

     But  what  happens if the block  is  NAK'ed?...   Easy,  the
sending computer just re-sends the previous block.

     Now the hard part...  What if the sending computer transmits
a block, the receiving computer gets it and sends an ACK, but the
sender does not see it?...   The sending computer thinks that  it
has  failed  and  after  10  seconds  re-transmits  the  block...
ARGH!...  The receiving computer has "stashed" the data in memory
or  on disk (data is written to disk after receiving 16  blocks),
the  receiving  computer is now 1 block AHEAD of the  transmiting
computer!   Here comes the operation of the block numbers...  The
receiver  detects that this is the last block (all  over  again),
and   transmits  back  an  ACK,   throws  away  the  block,   and
(effectively)  "catches  up"...    clever!    What's  more,   the
integrity  of  the  block number is  verified  by  the  receiving
computer,  because  it  "sums"  the SOH (01 Hex) with  the  block
number  plus  the 1's complement of the block  number),  and  the
result  MUST BE zero for a proper transfer (e.g.  01+01+FE hex  =
00, on the first block).  The sequence of events then, looks like
this:

Receiving Computer:

----/ACK/-----------------------/NAK/-----------------------
    06H                          15H

Sending Computer:

CSUM/---/SOH/BLK#/BLK#/DATA/CSUM/---/SOH/BLK#/BLK#/DATA/etc.
8bit     01H 003H 0FCH 8bit 8bit     01H 003H 0FCH 8bit ....

     Normal completion of data transfers will then conclude  with
an EOT (ASCII code protocol END OF TRANSMISSION, 04 Hex, Control-
D) from the sending computer,  and a final ACK from the receiving
computer.   Unfortunately,  if  the receiving computer misses the
EOT,  it will continue to wait for the next block (sending a  NAK
every  10  seconds,  up to 10 times) and  eventually  "time-out".
This is rarely the case however, and although not "bullet-proof",
it is a very workable protocol.

Receiving Computer:

----/ACK/---/ACK/"Transfer Complete"/A>(or B>)
     06H     06H .............................

Sending Computer:

CSUM/---/EOT/---/A>(or B>)
8bit     04H .............

     In   some  cases,   where  the  telephone  transmission   is
repeatedly "trashed" (weak signals, multiple noise "hits", etc.),
the receiving computer (and operator) will be provided the option
to  quit.   Here,  the operator enters "R" or "Q" in response  to
"Retry or Quit?" (after 10 retries).

Receiving Computer:

----/NAK/...NAK's ten times.../"Retry or Quit?"(Q)/A>...
     15H

Sending Computer:

CSUM/---/...Garbled Data....../-----------------------/A>...
8bit

 A  final  consideration when using the MODEM program,  is  a
timing  related  problems when transfer  status  messages  and/or
textual  data  is directed to the screen of a slow (4800 Baud  or
less)  terminal  or  to a hard copy  printer.   This  problem  is
readily apparent (multiple NAK's) when using MODEM for the  first
time, and can usually be "cured" by NOT SPECIFYING the "V" (VIEW)
sub-option  when sending or receiving files.   Users of  Lifeboat
Associates  BSTAM encounter the same problem,  but this is easily
fixed  with  the  files  TQPATCH.ASM  and  RQPATCH.ASM  (transfer
quiet/receive quiet) that Keith Petersen (Royal Oak CP/M,  "call-
back" remote system,  (313)-588-7054) wrote to solve the  problem
of low speed terminal I/O.

     For  users of CBBS's that do not have MODEM.ASM (but DO HAVE
a CP/M disk system...ESSENTIAL!),  let me suggest that you  "data
capture" the file MBOOTx.ASM from one of the RCPM's (it's a small
8  kilo-byte file that "fits" in most systems' memory) to get the
larger  MODEM.ASM (40 kilo-bytes).   Check it very carefully  for
errors  using the "data capture" (read ERROR PRONE method  here).
Then edit and assemble for your modem configuration.

     If  you are tired of buying software where the  advertisment
is written better than the program, then the RCPM's are just what
you have been looking for...and FREE!