[net.sources] UNIX Kermit

cmf@cwruecmp.UUCP (Carl Fongheiser) (06/19/84)

Somebody recently requested a copy of the sources to UNIX Kermit.  Well,
to answer a few questions, yes, it is in the public domain, however, I
will not post it at this time, mainly because the people at Columbia
are hard at work bringing UNIX Kermit up to current standards (UNIX
Kermit is probably the most primitive Kermit in existence at the moment).
They should have a new version out any time now, and it would be best
to wait until then.

				Carl Fongheiser
				...!decvax!cwruecmp!cmf

knutson@ut-ngp.UUCP (Jim Knutson) (09/28/84)

Several requests have been made for a UNIX Kermit to be posted and
Columbia doesn't seem to have any way to post it so here it is.
A new version is in the works so this may be obsolete sometime
soon (within the next several months).

--------------------- Cut Here --------------------------
: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting kermit.c'
sed 's/^X//' <<'//go.sysin dd *' >kermit.c
X/*
 *  K e r m i t	 File Transfer Utility
 *
 *  UNIX Kermit, Columbia University, 1981, 1982, 1983
 *	Bill Catchings, Bob Cattani, Chris Maio, Frank da Cruz, Alan Crosswell
 *
 *  Also:   Jim Guyton, Rand Corporation
 *	    Walter Underwood, Ford Aerospace
 *
 *  usage:  kermit c [lbe line baud escapechar]		to connect
 *	    kermit s [d..iflb line baud] file ...	to send files
 *	    kermit r [d..iflb line baud]		to receive files
 *
 *  where   c=connect, s=send, r=receive,
 *	    d=debug, i=image mode, f=no filename conversion, l=tty line,
 *	    b=baud rate, e=escape char.
 *
 *  For remote Kermit, format is either:
 *	    kermit r					to receive files
 *  or	    kermit s file ...				to send files
 *
 */

X/*
 *  Modification History:
 *
 *  Oct. 17 Included fixes from Alan Crosswell (CUCCA) for IBM_UTS:
 *	    - Changed MYEOL character from \n to \r.
 *	    - Change char to int in bufill so getc would return -1 on
 *	      EOF instead of 255 (-1 truncated to 8 bits)
 *	    - Added read() in rpack to eat the EOL character
 *	    - Added fflush() call in printmsg to force the output
 *	    NOTE: The last three changes are not conditionally compiled
 *		  since they should work equally well on any system.
 *
 *	    Changed Berkeley 4.x conditional compilation flag from
 *		UNIX4X to UCB4X.
 *	    Added support for error packets and cleaned up the printing
 *		routines.
 */

#include <stdio.h>	    /* Standard UNIX definitions */
#include <sys/ioctl.h>


X/* Conditional compilation for different machines/operating systems */
X/* One and only one of the following lines should be 1 */

#define UCB4X	    1	    /* Berkeley 4.x UNIX */
#define TOPS_20	    0	    /* TOPS-20 */
#define IBM_UTS	    0	    /* Amdahl UTS on IBM systems */
#define VAX_VMS	    0	    /* VAX/VMS (not yet implemented) */

X/* Conditional compilation for the different Unix variants */
X/* 0 means don't compile it, nonzero means do */

#if UCB4X
#define V6_LIBS	    0	    /* Dont't use retrofit libraries */
#define NO_FIONREAD 0	    /* We have ioctl(FIONREAD,...) for flushinput() */
#define NO_TANDEM   0	    /* We have TANDEM line discipline (xon/xoff) */
#endif

#if IBM_UTS
#define V6_LIBS	    0	    /* Don't use retrofit libraries */
#define NO_FIONREAD 1	    /* No ioctl(FIONREAD,...) for flushinput() */
#define NO_TANDEM   1	    /* No TANDEM line discipline (xon/xoff) */
#endif

#if V6_LIBS
#include <retrofit/sgtty.h>
#include <retrofit/signal.h>
#include <retrofit/setjmp.h>
#else
#include <sgtty.h>
#include <signal.h>
#include <setjmp.h>
#endif

#if NO_TANDEM
#define TANDEM	    0	    /* define it to be nothing if it's unsupported */
#endif


X/* Symbol Definitions */

#define MAXPACKSIZ  94	    /* Maximum packet size */
#define SOH	    1	    /* Start of header */
#define CR	    13	    /* ASCII Carriage Return */
#define SP	    32	    /* ASCII space */
#define DEL	    127	    /* Delete (rubout) */
#define ESCCHR	    '^'	    /* Default escape character for CONNECT */

#define MAXTRY	    10	    /* Times to retry a packet */
#define MYQUOTE	    '#'	    /* Quote character I will use */
#define MYPAD	    0	    /* Number of padding characters I will need */
#define MYPCHAR	    0	    /* Padding character I need (NULL) */

#if IBM_UTS
#define MYEOL	    '\r'    /* End-Of-Line character for UTS systems */
#else
#define MYEOL	    '\n'    /* End-Of-Line character I need */
#endif

#define MYTIME	    10	    /* Seconds after which I should be timed out */
#define MAXTIM	    60	    /* Maximum timeout interval */
#define MINTIM	    2	    /* Minumum timeout interval */

#define TRUE	    -1	    /* Boolean constants */
#define FALSE	    0


X/* Macro Definitions */

X/*
 * tochar: converts a control character to a printable one by adding a space.
 *
 * unchar: undoes tochar.
 *
 * ctl:	   converts between control characters and printable characters by
 *	   toggling the control bit (ie. ^A becomes A and A becomes ^A).
 */
#define tochar(ch)  ((ch) + ' ')
#define unchar(ch)  ((ch) - ' ')
#define ctl(ch)	    ((ch) ^ 64 )


X/* Global Variables */

int	size,		    /* Size of present data */
	rpsiz,		    /* Maximum receive packet size */
	spsiz,		    /* Maximum send packet size */
	pad,		    /* How much padding to send */
	timint,		    /* Timeout for foreign host on sends */
	n,		    /* Packet number */
	numtry,		    /* Times this packet retried */
	oldtry,		    /* Times previous packet retried */
	ttyfd,		    /* File descriptor of tty for I/O, 0 if remote */
	remote,		    /* -1 means we're a remote kermit */
	image,		    /* -1 means 8-bit mode */
	debug,		    /* indicates level of debugging output (0=none) */
	filnamcnv,	    /* -1 means do file name case conversions */
	filecount;	    /* Number of files left to send */

char	state,		    /* Present state of the automaton */
	padchar,	    /* Padding character to send */
	eol,		    /* End-Of-Line character to send */
	escchr,		    /* Connect command escape character */
	quote,		    /* Quote character in incoming data */
	**filelist,	    /* List of files to be sent */
	*filnam,	    /* Current file name */
	recpkt[MAXPACKSIZ], /* Receive packet buffer */
	packet[MAXPACKSIZ]; /* Packet buffer */

FILE	*fp,		    /* File pointer for current disk file */
	*log;		    /* File pointer for Logfile */

jmp_buf env;		    /* Environment ptr for timeout longjump */


X/*
 *  m a i n
 *
 *  Main routine - parse command and options, set up the
 *  tty lines, and dispatch to the appropriate routine.
 */

main(argc,argv)
int argc;			    /* Character pointers to and count of */
char **argv;				/* command line arguments */
{
    char *ttyname,			/* tty name for LINE argument */
	*cp;				/* char pointer */
    int speed,				/* speed of assigned tty, */
	cflg, rflg, sflg;		/* flags for CONNECT, RECEIVE, SEND */

    struct sgttyb
	rawmode,			/* Controlling tty raw mode */
	cookedmode,			/* Controlling tty cooked mode */
	ttymode;			/* mode of tty line in LINE option */

    if (argc < 2) usage();		/* Make sure there's a command line */

    cp = *++argv; argv++; argc -= 2;	/* Set up pointers to args */

X/*  Initialize these values and hope the first packet will get across OK */

    eol = CR;				/* EOL for outgoing packets */
    quote = '#';			/* Standard control-quote char "#" */
    pad = 0;				/* No padding */
    padchar = NULL;			/* Use null if any padding wanted */

    speed = cflg = sflg = rflg = 0;	/* Turn off all parse flags */
    ttyname = 0;			/* Default is remote mode */

#if UCB4X				/* Default to 7-bit masking, CRLF */
    image = FALSE;			/* translation and filename case */
    filnamcnv = TRUE;			/* conversion for UNIX systems */
#else
    image = TRUE;			/* Default to no processing for */
    filnamcnv = FALSE;			/* non-UNIX systems */
#endif

    escchr = ESCCHR;			/* Default escape character */
 
    while ((*cp) != NULL)		/* Parse characters in first arg. */
	switch (*cp++)
	{
	    case 'c': cflg++; break;	/* C = Connect command */
	    case 's': sflg++; break;	/* S = Send command */
	    case 'r': rflg++; break;	/* R = Receive command */

	    case 'd':			/* D = Increment debug mode count */
		debug++; break;
		
	    case 'f':
		filnamcnv = FALSE;	/* F = don't do case conversion */
		break;			/*     on filenames */

	    case 'i':			/* I = Image (8-bit) mode */
		image = TRUE; break;	/* (this is default for non-UNIX) */

	    case 'l':			/* L = specify tty line to use */
		if (argc--) ttyname = *argv++;
		else usage(); 
		if (debug) printf("Line to remote host is %s\n",ttyname); 
		break;
		
	    case 'e':			/* E = specify escape char */
		if (argc--) escchr = **argv++;
		else usage();
		if (debug) printf("Escape char is \"%c\"\n",escchr);
		break;
		
	    case 'b':			/* B = specify baud rate */
#if UCB4X
		if (argc--) speed = atoi(*argv++);
		else usage();
		if (debug) printf("Line speed to remote host is %d\n",speed);
		break;
#else
		printmsg("Speed setting implemented for Unix only.");
		exit(1);
#endif
	}

X/* Done parsing */

    if ((cflg+sflg+rflg) != 1)		/* Only one command allowed */
	usage();


    if (ttyname)			/* If LINE was specified, we */
    {					/* operate in local mode */
	ttyfd = open(ttyname,2);	/* Open the tty line */
	if (ttyfd < 0)
	{
	    printmsg("Cannot open %s",ttyname);
	    exit(1);
	}
	remote = FALSE;			/* Indicate we're in local mode */
    }
    else				/* No LINE specified so we operate */
    {					/* in remote mode (ie. controlling */
	ttyfd = 0;			/* tty is the communications line) */
	remote = TRUE;
    }
    

X/* Put the proper tty into the correct mode */

    if (remote)				/* If remote, use controlling tty */
    {
	gtty(0,&cookedmode);		/* Save current mode so we can */
	gtty(0,&rawmode);		/* restore it later */
	rawmode.sg_flags |= (RAW|TANDEM);
	rawmode.sg_flags &= ~(ECHO|CRMOD);
	stty(0,&rawmode);		/* Put tty in raw mode */
    }
    else				/* Local, use assigned line */
    {
	gtty(ttyfd,&ttymode);
	ttymode.sg_flags |= (RAW|TANDEM);
	ttymode.sg_flags &= ~(ECHO|CRMOD);

#if UCB4X				/* Speed changing for UNIX only */
	if (speed)			/* User specified a speed? */
	{
	    switch(speed)		/* Get internal system code */
	    {
		case 110: speed = B110; break;
		case 150: speed = B150; break;
		case 300: speed = B300; break;
		case 1200: speed = B1200; break;
		case 2400: speed = B2400; break;
		case 4800: speed = B4800; break;
		case 9600: speed = B9600; break; 

		default:
		    printmsg("Bad line speed.");
		    exit(1);
	    }
	    ttymode.sg_ispeed = speed;
	    ttymode.sg_ospeed = speed;
	}
#endif /* UCB4X */

	stty(ttyfd,&ttymode);		/* Put asg'd tty in raw mode */
    }	


X/* All set up, now execute the command that was given. */

    if (debug)
    {
	printf("Debugging level = %d\n\n",debug);

	if (cflg) printf("Connect command\n\n");
	if (sflg) printf("Send command\n\n");
	if (rflg) printf("Receive command\n\n");
    }
  
    if (cflg) connect();		/* Connect command */

    if (sflg)				/* Send command */ 
    {
	if (argc--) filnam = *argv++;	/* Get file to send */
	else
	{   if (remote)
		stty(0,&cookedmode);	/* Restore controlling tty's modes */
	    usage();			/* and give error */
	}
	fp = NULL;			/* Indicate no file open yet */
	filelist = argv;		/* Set up the rest of the file list */
	filecount = argc;		/* Number of files left to send */
	if (sendsw() == FALSE)		/* Send the file(s) */
	    printmsg("Send failed.");	/* Report failure */
	else				/*  or */
	    printmsg("done.");		/* success */
    }

    if (rflg)				/* Receive command */
    {
	if (recsw() == FALSE)		/* Receive the file(s) */
	    printmsg("Receive failed.");
	else				/* Report failure */
	    printmsg("done.");		/* or success */
    }

    if (remote) {
	sleep(1);
	stty(0,&cookedmode);		/* Restore controlling tty's modes */
    }
}


X/*
 *  s e n d s w
 *
 *  Sendsw is the state table switcher for sending files.  It loops until
 *  either it finishes, or an error is encountered.  The routines called
 *  by sendsw are responsible for changing the state.
 *
 */

sendsw()
{
    char sinit(), sfile(), sdata(), seof(), sbreak();

    state = 'S';			/* Send initiate is the start state */
    n = 0;				/* Initialize message number */
    numtry = 0;				/* Say no tries yet */
    while(TRUE)				/* Do this as long as necessary */
    {
	if (debug) printf("sendsw state: %c\n",state);
	switch(state)
	{
	    case 'S':	state = sinit();  break; /* Send-Init */
	    case 'F':	state = sfile();  break; /* Send-File */
	    case 'D':	state = sdata();  break; /* Send-Data */
	    case 'Z':	state = seof();	  break; /* Send-End-of-File */
	    case 'B':	state = sbreak(); break; /* Send-Break */
	    case 'C':	return (TRUE);		 /* Complete */
	    case 'A':	return (FALSE);		 /* "Abort" */
	    default:	return (FALSE);		 /* Unknown, fail */
	}
    }
}


X/*
 *  s i n i t
 *
 *  Send Initiate: send this host's parameters and get other side's back.
 */

char sinit()
{
    int num, len;			/* Packet number, length */

    if (numtry++ > MAXTRY) return('A'); /* If too many tries, give up */
    spar(packet);			/* Fill up init info packet */

    flushinput();			/* Flush pending input */

    spack('S',n,6,packet);		/* Send an S packet */
    switch(rpack(&len,&num,recpkt))	/* What was the reply? */
    {
	case 'N':  return(state);	/* NAK, try it again */

	case 'Y':			/* ACK */
	    if (n != num)		/* If wrong ACK, stay in S state */
		return(state);		/* and try again */
	    rpar(recpkt);		/* Get other side's init info */

	    if (eol == 0) eol = '\n';	/* Check and set defaults */
	    if (quote == 0) quote = '#';

	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* Bump packet count */
	    return('F');		/* OK, switch state to F */

	case 'E':			/* Error packet received */
	    prerrpkt(recpkt);		/* Print it out and */
	    return('A');		/* abort */

	case FALSE: return(state);	/* Receive failure, try again */

	default: return('A');		/* Anything else, just "abort" */
   }
 }


X/*
 *  s f i l e
 *
 *  Send File Header.
 */

char sfile()
{
    int num, len;			/* Packet number, length */
    char filnam1[50],			/* Converted file name */
	*newfilnam,			/* Pointer to file name to send */
	*cp;				/* char pointer */

    if (numtry++ > MAXTRY) return('A'); /* If too many tries, give up */
    
    if (fp == NULL)			/* If not already open, */
    {	if (debug) printf("   Opening %s for sending.\n",filnam);
	fp = fopen(filnam,"r");		/* open the file to be sent */
	if (fp == NULL)			/* If bad file pointer, give up */
	{
	    error("Cannot open file %s",filnam);
	    return('A');
	}
    }

    strcpy(filnam1, filnam);		/* Copy file name */
    newfilnam = cp = filnam1;
    while (*cp != '\0')			/* Strip off all leading directory */
	if (*cp++ == '/')		/* names (ie. up to the last /). */
	    newfilnam = cp;

    if (filnamcnv)			/* Convert lower case to upper	*/
	for (cp = newfilnam; *cp != '\0'; cp++)
	    if (*cp >= 'a' && *cp <= 'z')
		*cp ^= 040;

    len = cp - newfilnam;		/* Compute length of new filename */

    printmsg("Sending %s as %s",filnam,newfilnam);

    spack('F',n,len,newfilnam);		/* Send an F packet */
    switch(rpack(&len,&num,recpkt))	/* What was the reply? */
    {			
	case 'N':			/* NAK, just stay in this state, */
	    num = (--num<0 ? 63:num);	/* unless it's NAK for next packet */
	    if (n != num)		/* which is just like an ACK for */ 
		return(state);		/* this packet so fall thru to... */

	case 'Y':			/* ACK */
	    if (n != num) return(state); /* If wrong ACK, stay in F state */
	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* Bump packet count */
	    size = bufill(packet);	/* Get first data from file */
	    return('D');		/* Switch state to D */

	case 'E':			/* Error packet received */
	    prerrpkt(recpkt);		/* Print it out and */
	    return('A');		/* abort */

	case FALSE: return(state);	/* Receive failure, stay in F state */

	default:    return('A');	/* Something else, just "abort" */
    }
}


X/*
 *  s d a t a
 *
 *  Send File Data
 */

char sdata()
{
    int num, len;			/* Packet number, length */

    if (numtry++ > MAXTRY) return('A'); /* If too many tries, give up */

    spack('D',n,size,packet);		/* Send a D packet */
    switch(rpack(&len,&num,recpkt))	/* What was the reply? */
    {		    
	case 'N':			/* NAK, just stay in this state, */
	    num = (--num<0 ? 63:num);	/* unless it's NAK for next packet */
	    if (n != num)		/* which is just like an ACK for */
		return(state);		/* this packet so fall thru to... */
		
	case 'Y':			/* ACK */
	    if (n != num) return(state); /* If wrong ACK, fail */
	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* Bump packet count */
	    if ((size = bufill(packet)) == EOF) /* Get data from file */
		return('Z');		/* If EOF set state to that */
	    return('D');		/* Got data, stay in state D */

	case 'E':			/* Error packet received */
	    prerrpkt(recpkt);		/* Print it out and */
	    return('A');		/* abort */

	case FALSE: return(state);	/* Receive failure, stay in D */

	default:    return('A');	/* Anything else, "abort" */
    }
}


X/*
 *  s e o f
 *
 *  Send End-Of-File.
 */

char seof()
{
    int num, len;			/* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* If too many tries, "abort" */

    spack('Z',n,0,packet);		/* Send a 'Z' packet */
    switch(rpack(&len,&num,recpkt))	/* What was the reply? */
    {
	case 'N':			/* NAK, just stay in this state, */
	    num = (--num<0 ? 63:num);	/* unless it's NAK for next packet, */
	    if (n != num)		/* which is just like an ACK for */
		return(state);		/* this packet so fall thru to... */

	case 'Y':			/* ACK */
	    if (n != num) return(state); /* If wrong ACK, hold out */
	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* and bump packet count */
	    if (debug) printf("	  Closing input file %s, ",filnam);
	    fclose(fp);			/* Close the input file */
	    fp = NULL;			/* Set flag indicating no file open */ 

	    if (debug) printf("looking for next file...\n");
	    if (gnxtfl() == FALSE)	/* No more files go? */
		return('B');		/* if not, break, EOT, all done */
	    if (debug) printf("	  New file is %s\n",filnam);
	    return('F');		/* More files, switch state to F */

	case 'E':			/* Error packet received */
	    prerrpkt(recpkt);		/* Print it out and */
	    return('A');		/* abort */

	case FALSE: return(state);	/* Receive failure, stay in Z */

	default:    return('A');	/* Something else, "abort" */
    }
}


X/*
 *  s b r e a k
 *
 *  Send Break (EOT)
 */

char sbreak()
{
    int num, len;			/* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* If too many tries "abort" */

    spack('B',n,0,packet);		/* Send a B packet */
    switch (rpack(&len,&num,recpkt))	/* What was the reply? */
    {
	case 'N':			/* NAK, just stay in this state, */
	    num = (--num<0 ? 63:num);	/* unless NAK for previous packet, */
	    if (n != num)		/* which is just like an ACK for */
		return(state);		/* this packet so fall thru to... */

	case 'Y':			/* ACK */
	    if (n != num) return(state); /* If wrong ACK, fail */
	    numtry = 0;			/* Reset try counter */
	    n = (n+1)%64;		/* and bump packet count */
	    return('C');		/* Switch state to Complete */

	case 'E':			/* Error packet received */
	    prerrpkt(recpkt);		/* Print it out and */
	    return('A');		/* abort */

	case FALSE: return(state);	/* Receive failure, stay in B */

	default:    return ('A');	/* Other, "abort" */
   }
}


X/*
 *  r e c s w
 *
 *  This is the state table switcher for receiving files.
 */

recsw()
{
    char rinit(), rfile(), rdata();	/* Use these procedures */

    state = 'R';			/* Receive-Init is the start state */
    n = 0;				/* Initialize message number */
    numtry = 0;				/* Say no tries yet */

    while(TRUE)
    {
	if (debug) printf(" recsw state: %c\n",state);
	switch(state)			/* Do until done */
	{
	    case 'R':	state = rinit(); break; /* Receive-Init */
	    case 'F':	state = rfile(); break; /* Receive-File */
	    case 'D':	state = rdata(); break; /* Receive-Data */
	    case 'C':	return(TRUE);		/* Complete state */
	    case 'A':	return(FALSE);		/* "Abort" state */
	}
    }
}

    
X/*
 *  r i n i t
 *
 *  Receive Initialization
 */
  
char rinit()
{
    int len, num;			/* Packet length, number */

    if (numtry++ > MAXTRY) return('A'); /* If too many tries, "abort" */

    switch(rpack(&len,&num,packet))	/* Get a packet */
    {
	case 'S':			/* Send-Init */
	    rpar(packet);		/* Get the other side's init data */
	    spar(packet);		/* Fill up packet with my init info */
	    spack('Y',n,6,packet);	/* ACK with my parameters */
	    oldtry = numtry;		/* Save old try count */
	    numtry = 0;			/* Start a new counter */
	    n = (n+1)%64;		/* Bump packet number, mod 64 */
	    return('F');		/* Enter File-Receive state */

	case 'E':			/* Error packet received */
	    prerrpkt(recpkt);		/* Print it out and */
	    return('A');		/* abort */

	case FALSE:			/* Didn't get packet */
	    spack('N',n,0,0);		/* Return a NAK */
	    return(state);		/* Keep trying */

	default:     return('A');	/* Some other packet type, "abort" */
    }
}


X/*
 *  r f i l e
 *
 *  Receive File Header
 */

char rfile()
{
    int num, len;			/* Packet number, length */
    char filnam1[50];			/* Holds the converted file name */

    if (numtry++ > MAXTRY) return('A'); /* "abort" if too many tries */

    switch(rpack(&len,&num,packet))	/* Get a packet */
    {
	case 'S':			/* Send-Init, maybe our ACK lost */
	    if (oldtry++ > MAXTRY) return('A'); /* If too many tries "abort" */
	    if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */
	    {				/* Yes, ACK it again with  */
		spar(packet);		/* our Send-Init parameters */
		spack('Y',num,6,packet);
		numtry = 0;		/* Reset try counter */
		return(state);		/* Stay in this state */
	    }
	    else return('A');		/* Not previous packet, "abort" */

	case 'Z':			/* End-Of-File */
	    if (oldtry++ > MAXTRY) return('A');
	    if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */
	    {				/* Yes, ACK it again. */
		spack('Y',num,0,0);
		numtry = 0;
		return(state);		/* Stay in this state */
	    }
	    else return('A');		/* Not previous packet, "abort" */

	case 'F':			/* File Header (just what we want) */
	    if (num != n) return('A');	/* The packet number must be right */
	    strcpy(filnam1, packet);	/* Copy the file name */

	    if (filnamcnv)		/* Convert upper case to lower */
		for (filnam=filnam1; *filnam != '\0'; filnam++)
		    if (*filnam >= 'A' && *filnam <= 'Z')
			*filnam |= 040;

	    if ((fp=fopen(filnam1,"w"))==NULL) /* Try to open a new file */
	    {
		error("Cannot create %s",filnam1); /* Give up if can't */
		return('A');
	    }
	    else			/* OK, give message */
		printmsg("Receiving %s as %s",packet,filnam1);

	    spack('Y',n,0,0);		/* Acknowledge the file header */
	    oldtry = numtry;		/* Reset try counters */
	    numtry = 0;			/* ... */
	    n = (n+1)%64;		/* Bump packet number, mod 64 */
	    return('D');		/* Switch to Data state */

	case 'B':			/* Break transmission (EOT) */
	    if (num != n) return ('A'); /* Need right packet number here */
	    spack('Y',n,0,0);		/* Say OK */
	    return('C');		/* Go to complete state */

	case 'E':			/* Error packet received */
	    prerrpkt(recpkt);		/* Print it out and */
	    return('A');		/* abort */

	case FALSE:			/* Didn't get packet */
	    spack('N',n,0,0);		/* Return a NAK */
	    return(state);		/* Keep trying */

	default:    return ('A');	/* Some other packet, "abort" */
    }
}


X/*
 *  r d a t a
 *
 *  Receive Data
 */

char rdata()
{
    int num, len;			/* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* "abort" if too many tries */

    switch(rpack(&len,&num,packet))	/* Get packet */
    {
	case 'D':			/* Got Data packet */
	    if (num != n)		/* Right packet? */
	    {				/* No */
		if (oldtry++ > MAXTRY)
		    return('A');	/* If too many tries, abort */
		if (num == ((n==0) ? 63:n-1)) /* Else check packet number */
		{			/* Previous packet again? */
		    spack('Y',num,6,packet); /* Yes, re-ACK it */
		    numtry = 0;		/* Reset try counter */
		    return(state);	/* Don't write out data! */
		}
		else return('A');	/* sorry, wrong number */
	    }
	    /* Got data with right packet number */
	    bufemp(packet,len);		/* Write the data to the file */
	    spack('Y',n,0,0);		/* Acknowledge the packet */
	    oldtry = numtry;		/* Reset the try counters */
	    numtry = 0;			/* ... */
	    n = (n+1)%64;		/* Bump packet number, mod 64 */
	    return('D');		/* Remain in data state */

	case 'F':			/* Got a File Header */
	    if (oldtry++ > MAXTRY)
		return('A');		/* If too many tries, "abort" */
	    if (num == ((n==0) ? 63:n-1)) /* Else check packet number */
	    {				/* It was the previous one */
		spack('Y',num,0,0);	/* ACK it again */
		numtry = 0;		/* Reset try counter */
		return(state);		/* Stay in Data state */
	    }
	    else return('A');		/* Not previous packet, "abort" */

	case 'Z':			/* End-Of-File */
	    if (num != n) return('A');	/* Must have right packet number */
	    spack('Y',n,0,0);		/* OK, ACK it. */
	    fclose(fp);			/* Close the file */
	    n = (n+1)%64;		/* Bump packet number */
	    return('F');		/* Go back to Receive File state */

	case 'E':			/* Error packet received */
	    prerrpkt(recpkt);		/* Print it out and */
	    return('A');		/* abort */

	case FALSE:			/* Didn't get packet */
	    spack('N',n,0,0);		/* Return a NAK */
	    return(state);		/* Keep trying */

	default:     return('A');	/* Some other packet, "abort" */
    }
}

X/*
 *  c o n n e c t
 *
 *  Establish a virtual terminal connection with the remote host, over an
 *  assigned tty line. 
 */

connect()
{
    int pid,				/* Holds process id of child */
	connected;			/* Boolean connect flag */
    char bel = '\07',
	c;

    struct sgttyb
	rawmode,			/* Controlling tty raw mode */
	cookedmode;			/* Controlling tty cooked mode */

    if (remote)				/* Nothing to connect to in remote */
    {					/* mode, so just return */
	printmsg("No line specified for connection.");
	return;
    }

    gtty(0,&cookedmode);		/* Save current mode so we can */
    gtty(0,&rawmode);			/* restore it later */
    rawmode.sg_flags |= (RAW|TANDEM);
    rawmode.sg_flags &= ~(ECHO|CRMOD);
    stty(0,&rawmode);			/* Put tty in raw mode */

    pid = fork();	    /* Start fork to get typeout from remote host */

    if (pid)			    /* Parent: send type-in to remote host */
    {
	printmsg("connected...\r");
	connected = TRUE;		/* Put us in "connect mode" */
	while (connected)
	{
	    read(0,&c,1);		/* Get a character */
	    if ((c&0177) == escchr)	/* Check for escape character */
	    {
		read(0,&c,1);
		if ((c&0177) == escchr)
		    write(ttyfd,&c,1);
		else
		switch (c&0177)
		{
		    case 'c':
		    case 'C':
			connected = FALSE;
			write(0,"\r\n",2);
			break;

		    case 'h':
		    case 'H':
			write(0,"\r\nYes, I'm still here...\r\n",26);
			break;

		    case 'b':
		    case 'B':
			genbrk();
			break;

		    case '?':
			write(0,"\r\nc - disconnect\r\nh - hello\r\nb - break\r\n",40);
			break;

		    default:
			write(0,&bel,1);
			break;
		}
	    }
	    else
	    {				/* If not escape charater, */
		write(ttyfd,&c,1);	/* write it out */
		c = NULL;		/* Nullify it (why?) */
	    }
	}
	kill(pid,9);			/* Done, kill the child */
	wait(0);			/* and bury him */
	stty(0,&cookedmode);		/* Restore tty mode */
	printmsg("disconnected.");
	return;				/* Done */
    }
    else		  /* Child does the reading from the remote host */
    {
	while(1)			/* Do this forever */
	{
	    read(ttyfd,&c,1);
	    write(1,&c,1);
	}
    }
}

X/*
 *	KERMIT utilities.
 */

clkint()				/* Timer interrupt handler */
{
    longjmp(env,TRUE);			/* Tell rpack to give up */
}


X/*
 *  s p a c k
 *
 *  Send a Packet
 */

spack(type,num,len,data)
char type, *data;
int num, len;
{
    int i;				/* Character loop counter */
    char chksum, buffer[100];		/* Checksum, packet buffer */
    register char *bufp;		/* Buffer pointer */

    if (debug>1)			/* Display outgoing packet */
    {
	if (data != NULL)
	    data[len] = '\0';		/* Null-terminate data to print it */
	printf("  spack type: %c\n",type);
	printf("	 num:  %d\n",num);
	printf("	 len:  %d\n",len);
	if (data != NULL)
	    printf("	    data: \"%s\"\n",data);
    }
  
    bufp = buffer;			/* Set up buffer pointer */
    for (i=1; i<=pad; i++) write(ttyfd,&padchar,1); /* Issue any padding */

    *bufp++ = SOH;			/* Packet marker, ASCII 1 (SOH) */
    *bufp++ = tochar(len+3);		/* Send the character count */
    chksum  = tochar(len+3);		/* Initialize the checksum */
    *bufp++ = tochar(num);		/* Packet number */
    chksum += tochar(num);		/* Update checksum */
    *bufp++ = type;			/* Packet type */
    chksum += type;			/* Update checksum */

    for (i=0; i<len; i++)		/* Loop for all data characters */
    {
	*bufp++ = data[i];		/* Get a character */
	chksum += data[i];		/* Update checksum */
    }
    chksum = (((chksum&0300) >> 6)+chksum)&077; /* Compute final checksum */
    *bufp++ = tochar(chksum);		/* Put it in the packet */
    *bufp = eol;			/* Extra-packet line terminator */
    write(ttyfd, buffer,bufp-buffer+1); /* Send the packet */
}

X/*
 *  r p a c k
 *
 *  Read a Packet
 */

rpack(len,num,data)
int *len, *num;				/* Packet length, number */
char *data;				/* Packet data */
{
    int i, done;			/* Data character number, loop exit */
    char t,				/* Current input character */
	type,				/* Packet type */
	cchksum,			/* Our (computed) checksum */
	rchksum;			/* Checksum received from other host */

#if UCB4X				/* TOPS-20 can't handle timeouts... */
    if (setjmp(env)) return FALSE;	/* Timed out, fail */
    signal(SIGALRM,clkint);		/* Setup the timeout */
    if ((timint > MAXTIM) || (timint < MINTIM)) timint = MYTIME;
    alarm(timint);
#endif /* UCB4X */

    while (t != SOH)			/* Wait for packet header */
    {
	read(ttyfd,&t,1);
	t &= 0177;			/* Handle parity */
    }

    done = FALSE;			/* Got SOH, init loop */
    while (!done)			/* Loop to get a packet */
    {
	read(ttyfd,&t,1);		/* Get character */
	if (!image) t &= 0177;		/* Handle parity */
	if (t == SOH) continue;		/* Resynchronize if SOH */
	cchksum = t;			/* Start the checksum */
	*len = unchar(t)-3;		/* Character count */

	read(ttyfd,&t,1);		/* Get character */
	if (!image) t &= 0177;		/* Handle parity */
	if (t == SOH) continue;		/* Resynchronize if SOH */
	cchksum = cchksum + t;		/* Update checksum */
	*num = unchar(t);		/* Packet number */

	read(ttyfd,&t,1);		/* Get character */
	if (!image) t &= 0177;		/* Handle parity */
	if (t == SOH) continue;		/* Resynchronize if SOH */
	cchksum = cchksum + t;		/* Update checksum */
	type = t;			/* Packet type */

	for (i=0; i<*len; i++)		/* The data itself, if any */
	{				/* Loop for character count */
	    read(ttyfd,&t,1);		/* Get character */
	    if (!image) t &= 0177;	/* Handle parity */
	    if (t == SOH) continue;	/* Resynch if SOH */
	    cchksum = cchksum + t;	/* Update checksum */
	    data[i] = t;		/* Put it in the data buffer */
	}
	data[*len] = 0;			/* Mark the end of the data */

	read(ttyfd,&t,1);		/* Get last character (checksum) */
	rchksum = unchar(t);		/* Convert to numeric */
	read(ttyfd,&t,1);		/* get EOL character and toss it */
	if (!image) t &= 0177;		/* Handle parity */
	if (t == SOH) continue;		/* Resynchronize if SOH */
	done = TRUE;			/* Got checksum, done */
    }

#if UCB4X
    alarm(0);				/* Disable the timer interrupt */
#endif

    if (debug>1)			/* Display incoming packet */
    {
	if (data != NULL)
	    data[*len] = '\0';		/* Null-terminate data to print it */
	printf("  rpack type: %c\n",type);
	printf("	 num:  %d\n",*num);
	printf("	 len:  %d\n",*len);
	if (data != NULL)
	    printf("	    data: \"%s\"\n",data);
    }
					/* Fold in bits 7,8 to compute */
    cchksum = (((cchksum&0300) >> 6)+cchksum)&077; /* final checksum */

    if (cchksum != rchksum) return(FALSE);

    return(type);			/* All OK, return packet type */
}


X/*
 *  b u f i l l
 *
 *  Get a bufferful of data from the file that's being sent.
 *  Only control-quoting is done; 8-bit & repeat count prefixes are
 *  not handled.
 */

bufill(buffer)
char buffer[];				/* Buffer */
{
    int i,				/* Loop index */
	t;				/* Char read from file */
    char t7;				/* 7-bit version of above */

    i = 0;				/* Init data buffer pointer */
    while((t = getc(fp)) != EOF)	/* Get the next character */
    {
	t7 = t & 0177;			/* Get low order 7 bits */

	if (t7 < SP || t7==DEL || t7==quote) /* Does this char require */
	{				    /* special handling? */
	    if (t=='\n' && !image)
	    {				/* Do LF->CRLF mapping if !image */
		buffer[i++] = quote;
		buffer[i++] = ctl('\r');
	    }
	    buffer[i++] = quote;	/* Quote the character */
	    if (t7 != quote)
	    {
		t = ctl(t);		/* and uncontrolify */
		t7 = ctl(t7);
	    }
	}
	if (image)
	    buffer[i++] = t;		/* Deposit the character itself */
	else
	    buffer[i++] = t7;

	if (i >= spsiz-8) return(i);	/* Check length */
    }
    if (i==0) return(EOF);		/* Wind up here only on EOF */
    return(i);				/* Handle partial buffer */
}


X/*
 *	b u f e m p
 *
 *  Put data from an incoming packet into a file.
 */

bufemp(buffer,len)
char  buffer[];				/* Buffer */
int   len;				/* Length */
{
    int i;				/* Counter */
    char t;				/* Character holder */

    for (i=0; i<len; i++)		/* Loop thru the data field */
    {
	t = buffer[i];			/* Get character */
	if (t == MYQUOTE)		/* Control quote? */
	{				/* Yes */
	    t = buffer[++i];		/* Get the quoted character */
	    if ((t & 0177) != MYQUOTE)	/* Low order bits match quote char? */
		t = ctl(t);		/* No, uncontrollify it */
	}
	if (t==CR && !image)		/* Don't pass CR if in image mode */
	    continue;

	putc(t,fp);
    }
}


X/*
 *  g n x t f l
 *
 *  Get next file in a file group
 */

gnxtfl()
{
    if (debug) printf("	  gnxtfl: filelist = \"%s\"\n",*filelist);
    filnam = *(filelist++);
    if (filecount-- == 0) return FALSE; /* If no more, fail */
    else return TRUE;			/* else succeed */
}


X/*
 *  s p a r
 *
 *  Fill the data array with my send-init parameters
 *
 */

spar(data)
char data[];
{
    data[0] = tochar(MAXPACKSIZ);	   /* Biggest packet I can receive */
    data[1] = tochar(MYTIME);		/* When I want to be timed out */
    data[2] = tochar(MYPAD);		/* How much padding I need */
    data[3] = ctl(MYPCHAR);		/* Padding character I want */
    data[4] = tochar(MYEOL);		/* End-Of-Line character I want */
    data[5] = MYQUOTE;			/* Control-Quote character I send */
}


X/*  r p a r
 *
 *  Get the other host's send-init parameters
 *
 */

rpar(data)
char data[];
{
    spsiz = unchar(data[0]);		/* Maximum send packet size */
    timint = unchar(data[1]);		/* When I should time out */
    pad = unchar(data[2]);		/* Number of pads to send */
    padchar = ctl(data[3]);		/* Padding character to send */
    eol = unchar(data[4]);		/* EOL character I must send */
    quote = data[5];			/* Incoming data quote character */
}
 

X/*
 *  f l u s h i n p u t
 *
 *  Dump all pending input to clear stacked up NACK's.
 *  (Implemented only for Berkeley Unix at this time).
 */

#if UCB4X&(~NO_FIONREAD)
flushinput()
{
    long int count;			/* Number of bytes ready to read */
    long int i;				/* Number of bytes to read in loop */

    ioctl(ttyfd, FIONREAD, &count);	/* See how many bytes pending read */
    if (!count) return;			/* If zero, then no input to flush */

    while (count)			/* Loop till all are flushed */
    {
	i = (count<sizeof(recpkt)) ?	/* Read min of count and size of */
	    count : sizeof(recpkt);	/*  the read buffer */
	read(ttyfd, recpkt, i);		/* Read a bunch */
	count -= i;			/* Subtract from amount to read */
    }
}
#else
flushinput()		/* Null version for non-Berkeley Unix */
{}
#endif /* UCB4X&(~FIONREAD) */


X/*
 *  Kermit printing routines:
 *
 *  usage - print command line options showing proper syntax
 *  printmsg -	like printf with "Kermit: " prepended
 *  error - like printmsg if local kermit; sends a error packet if remote
 *  prerrpkt - print contents of error packet received from remote host
 */

X/*
 *  u s a g e 
 *
 *  Print summary of usage info and quit
 */

usage()
{
#if UCB4X
    printf("Usage: kermit c[lbe line baud esc.char]	 (connect mode)\n");
    printf("or:	   kermit s[diflb line baud] file ...	 (send mode)\n");
    printf("or:	   kermit r[diflb line baud]		 (receive mode)\n");
#else
    printf("Usage: kermit c[le line esc.char]		 (connect mode)\n");
    printf("or:	   kermit s[difl line] file ...		 (send mode)\n");
    printf("or:	   kermit r[difl line]			 (receive mode)\n");
#endif
    exit(1);
}

X/*
 *  p r i n t m s g
 *
 *  Print message on standard output if not remote.
 */

X/*VARARGS1*/
printmsg(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
    if (!remote)
    {
	printf("Kermit: ");
	printf(fmt,a1,a2,a3,a4,a5);
	printf("\n");
	fflush(stdout);			/* force output (UTS needs it) */
    }
}

X/*
 *  e r r o r
 *
 *  Print error message.
 *
 *  If local, print error message with printmsg.
 *  If remote, send an error packet with the message.
 */

X/*VARARGS1*/
error(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
    char msg[80];
    int len;

    if (remote)
    {
	sprintf(msg,fmt,a1,a2,a3,a4,a5); /* Make it a string */
	len = strlen(msg);
	spack('E',n,len,msg);		/* Send the error packet */
    }
    else
	printmsg(fmt, a1, a2, a3, a4, a5);

    return;
}

X/*
 *  p r e r r p k t
 *
 *  Print contents of error packet received from remote host.
 */
prerrpkt(msg)
char *msg;
{
    printf("Kermit aborting with following error from remote host:\n%s\n",msg);
    return;
}


X/*
 * Send a break.
 */
genbrk()
{

	ioctl(ttyfd, TIOCSBRK, NULL);
	sleep(1);
	ioctl(ttyfd, TIOCCBRK, NULL);
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 640 kermit.c
	/bin/echo -n '	'; /bin/ls -ld kermit.c
fi
/bin/echo 'Extracting kermit.1c'
sed 's/^X//' <<'//go.sysin dd *' >kermit.1c
X.tr ~ \ 
X.TH KERMIT 1 local
X.SH NAME
kermit \- file transfer, virt. terminal over tty link
X.SH SYNOPSIS
X.BR "kermit c" [ lbe ]
X.RI [ line ]
X.RI [ baud ]
X.RI [ esc  ]
X.PP
X.BR "kermit r" [ ddilb ]
X.RI [ line ]
X.RI [ baud ]
X.PP
X.BR "kermit s" [ ddilb ]
X.RI [ line ]
X.RI [ baud ]
X.IR file ~...
X.SH DESCRIPTION
X.I Kermit
provides reliable file transfer and primitive virtual terminal communication
between machines.  It has been implemented on many different computers,
including microprocessors (see below).  The files transferred may be
arbitrary ASCII data (7-bit characters) and may be of any length.  The file
transfer protocol uses small (96 character) checksummed packets, with
ACK/NACK responses and timeouts.  \fIKermit\fR currently uses a five
second timeout and ten retries.
X.PP
The arguments to \fIkermit\fR are a set of flags (no spaces between the
flags), three optional args (which, if included, must be in the same
order as the flags which indicate their presence), and, if this is a
Send operation a list of one or more files.  (It is similar in some way
to the \fItar\fR command structure).
X.PP
X.I Kermit
has three modes, Connect, Send, and Receive.  The first is for a virtual
terminal connection, the other two for file transfer.  These modes are
specified by the first flag, which should be
X.BR c ,~ s ", or " r ,
respectively.  Exactly one mode must be specified.
X.PP
The \fBd\fR flag (debug) makes \fIkermit\fR a bit more verbose.  The
states \fIkermit\fR goes through are printed along with other traces of
it's operation.  A second \fBd\fR flag will cause \fIkermit\fR to give
an even more detailed trace.
X.PP
The \fBi\fR flag (image) allows slightly more efficient file transfer
between Unix machines.  Normally (on Kermits defined to run on Unix
systems) newline is mapped to CRLF on output, CR's are discarded on
input, and bytes are masked to 7 bits.  If this is set, no mapping is
done on newlines, and all eight bits of each byte are sent or received.
This is the default for non-Unix kermits.
X.PP
The \fBl\fR flag (line) specifies the tty line that \fIkermit\fR should
use to communicate with the other machine.  This is specified as a
regular filename, like "/dev/ttyh1".  If no \fBl\fR option is specified,
standard input is used and \fIkermit\fR assumes it is running on the
remote host (ie. NOT the machine to which your terminal is attached).
X.PP
The \fBb\fR flag (baud) sets the baud rate on the line specified by the
\fBl\fR flag.  No changes are made if the \fBb\fR flag is not used.
Legal speeds are: 110, 150, 300, 1200, 2400, 4800, 9600.  Note that this
version of \fIkermit\fR supports this option on Unix systems only.
X.PP
The \fBe\fR flag (escape) allows the user to set the first character of
the two character escape sequence for Connect mode.  When the escape
character is typed, \fIkermit\fR will hold it and wait for the next
character.  If the next character is \fBc\fR or \fBC\fR, \fIkermit\fR
will close the connection with the remote host.  If the second character
is the same as the escape character, the escape character itself is
passed.  Any character other than these two results in a bell being sent
to the user's terminal and no characters passwd to the remote host.  All
other typed characters are passed through unchanged.
The default escape character is '^'.
X.PP
The file arguments are only meaningful to a Send \fIkermit\fR.  The
Receiving \fIkermit\fR will attempt to store the file with the
same name that was used to send it.  Unix \fIkermit\fRs normally convert
outgoing file names to uppercase and incoming ones to lower case (see
the \fBf\fR flag).  If a filename contains a slash (/) all outgoing
kermits will strip off the leading part of the name through the last
slash.
X.SH EXAMPLE
For this example we will assume two Unix machines.  We are logged onto
"unixa" (the local machine), and want to communicate with "unixb" (the
remote machine).  There is a modem on "/dev/tty03".
X.PP
We want to connect to "unixb", then transfer "file1" to that machine.
X.sp 1
We type:   kermit clb /dev/tty03 1200
X.sp 1
Kermit answers:   Kermit: connected...
X.sp 1
Now we dial the remote machine and connect the modem.  Anything typed on
the terminal will be sent to the remote machine and any output from that
machine will be displayed on our terminal.  We hit RETURN, get a
"login:" prompt and login.
X.PP
Now we need to start a \fIkermit\fR on the remote machine so that we can
send the file over.  First we start up the remote, (in this case
receiving) \fIkermit\fR, then the local, (sending) one.  Remember that
we are talking to unixb right now.
X.PP
We type:   kermit r
X.br
	(there is now a Receive \fIkermit\fR on unixb)
X.PP
We type ^ (the escape character) and then \fBc\fR to kill the local
(Connecting) \fIkermit\fR.
X.PP
Kermit answers:   Kermit: disconnected.
X.PP
We type:   kermit slb /dev/tty03 1200 file1
X.PP
Kermit answers:	Sending file1 as FILE1
X.PP
When the transmission is finished, \fIkermit\fR will type either "Send
complete", or "Send failed.", depending on the success of the transfer.
If we now wanted to transfer a file from unixb (remote) to unixa
(local), we would use these commands:
X.PP
	kermit clb /dev/tty03 1200
X.br
	~~(connected to unixb)
X.br
	kermit s file9
X.br
	^c (up-arrow c not control-c)
X.br
	~~(talking to unixa again)
X.br
	kermit rl /dev/tty03 1200
X.PP
After all the transfers were done, we should connect again, log off
of unixb, kill the Connect \fIkermit\fR and hang up the phone.
X.PP
Detail on other implementations and on the protocol is given in
the \fIKermit Users Guide\fR, and the \fIKermit Protocol Handbook\fR.
X.SH FEATURES
\fIKermit\fR can interact strangely with the tty driver.  In particular,
a tty with "hangup on last close" set (stty hup), will reset to 300 Baud
between \fIkermit\fR commands.  It will also hang up a modem at that
time.  It is better to run with "stty -hup", and use "stty 0" to
explicitly hang up the modem.
X.PP
The KERMIT Protocol uses only printing ASCII characters,
Ctrl-A, and CRLF.  Ctrl-S/Ctrl-Q flow control can be used "underneath"
the Kermit protocol (TANDEM line discipline on Berkeley Unix).
X.PP
Since BREAK is not an ASCII character, \fIkermit\fR cannot send a BREAK
to the remote machine.  On some systems, a BREAK will be read as a NUL.
X.PP
This \fIkermit\fR does have timeouts when run under Unix, so the
protocol is stable when communicating with "dumb" kermits (that don't
have timeouts).
X.SH OTHER IMPLEMENTATIONS
X.I Kermits
have been written for TOPS-20, TOPS-10, IBM VM/CMS, Unix, VAX/VMS,
RT-11, MS-DOS, CP/M, and Apple DOS.  The Unix \fIkermit\fR in use
at Ford Aerospace has been tested on v6/PWB, v7, Onyx System III,
Bell System V, and Berkeley 4.1.  More information is given in the
\fIKermit Users Guide\fR.
X.SH SEE ALSO
stty(1)
X.PP
\fIKermit Users Guide\fR, Fourth Edition (4 May 83), Frank da Cruz,
Daphne Tzoar, Bill Catchings
X.PP
\fIKermit Protocol Manual\fR, Protocol Version 3 (29 April 83), Frank da Cruz,
Bill Catchings
X.PP
Both of the above documents are from the Columbia University Center for
Computing Activities, New York, New York, 10027.
X.SH AUTHORS
KERMIT kernel by Bill Catchings, Columbia University Center for Computing
Activities
X.PP
KERMIT-Unix adaptation by Chris Maio and Bob Cattani, Columbia University
Computer Science Dept.
X.PP
Local mods for v6, System III, and System V by Walter Underwood.  Includes
bug fixes from Jim Guyton at RAND-Unix.
X.SH DIAGNOSTICS
X.IP "cannot open \fIdevice\fR"
The file named in the \fIline\fR argument did not exist or had the wrong
permissions.
X.IP "bad line speed"
The \fIbaud\fR argument was not a legal speed.
X.IP "Could not create \fIfile\fR"
A Receive \fIkermit\fR could not create the file being sent to it.
X.IP "nothing to connect to"
A Connect \fIkermit\fR was started without a \fIline\fR argument.
X.SH BUGS AND CAVEATS
There is no locking on the use of the outgoing line.  Several users
could run \fIkermit\fR (or anything else) on the line simultaneously.
X.PP
The acronym (\fIK\fRL10 \fIE\fRrror-free \fIR\fReciprocal
\fIM\fRicro \fII\fRnterconnect over \fIT\fRTY lines) is charming,
but strained.
X.PP
This implementation does not send or process error-message packets.
X.PP
Eight-bit quoting is not implemented.

//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 440 kermit.1c
	/bin/echo -n '	'; /bin/ls -ld kermit.1c
fi
--------- Cut here also -----------