[net.sources] General Machine-Machine Transfer

rhbartels (06/04/82)

                                 SYSTEM OUTLINE
                                       by
                                J. C. Winterton

                    Machine to Machine Transmission Package
                                       at
                     Mathematics Faculty Computing Facility
                Copyright (c) 1982 by the University of Waterloo


     Outline            Machine to Machine Transmission      March 15, 1982


                               TABLE OF CONTENTS

              1. General.............................................     1
              2. Requirements........................................     1
                   2.1 Program address space.........................     1
                   2.2 Simplicity....................................     1
                   2.3 Specification.................................     2
              3. Capabilities........................................     2
                   3.1 Shell.........................................     2
                   3.2 Multiple Protocols............................     2
                   3.3 Unattended Operation..........................     2


     Draft - Subject to Change       - ii -          University of Waterloo

     Outline            Machine to Machine Transmission      March 15, 1982

          Current  and  projected numbers of computers and work stations in
     the University  and  the  existence  of  local  area  network  systems
     accessible  to  them  raises  the  need  for a straightforward program
     designed to allow the various machines on campus to  communicate  with
     each other.  Communication over the local networks can be either under
     control of a terminal attached to one of the machines in  a  conversa-
     tion  or  of  a system daemon running on at least one of the machines.
     This document  presents  the  specification  for  such  a  program  in
     outline.

          This section discusses the requirements of the  program  as  they
     are known and understood at this date.

          Since  it  is  expected  that  the program will usually be run in
     restricted address space in a work station or personal  micro-computer
     running  something  like  CP/M,  the  program  address  space is quite
     limited.  It is doubtful that most machines actually running CP/M will
     have  more  than, say, 64K bytes.  Since 8K is taken up by the monitor
     and I/O system, there is an upper limit of 56K Bytes on  the  program.
     The  program  size  target should be considerably smaller than this to
     allow for imponderables.  At this time, the target is 32K Bytes.

          It is felt that this will also be a benefit it larger  multi-user
     machines,  but  considering the size of some libraries (e.g. TSS Blib)
     it may not be possible to meet this target in these cases.

          It is not expected that a  program  will  be  supplied  for  each
     machine  in  existence.   An  implementation will be done for UNIX[1],
     TSS, GCOS6 and, perhaps, THOTH.  The  source  code  and  specification
     will  be  supplied  to  anyone else wanting to implement this program.
     Because of this stand, it is necessary that simplicity  of  algorithms
     be   of   higher   priority   than   efficiency   so  that  relatively
     unsophisticated users may successfully implement the  program.   Hope-
     fully,  like  music  by  J.S.Bach,  this program will be writeable and
     compilable in whatever reasonable language is available on the  target
     machine. [2]

     __________________
     [1] UNIX is a Trademark of Bell Laboratories.
     [2] Bach wrote non-specifically and most of his works can be
     played on whatever instruments are available.

     Draft - Subject to Change       - 1 -           University of Waterloo

     Outline            Machine to Machine Transmission      March 15, 1982

          Where  data  are to be encoded, they will be converted from 8 bit
     representation to hexadecimal.  Data compression is provided  whenever
     two implimentations of this program are in communication.

          The  specification  of  this  program  must  be  as  complete  as
     possible.  Pseudo code or some similar representation  shall  be  used
     where  logic is complex.  At least one of the implementations prepared
     by us shall be wholly from this specification.

          The following are the capabilities that are foreseen an necessary
     in the program.

          The term shell is used in the UNIX sense.  The program acts as  a
     link  between  the user and the network session.  It is able to accept
     commands to be executed:

          (a)  By the host machine;

          (b)  By the network (if possible); and

          (c)  By the remote host machine.

          The host/remote host commands in the repertoire  of  the  program
     include redirection commands to facilitate file transfers.

          Multiple  protocols  in the style of the UNIX uucp system are not
     envisaged.  Where two copies of the program are  able  to  co-operate,
     8-bit  data  may  be encoded and sent.  The program should be built to
     handle only a seven bit data path.  Where only one copy of the program
     is  active  in  the circuit, data may very well be restricted to ascii
     graphics.

          Unattended operation of the program  is  desirable  where  it  is
     under  the  control  of  a daemon such as the UNIX cron.  It should be
     possible to set up a list of machines to  be  polled  for  work  on  a
     "spooled"  basis  so  that  lengthy  transmissions may be accomplished
     without the need for an  "operator"  at  a  terminal  controlling  the
     program.   This  is on all fours with the present UNIX uucp capability
     which has served as a model for much of this design.

     Draft - Subject to Change       - 2 -           University of Waterloo

---------------------------------------------------------------------------
		       Brief On-Line Documentation
---------------------------------------------------------------------------
MMX - machine to machine transfer program.

Syntax:
     MMX [SLave] [l=linename] [s=baudrate]

Description:
     This C program transfers  files  over  a  communication
line  in  a  secure manner.  Checksumming and other controls
are present as well as data compression.  A 7-bit data  path
is  assumed, so that the use of "transparent" or "raw" modes
on communication lines is avoided.
     The  program  operates  in two modes: MASTER and SLAVE.
Operation in the MASTER mode implies that commands  will  be
given  from the standard input device and that communication
with the network will be over a separate device.  SLAVE mode
implies  that  the  communications network is identical with
the standard input/output device.  Unless and until the host
system  has  the  ability  to attach separate communications
lines to programs, the program must  be  operated  in  SLAVE
mode.
     Currently,  Honeywell  TSS   cannot   attach   separate
communication  lines,  and therefore, it is necessary to use
SLAVE mode on that machine.  The Math UNIX machine,  on  the
other  hand,  is  perfectly  capable  of operating in either
mode.

Options:
   SLave
       Causes the program to issue internal !SLave and !Open
       command on start up.
   l=linename
       In   machines   that  possess  the  capability,  this
       parameter sets a  string  to  be  used  by  the  open
       command for the separate communication line.
   s=baudrate
       In   machines   that  possess  the  capability,  this
       parameter allows the program to set the baud rate  of
       the line.

Defaults:
     Defaults are set in each machine for the  options.   In
the  current  version  for  Honeywell  TSS,  SLave is forced
during startup.  In the math UNIX machine the defaults  are:
MAster, l=/dev/sytek, s=2400.

Program Operation:
     When called, the program operates as a  minor  "shell".
The  initiating  end  of  the  conversation  operates as the
master (in MASTER mode) and the responding end as SLAVE.  It
accepts  commands  and  acts  upon  them as described below.
Commands  are  prefixed  by  the  command  escape  character
exclamation-point   (!).    When   an  exclamation-point  is
recognized at the beginning of a line, the  line  is  parsed
and  validated  commands  obeyed.   When  an error occurs, a
descriptive  error  message  appears.    When   the   second
character  of  a command line is also a command escape, then
the line is sent to the other program stripped of the  first
escape  (!).   There  is  no escape sequence to issue system
commands from within this program.


Commands:

   Open 
       Condition the communication line as necessary.   This
       command  must be issued to both copies of the program
       before  any  transfer  can  take  place.   Note  that
       calling  the  program  with the SLave option performs
       this  function   automatically   for   the   standard
       input/output device.

   Send localfile remotefile
       Transmit file localfile from the master to remotefile
       at  the  slave.   Correct  pathnames  and formats for
       pathnames are the responsibility of the user.

   Get remotefile localfile
       Transmit  the  file  remotefile  from  the  slave  to
       localfile at the  master.   Pathnames  are  the  full
       responsibility of the user.

   MaxData
       Sets the  maximum  number  of  data  bytes  from  the
       sending  file  that  will be passed in any one packet
       during file transmission.

       It is necessary to limit this artificially due to the
       fact  that  some  systems  are line oriented and have
       limited  line   sizes   (Honeywell   TSS).    MaxData
       represents  a  count of bytes that will be taken from
       the file being  sent.   Since  a  four  bit  encoding
       techinique   is   used,   each   eight  bit  byte  is
       transmitted as two graphic characters.   This  expan-
       sion  is  alleviated  somewhat by also employing data
       compression techinques where the number of  identical
       characters  exceeds two.  There are ten (10) overhead
       characters on  a  data  packet  in  addition  to  the
       encoded bytes.  Default is 60 bytes.

   Quit 
       Stops  the  program.   In  the  UNIX  environment,  a
       control-D (EOT, D) may be sent as a command to effect
       a  wrapup  of  the  program,  but  the   command   is
       available.

   MAster
       If the program is in slave state,  switch  to  master
       state.

   SLave
       If the program is in master state,  switch  to  slave
       state.

   Remote
       Inform the program of the type of system that  is  at
       the  other end of the conversation.  This feature has
       not been useful to date, and may be removed.

   Viewsetup
       Display the controlling parameters of the program.

   Continue
       Because many systems do not prompt, end of  transmis-
       sion  over  the  communication  line is detected by a
       time out heuristic.  The command is provided to cause
       the  program  to  re-examine  the  communication line
       buffers where it is believed that more  data  may  be
       present.   This  is  useful  when  system are heavily
       loaded and multiple line output may be sent with more
       than four seconds between data bursts.

   Zdebug
       Some versions of this program (notably the UNIX  ver-
       sion) contain debugging code activated by the command
       "!Zdebug on" and deactivated by "!Zdebug off".   This
       code may be removed when not required or impractical.


(Copyright (c) 1982, University of Waterloo)
------------------------------------------------------------

                     DATA STRUCTURES FOR FILE TRANSMISSION
                                       by
                                J. C. Winterton

                    Machine to Machine Transmission Package
                                       at
                     Mathematics Faculty Computing Facility

                Copyright (c) 1982 by the University of Waterloo

     Formats            Machine to Machine Transmission      April 29, 1982

                               TABLE OF CONTENTS

              1. General..................................................1
              2. Data Packet..............................................1
                   2.1 Data Compression Scheme............................1
                   2.2 Processing the Data packet.........................2
              3. Beginning of File Packet.................................2
                   3.1 Beginning of File Packet Processing................2
              4. End of File Packet.......................................3
                   4.1 End of File Packet Processing......................3
              5. Hand Shake Packet........................................3
              6. Warm Feelings............................................4

     Draft - Subject to Change       - ii -          University of Waterloo

     Formats            Machine to Machine Transmission      April 29, 1982

     1. General.

          This document contains the data structures necessary  to  operate
     the  Machine to Machine Transmission program.  All of these structures
     are effectively character arrays (strings).   In  this  design  it  is
     considered that only a 7-bit data path is available.

          When  files  are being transmitted, all data is packetized in the
     format shown below.  Two other packet structures are necessary, namely
     a  beginning  of file packet and an end of file packet.  An additional
     packet is defined for the activation/deactivation of data compression.
     For  convenience  of  description,  PL/I  is  used  as the description
     language.  Because it is believed that many of the  operating  systems
     involved  consider  the  ASCII  control  characters  as proprietary to
     themselves, use of these characters has been avoided.

     2. Data Packet.

          The data packet shown below is used to transfer

           dcl 1 data_packet unaligned,
                2 d_soh char init ('|'),
                2 check_sum char(4),                     /* modulo 10000 */
                2 data_length char(3),                          /* <=250 */
                2 d_stx char init ('{'),
                2 data_bytes(data_length) char,       /* data characters */
                2 data_terminator char;
                                           /* '~' if incomplete "record" */
                                               /* '}' if end of "record" */
     2.1 Data Compression Scheme.

          Data  compression  is  a feature of this protocol.  The technique
     employed is:

          Scan the uncompressed  data  for  occurances  of  three  or  more
          occurances  of  the  same  character.   When  such are found, the
          packet encoding routine translates them into GS-count-hh (4 ASCII
          characters).[1]
          Count is  the  binary  number  of  characters  to  be  propagated
          expressed  as  an  ASCII  graphic  character  by  adding  a space
          ('0100000'b).  This technique limits  the  compression  range  to
          '0000011'b  through  '0011010'b  (3 through 26) in order to avoid
          entering control characters and key signal characters  improperly
          in the text.
          hh is the  hexadecimal  representation  of  the  character  being
          compressed.

     ___________________________
     [1] GS is the ASCII character '!'.


     Draft - Subject to Change       - 1 -           University of Waterloo

     Formats            Machine to Machine Transmission      April 29, 1982

          This  scheme  will  only be invoked when co-operating versions of
     the program are in contact.  If  the  identity  handshake  fails  when
     transmission begins, the data are transmitted uncompressed.

     2.2 Processing the Data packet.

          The  data  of Figure 1 are encoded in ASCII hexadecimal notation,
     two characters per byte for transmission.  On transmission, the  final
     byte pair is followed by either an ASCII '}' or '~' character [2] and,
     if necessary, a new-line character (\n) as  appropriate  to  the  host
     system.   In  most cases, the '}' will be used as a packet terminator.
     However, when it is considered important to pass data in  "blocks"  or
     "records",  '~' is used at the end of each packet of a block until the
     last, when '}' is used to indicate to the receiving  program  that  an
     output write may now  be  accomplished.   [3]  The  receiving  program
     decodes  the packet, and if found correct an ASCII 'y' character and a
     newline are returned.  On receipt of  the  'y',  the  sending  program
     sends the next packet.  If an error is detected, the receiving program
     returns a sequence consisting of an ASCII 'n' followed  by  a  newline
     character.  On receipt of the 'n', the sending program will retransmit
     the packet.  The sending program shall attempt to send each  packet  a
     minimum  of  five (5) times.  If receipt of the packet is not possible
     after the retry limit, the programs should wrap up gracefully.

     3. Beginning of File Packet.

          When it is desired  to  transmit  a  file  between  two  machines
     running  this  program,  the  fact is announced by the sending machine
     with the transmission of a Beginning of File Packet Figure 2.

     3.1 Beginning of File Packet Processing.

          This packet is not encoded, but sent as plain ASCII text followed
     by  a  newline  character.   Pathnames  are  the  resonsibility of the
     sending program.  Retry processing is the same for this packet as  for
     a data packet with the following exceptions:

          (a) The   sending   program   will  open  the  file  to  be  sent
          (send_pathname) before sending this packet.  If the  file  cannot
          be opened, appropriate error action shall be taken.

          (b) On  receipt  of this packet, in addition to checksumming, the
          receiving program will attempt to open/create the receiving file.
          An 'n' shall be sent if this fails.

     ___________________________
     [2] This restricts the operation of  Hazeltine  2000  series
     equipment  to  having  the escape character set to the ASCII
     ESC ('0011011'b).
     [3]  A  record is an arbitrary division of text which may be
     considered to be sent as a unit.  In some computer  systems,
     it may be a logical record.

     Draft - Subject to Change       - 2 -           University of Waterloo

     Formats            Machine to Machine Transmission      April 29, 1982
           
	   dcl 1 beginning_of_file_packet unaligned,
                2 bof_indicator char(5) init ('|FILE'),
                2 bof_direction char(1),
                                                  /* 1 = slave_to_master */
                                                  /* 2 = master_to_slave */
                2 bof_checksum char(4),                  /* modulo 10000 */
                2 bof_stx char init ('{'),
                2 send_pathname_size char(3), /* length of send-pathname */
                2 receive_pathname_size char(3),
                                           /* length of receive_pathname */
                2 send_pathname(send_pathname_size) char,
                                                        /* send_pathname */
                2 receive_pathname(receive_pathname_size) char,
                                                     /* receive_pathname */
                2 bof_terminator char init ('}');

                       Figure 2. Beginning of File Packet


     4. End of File Packet.

          When  all  the  data  of a file has been transferred, the sending
     program sends an End of File Packet Figure 3.

           dcl 1 end_of_file_packet unaligned,
                2 eof_heading char(4) ('|EOF'),
                2 eof_stx char init ('{'),
                2 eof_length char (3),           /* length of next field */
                2 eof_sending_name(eof_length) char,
                                        /* send_pathname from bof packet */
                2 eof_terminator char init ('}');


                          Figure 3. End of File Packet

     4.1 End of File Packet Processing.

          This  packet  is  sent  in plain ASCII text followed by a newline
     with the retry protocol of the data packet.  Once  the  processing  of
     this  packet  is  completed, the files are closed at both ends and the
     program returns to manual or shell mode to accept new commands.

     5. Hand Shake Packet.

          This packet is sent by the sending program before the start of  a
     file  transfer.   If it is returned by the receiving program, the data
     compression algorithm is activated.  If  the  packet  is  acknowledged
     improperly, data compression is disengaged for the transfer.

     Draft - Subject to Change       - 3 -           University of Waterloo

     Formats            Machine to Machine Transmission      April 29, 1982

           dcl 1 hand_shake_packet unaligned,
                2 hs_soh char init ('|'),
                2 hs_dum char(12) init ('!$Compress$!'),
                2 hs_stx char init ('{'),
                2 hs_etx char init ('}');

                          Figure 4. Hand Shake Packet

     6. Warm Feelings.

          Since  this  program  is  probably  operated  under  control of a
     terminal attached to an operator, it is sensible to send out some kind
     of reassuring message every five or six seconds.  It is suggested that
     this "warm feelings" message should be brief.  It has  been  suggested
     that the output could be the number of characters or packets sent.  If
     it is known that the operator  terminal  is  a  CRT  with  addressable
     cursor, an update in place would be desireable.

     Draft - Subject to Change       - 4 -           University of Waterloo
---------------------------------------------------------------------------
		 HERE      COME     DE     CODE
---------------------------------------------------------------------------
/* This program was developed by John C. Winterton at the University of
   Waterloo using UNIX 4.1BSD under an educational licence.  The program
   is made available without support or warranty of any kind implicit
   or explicit.
   
   The purpose of the program is to communicate over a seven bit data
   path with a similarly working copy of itself for the purpose of securely
   transfering files from machine to machine.  This version contains specific
   code for the version of the UNIX operating system on which it was
   developed.  It further contains some code aimed at communicating with
   a Honeywell TSS machine.
   
   The subroutine _abbrv is the work of Kevin P. Martin, and works well
   indeed.  The other stuff is the author's, who takes the blame but
   but no responsibility.						*/

/*	manifests for the mmx package					*/

#ifdef unix
#include <stdio.h>
#include <sgtty.h>
#include <ascii.h>
#define SPEEDEFAULT B2400		/* default baud rate		*/
#define LINEDEFAULT "/dev/sytek"	/* default comm line file	*/
#define LINETTY     "/dev/tty"		/* name of login tty		*/
#endif

#define BUFFSIZE 1024			/* length of keyboard buffers	*/
#define MAXSTRING 1024			/* length of general strings	*/
#define PACKSIZE 1024			/* length of packet buffers	*/
#define DEF_LENGTH 60			/* default data length in pkts	*/

#define CL 017				/* four bit mask for lower char	*/
#define CU 0360				/* four bit mask for upper char	*/
#define CM 0177				/* 7    bit mask for input char	*/

#define HEXUPPER 0x30			/* hex encodeing upper bits	*/
#define HEXFIRST HEXUPPER 		/* '0'				*/
#define HEXLAST  0x3f			/* '?'				*/
#define HEXERROR -2			/* error signal for conversion	*/

#define PROGESC '!'			/* escape character for commands*/

#define DISCONNECTED 0			/* state of communication line	*/
#define CONNECTED    1

#define MASTERPROMPT "\n>"		/* prompt for master state	*/
#define SLAVEPROMPT  "?"		/* prompt for slave state	*/


	/* manifests for ourcmd's line parsing */

#define STRING		0
#define OPENLINE	1
#define SENDFILE	2
#define	GETFILE		3
#define MAXLINE		4
#define FINISHED	5
#define MASTER		6
#define SLAVE		7
#define REMOTE		8
#define VIEWSETUP	9
#define CONTINUE	10
#define ZDEBUG		11

	/* manifests for the promptable (system names) */
	
#define SYTEK		1
#define LOCALNET	2
#define	UNIX		3
#define	GCOS		4
#define MOD400		5
#define THOTH		6

	/* manifests for packet transmission (special chars) */

#define X_SOH		'|'	/* pseudo start of header		*/
#define X_STX		'{'	/* pseudo start of text			*/
#define X_ETX		'}'	/* pseudo end of text			*/
#define X_ETB		'~'	/* pseudo end of text block		*/
#define X_GS		'!'	/* pseudo group separator (comprssion	*/

	/* manifests for character offsets into decoded packet blocks	*/

/* data packet */

#define D_SOH		0
#define D_CKSUM		1
#define D_DLENGTH	5
#define D_STX		8
#define D_BYTES		9

/* begin file packet */

#define F_SOH		0
#define F_FILE		1
#define F_DIRECTION	5
#define F_CKSUM		6
#define F_STX		10
#define F_SENDLENGTH	11
#define F_RECVLENGTH	14
#define F_SENDPATH	17

/* end file packet */

#define E_SOH		0
#define E_EOF		1
#define E_STX		4
#define E_EOFLENGTH	5
#define E_SENDPATH	8

/* values for F_DIRECTION */
#define TO_MASTER	'1'
#define TO_SLAVE	'2'

/* for direction of motion in the data packet processor */
#define NONE		0
#define IN		1
#define OUT		2

#define ERR_LIMIT	5	/* transmission error limit */
#define COM_LIMIT	26	/* maximum number of chars for compression */
#define WARM_FEEL	10	/* number of packets to pass before output
				   of warm feelings indicator to MASTER    */
/*
 * _abbrv( pat, str )
 *
 * Perform match against string pattern.
 * The pattern characters which are lowercase are optional.
 * This routine recurs to a depth of n, where n is the number of optional
 * characters provided in the match string.
 *
 */

int _abbrv( pat, str )
char *str, *pat;
{
	char c;
	/*
	 * Neither reqd nor patc is used without setting after the recursion,
	 * so they can be static to save stack space.
	 */
	static int reqd;
	static char patc;

	for(;;) {	/* each target character */
		/*
		 * Pick up the next character to be matched.
		 * and uppercase it.
		 */
		c = *(str++);
		if( c >= 'a' && c <= 'z' )
			c &= ~040;

		for(;;) {	/* each pattern character */
			patc = *(pat++);

			/*
			 * Is it required (non-lowercase, including null) ?
			 */
			reqd = patc < 'a' || patc > 'z';

			/*
			 * Compare uppercased pattern char with uppercased
			 * target char.
			 */
			if( (reqd ? patc : patc & ~040) == c ) {
				/*
				 * We have a hit. If the character was required,
				 * continue along the target string.
				 */
				if( reqd )
					break;

				/*
				 * Otherwise, recursively look down the string.
				 * If the remainder of the string matches the
				 * remainder of the pattern, all is matched.
				 */
				if( _abbrv( pat, str ) )
					return( 1 );

				/*
				 * If the remainder of the string and the rest
				 * of the pattern did not match, try skipping
				 * this (optional) pattern character,
				 * and find the next.
				 */
			}
			else {
				/*
				 * Didn't match. If it was required, fail.
				 * else try next pattern character.
				 */
				if( reqd )
					return( 0 );
			}
			/*
			 * Loop back to next pattern character (on same
			 * target character).
			 */
		}

		/*
		 * We matched a required character... Was it a null?
		 * (end of pattern)
		 */
		if( c == '\0' )
			return( 1 );
		/*
		 * Loop back to next target string character and next pattern
		 * character.
		 */
	}
}
/*		convert input 8-bit  character to hexadecimal	*/

#include "manifs.h"

char *a_to_hex(cin, cout)
char cin, *cout;
{
	cout[0] = ((cin >> 4) & CL) | HEXUPPER;
	cout[1] = (cin & CL) | HEXUPPER;
	return (cout);
}
/*	output the appropriate ack	*/

#include "manifs.h"

int ack()

{
	sendline("y\n",2);
	return(1);
}
		/* NON-PORTABLE	- UCB UNIX 4.X */

/* this routine uses an ioctl to see if there are any characters in the
	input stream.
	It returns the number of chars if there are any.		*/
   
#include "manifs.h"

long ccount(stream)
FILE *stream;
{

	extern char prtab[];
	extern int prompt, data_present;
	
	long nchars;
	unsigned sleeptime;
	
	ioctl(fileno(stream), FIONREAD, &nchars);
	nchars += (long) stream->_cnt;

	while (nchars == 0)
	{
		if (prtab[prompt] != DEL)
			return(nchars);
		for (sleeptime = 1; sleeptime <= 16; sleeptime *= 2)
		{
			ioctl(fileno(stream), FIONREAD, &nchars);
			nchars += (long) stream->_cnt;
			if (nchars != 0)
				return(nchars);
			if (sleeptime > 2 && data_present)
				return(nchars);
			if (sleeptime == 8)
				message ("Wait");
			if (sleeptime > 8)
				message (".");
			sleep(sleeptime);
		}
		break;
	}

	return(nchars);
}
/*	checksum from a given X_STX to either a X_ETX or X_ETB exclusive */

#include "manifs.h"

unsigned checksum(p)
char *p;

{		/* p->X_STX */

	int c, i;
	unsigned sum;


	if (p[0] != X_STX)
		return (0);	/* error */
	sum = 0;
	for (i = 1; ; ++i)
	{
		c = (p[i]) & CM;
		switch(c)
		{
		default:
			if (c < ' ' || c > '~')
				return(0);	/* non-packet char */
			sum += c;
			continue;
		case X_ETX:
		case X_ETB:
			return(sum);
		}
	}
}
/*	process incoming packet in indicated buffer	*/

#include "manifs.h"

int dopacket(ptr, len)
char ptr[];
int len;
{
	extern char handshake[];
	extern int errcount, compress;
	
	char c;
	int i;
	
	c = ptr[1];	/*	get first char of header	*/
	switch(c)	/*	and identify packet		*/
	{
	case '!':
		i = strncmp(ptr, handshake, strlen(handshake) - 1);
		if (i == 0)
		{
			compress = 1;
			return(ack());
		}
		message("Compression request garbled - quitting.\n");
		nack();
		return(2);	/* transfer request fails */
	case 'F':
		if (filepacket(ptr, len))
			return(ack());
		return(nack());
	case 'E':
		if(eofpacket(ptr, len))
		{
			ack();
			return(2);
		}
		return(nack());
	default:
		if (c >= '0' && c <= '9')
			if(xferpacket( ptr, len))
			{
				errcount = 0;
				return(ack());
			}
		if ( (++errcount) > ERR_LIMIT )
		{
			message ("Too many errors - quitting.\n");
			nack();
			return(2);
		}
		return(nack());
	}
}
/*	process incoming eof packet	*/

#include "manifs.h"

int eofpacket(ptr, len)
char ptr[];
int len;
{
	extern FILE *xfer_file;
	extern int filecmd;

	filecmd = NONE;
	fclose(xfer_file);
	return(1);
}
	/* NON PORTABLE - UCB UNIX 4.X */

/*	externals for mmx program					*/

#include "manifs.h"

FILE *in_line = 0;		/*	comm line input unit		*/
FILE *out_line = 0;		/*	comm line output unit		*/
FILE *xfer_file;		/*	for current file transfer	*/
int filecmd = NONE;		/*	direction indicator for xfer	*/
struct sgttyb com_line;		/*	comm line parameters from gtty	*/
struct sgttyb com_save;		/*	copy of com_line for wrapup	*/
struct tchars com_1;		/*	from ioctl for signals		*/
struct tchars com_1s;		/*	copy of com_1 for wrapup	*/
struct ltchars com_2;		/*	from ioctl for other signals	*/
struct ltchars com_2s;		/*	copy of com_2 for wrapup	*/

unsigned zdebug = 0;		/*	debug control			*/
int on_line = 0;		/*	flag set by setup if /dev/tty	*/
				/*	really is a terminal		*/

char syntax[] = {
	"Use:mmx [slave] [l=linename] [s=baudrate]\n"
};
char fileformat[] = 		/*	constant used in inwards	*/
	{"%03d%03d%s%s%c"};

char callname[20];		/*	name for the message routine	*/

char handshake[] = {
	"|!$Compress$!{}\n"	/*	data compression handshake	*/
};

char linebuf[BUFFSIZE];		/*	keyboard input buffer		*/

char packet[PACKSIZE];		/*	data packet			*/

int eofsw = 0;			/*	additional control for loadline	*/

char file_local[MAXSTRING];	/*	name of local transfer file	*/
char file_remote[MAXSTRING];	/*	name of remote tranfer file	*/

	/*	Table of _abbrv patterns for the main ourcmd processor	*/
	
	/* note: The zeroth and last entry of all tables of this type
		 are set to null strings for the benefit of the identify()
		 routine						*/

char *optab[] = {
	"",			/*	zero'th one is always null	*/
	"Open",			/*	open the line			*/
	"Send",			/*	send infile outfile		*/
	"Get",			/*	get infile outfile		*/
	"MaxData",		/*	maxdata nnn			*/
	"Quit",			/*	quit				*/
	"MAster",		/*	enter master state (default)	*/
	"SLave",		/*	enter slave state		*/
	"Remote",		/*	request new remote set up	*/
	"Viewsetup",		/*	display remote set up		*/
	"Continue",		/*	read the comm_line send nothing	*/
	"Zdebug",		/*	zdebug on|off			*/
	""			/*	last one is always null		*/
};

	/*	This collection of variables contains the program state	*/

int progstate = MASTER;		/*	current state of things		*/
int linestate = DISCONNECTED;	/*	current state of comm line	*/
int speed;			/*	line speed 			*/
int errcount = 0;		/*	error count for packet xmit	*/
int compress = 0;		/*	data compression enable switch	*/
int maxline = DEF_LENGTH;	/*	default maximum transmission	*/
int newline = SYTEK;		/*	settable newline for outgoing	*/
int prompt  = SYTEK;		/*	termination style of input	*/
int remote = SYTEK;		/*	comm line device type		*/
int data_present = 0;		/*	for readline routine		*/
char line_name[MAXSTRING];	/*	comm line name string		*/
char line_save[MAXSTRING];	/*	copy of line_name (MASTER)	*/
char in_prompt[5]		/*	prompt string at terminal	*/
	= {MASTERPROMPT};	/*	default for starting up		*/

	/*	The entries in the following three tables bear a one-to-one
		relation to each other as follows:
		
		prtable is the table of known system names for _abbrv
			processing by the !remote request;
		prtab entries indicate the prompt character for the
			corresponding system;
		nltab entries contain the chracter that the relevant system
			accepts as a <newline> or <end transmission>	*/

char *prtable[] = {

	"",
	"Sytek",
	"Localnet",
	"Unix",
	"Gcos8",
	"Mod400",
	"Thoth",
	""
};

	/* note: in prtab an entry of DEL (\0177) indicates a system with
		no particular known prompt. Serviced by timeout		*/

char prtab[] = {
	NUL,
	DEL,		/*	sytek				*/
	DEL,		/*	localnet			*/
	DEL,		/*	unix				*/
	DEL,		/*	control-q for the bun		*/
	DEL,		/*	Mod400 (GCOS6)			*/
	DEL,		/*	thoth(?)			*/
	NUL
};
char nltab[] = {
	NUL,
	CR,			/*	sytek				*/
	CR,			/*	localnet			*/
	CR,			/*	unix				*/
	CR,			/*	gcos8				*/
	CR,			/*	Mod400				*/
	CR,			/*	thoth(?)			*/
	NUL
};
/*	routine to packetize and send an input file	*/

#include "manifs.h"

int fileout()

{
	extern FILE *xfer_file;
	extern char packet[], linebuf[], file_local[];
	extern int compress, maxline, errcount, progstate;

	int lx, c, pkt_cnt;

	c = lx = pkt_cnt = 0;
	while ( (c = getc(xfer_file)) != EOF)
	{
		linebuf[lx++] = c;
		if (lx < maxline)
			continue;
		outdata(linebuf, lx);
		if (errcount >= ERR_LIMIT)
			return(0);
		if ( (progstate == MASTER) &&
			( (++pkt_cnt % WARM_FEEL) == 0) )
				message(".");
		lx = 0;
	}
	if (lx)
	{
		outdata(linebuf, lx);
		if (errcount >= ERR_LIMIT)
			return(0);
	}
	sprintf(packet, "|EOF{%03d%s}", strlen(file_local), file_local);
	for (errcount = 0; errcount < ERR_LIMIT; errcount++)
		if (sendpacket(packet))
			break;
	
	return(errcount < ERR_LIMIT);
}
/*	process an incoming file header packet		*/

#include "manifs.h"

int filepacket(ptr, len)
char ptr[];
int len;
{
	extern FILE *xfer_file;
	extern char file_local[];
	extern int progstate, filecmd;
	extern unsigned checksum();

	char filename[160];
	int i, s_length, r_length, limit;
	unsigned cksum, value;
	char *pick;

	value = checksum(&(ptr[F_STX]));
	i = sscanf(ptr, "|FILE%*c%4d{%3d%3d", &cksum, &s_length, &r_length);
	if (i < 3)
		return(0);		/* error on packet */
	if (value != cksum)
		return(0);

	switch(progstate)
	{
	case MASTER:
		switch (ptr[F_DIRECTION])
		{
		case TO_MASTER:
			pick = &(ptr[F_SENDPATH + s_length]);
			limit = r_length;
			break;
		case TO_SLAVE:
			pick = &(ptr[F_SENDPATH]);
			limit = s_length;
			break;
		default:
			return(0);
		}
		break;
	case SLAVE:
		switch (ptr[F_DIRECTION])
		{
		case TO_SLAVE:
			pick = &(ptr[F_SENDPATH + s_length]);
			limit = r_length;
			break;
		case TO_MASTER:
			pick = &(ptr[F_SENDPATH]);
			limit = s_length;
			break;
		default:
			return(0);
		}
		break;
	default:
		message("Program in an impossible state in filepacket\n");
		message("Dumping");
		kill(getpid(), 9);
	}

	for (i = 0; i < limit; ++i)
		filename[i] = pick[i];
	filename[i] = NUL;

	filecmd = IN;	
	if (progstate == MASTER)
		if (ptr[F_DIRECTION] == TO_MASTER)
		{
			filecmd   = OUT;
			xfer_file = fopen(filename, "w");
		}
		else
			xfer_file = fopen(filename, "r");
	else	/* state is SLAVE */
		if (ptr[F_DIRECTION] == TO_SLAVE)
		{
			filecmd = OUT;
			xfer_file = fopen(filename, "w");
		}
		else
			xfer_file = fopen(filename, "r");

	if (xfer_file == NULL)
	{
		filecmd = NONE;
		return(0);
	}

	strcpy (file_local, filename);
	return(1);
}
/* convert a pair of hexadecimal coded input chars to an 8 bit char	*/

#include "manifs.h"

int hex_to_a(cin)
char *cin;
{

	register int i, cout;
	register char c;

	cout = 0;		/* insurance */
	for (i = 0; i < 2; ++i)
	{
		c = cin[i];
		if (HEXFIRST <= c && c <= HEXLAST)
			cout = ( ( cout << 4 ) & CU ) | (c & CL);
		else
			return(HEXERROR);
	}
	return (cout);
}
/*		This routine identifies the presented argument
		againts the table of acceptable commands		*/

#include "manifs.h"

int identify(table,string)
char *table[];
char string[];

{

	int i;

	for (i = 1; table[i][0] != NUL; ++i)
		if ((_abbrv(table[i], string) == 1))
			return (i);
	return (0);
}
/*		This routine tests stdin and returns 1 if it is a tty
		otherwise it returns 0					*/

int ifonline ()
{
	extern int on_line;
	
	return (on_line);
}
/*	manage an inbound file transfer					/*

/*	when called, localname has been opened on xfer_file and
	compression handshaking is complete.  This routine formats and
	sends out the file header packet with verification then turns
	control over to the inbound packet processor			*/

#include "manifs.h"

int inwards(remotename, localname)
char remotename[], localname[];
{
	extern char packet[], *rawfgets(), fileformat[];
	extern int progstate;
	extern unsigned checksum();

	int err_cnt, l_length, r_length, i, q;
	unsigned cksum;
	char work[BUFFSIZE], *p;

	strcpy (packet, "|FILE");
	packet[F_DIRECTION] = (progstate == MASTER ? TO_MASTER : TO_SLAVE);
	packet[F_STX] = X_STX;
	l_length = strlen(localname);
	r_length = strlen(remotename);
	if (progstate == MASTER)
		sprintf(work, fileformat,
			r_length, l_length, remotename, localname, X_ETX);
	else
		sprintf(work, fileformat,
			l_length, r_length, localname, remotename, X_ETX);
	p = &(packet[F_SENDLENGTH]);
	q = strlen(work);
	for (i = 0; i < q; ++i)
		p[i] = work[i];
	cksum = checksum(&(packet[F_STX]));
	if (cksum == 0)
	{
		message ("Error calculating checksum in inwards routine\n");
		kill (getpid(), 9);	/* program bug if happens */
	}
	sprintf(work, "%04d", cksum % 10000);
	p = &(packet[F_CKSUM]);
	for (i = 0; i < 4; ++i)
		p[i] = work[i];
	for (err_cnt = 0; err_cnt < ERR_LIMIT; ++err_cnt)
		if (sendpacket(packet))
			break;
	if (err_cnt >= ERR_LIMIT)
	{
		message ("Error limit reached on file packet\n");
		return(0);
	}
	if (!procpack())
		message("Get: File transfer failed\n");
	else
		message("Get: Completed\n");
	return(1);
}
/*	parse the command option l=string for the comm line	*/

int linearg(string)
char *string;
{
	extern char line_name[], line_save[];

	if (string[1] != '=')
	{
		message ("%s not understood\n", string);
		return (1);
	}
	strcpy(line_name, &string[2]);	/*	used here	*/
	strcpy(line_save, line_name);	/*	remember here	*/
	return(0);
}
/*	list options for a verb when they consist of another table	*/

#include "manifs.h"

int listopt(string, table)
char string[], *table[];
{

	int i;
	
	message ("Options for the %s command are:\n", string);
	for (i = 1; table[i][0] != NUL; ++i)
		message ("%s ", table[i]);
	message ("\n");
	return(1);
}
/*	load a line into a string from the stdin file		*/

#include "manifs.h"

int loadline(string, limit)
char string[];
int limit;
{
	extern int progstate, eofsw, progstate, linestate;
	extern char in_prompt[];
	extern char *rawfgets();

	char *ret;

	if (eofsw > 0)
		return (EOF);

	message ("%s", in_prompt);	/* prompt */

	if (progstate == SLAVE && linestate == CONNECTED)
		ret = rawfgets(string, limit, stdin);
	else
		ret = fgets(string, limit, stdin);
	if (ret == NULL) 
	{
		eofsw = 1;
		return (EOF);
	}
	return ( strlen(string) );
}
	/* NON PORTABLE 	UCB UNIX 4.X */

/*	Set up input and output units for the comm line		*/

#include "manifs.h"

int lopen()
{

	extern int linestate;
	extern int speed;
	extern char line_name[];
	extern FILE *in_line, *out_line;
	extern struct sgttyb com_line;
	extern struct sgttyb com_save;
	extern struct tchars com_1;
	extern struct tchars com_1s;
	extern struct ltchars com_2;
	extern struct ltchars com_2s;

	int stat, stat_too;
	int ldiscipline = NTTYDISC;
	int not_the_tty;

	not_the_tty = ( strcmp (line_name, LINETTY) );	/* zero if terminal */

	if (linestate == CONNECTED)
	{
		message ("Line is already open\n");
		return(1);
	}

	if (not_the_tty)
	{
		in_line = fopen (line_name, "r");	/* open for read */
		out_line = fopen (line_name, "w");	/* open for write */
		if (in_line == NULL || out_line == NULL	)
		{
			message ("Could not open line %s\n", line_name);
			return(0);
		}
	}
	else
	{
		in_line = stdin;
		out_line = stdout;
	}

	/* set new tty	*/
	stat = ioctl(fileno(in_line), TIOCSETD, &ldiscipline);
	if (stat < 0)
	{
		message ("Couldn't set new tty discipline on line %s\n",
			line_name);
		return(0);
	}

		/* read line parameters */
	stat = ioctl(fileno(in_line), TIOCGETP, &com_line);
	stat_too = ioctl(fileno(in_line), TIOCGETP, &com_save);
	if (stat < 0 || stat_too < 0)
	{
		message ("Open routine failed reading line parameters\n");
		return(0);
	}
	ioctl (fileno(in_line), TIOCGETC, &com_1);
	ioctl (fileno(in_line), TIOCGETC, &com_1s);
	ioctl (fileno(in_line), TIOCGLTC, &com_2);
	ioctl (fileno(in_line), TIOCGLTC, &com_2s);
	com_1.t_intrc = com_1.t_quitc = com_1.t_eofc = -1;
	ioctl(fileno(in_line), TIOCSETC, &com_1);
	com_2.t_suspc = com_2.t_dsuspc = com_2.t_rprntc =
		com_2.t_flushc = com_2.t_werasc = com_2.t_lnextc = -1;
	ioctl(fileno(in_line), TIOCSLTC, &com_2);

	if (not_the_tty)
		com_line.sg_ispeed = com_line.sg_ospeed = speed;

	com_line.sg_erase = '\010';	/* erase char is backspace */
	com_line.sg_kill  = '\030';	/* kill char is ctrl-x	   */
		/* set line controls for char by char */
	com_line.sg_flags = CBREAK | TANDEM;

	stat = ioctl(fileno(in_line), TIOCSETP, &com_line); /* set it up */
	if (stat < 0)
	{
		message ("Failed to set up line properly\n");
		return(0);
	}

	/* set exclusive use */
	stat = ioctl(fileno(in_line), TIOCEXCL, NULL);
	if (stat < 0)
	{
		message ("Failed to set exclusive use on line\n");
		return(0);
	}

	/* set hangup on close */
	stat = ioctl(fileno(in_line), TIOCHPCL, NULL);
	if (stat < 0)
	{
		message("Failed to set hang up on close on line\n");
		return(0);
	}

	linestate = CONNECTED;
	message("Line connected\n");
	return(1);
}
	/* general machine to machine file transfer shell program */

#include "manifs.h"

int main (argc, argv)
int argc;
char *argv[];
{
	extern char linebuf[], callname[];
	extern int progstate;

	int status, i;
	char c;

	status = setup (argc, argv);
	if (status == 0)
	{
		message ("%s: Setup fails\n", callname);
		exit(-1);
	}
	message ("%s: Setup complete\nType !v for program status\n",
		callname);
	for (status = 1; status != 0; )
		switch (status) {
			case 1:
				status = process(loadline(linebuf, BUFFSIZE));
				continue;
			case 2:
					/* note - anti-reflection code */

				i = readline(linebuf, BUFFSIZE);
				c = linebuf[0];
				if ( ( (progstate == SLAVE) && (c == X_SOH) )
					|| (c == PROGESC) )
					status = process(i);
				else
					status = 1;
				continue;
			default:
				message ("Illegal status in main %d\n",
					status);
				wrapup();
				exit(-1);
		}
	exit(0);

	/* note on anti-reflection:
		The case 2 above must be left this way.  If process is
		called directly (e.g. status = process(readline ...))
		then the last line input will be sent to the other device
		ad nauseam.  This code checks for things that CAN be
		handled by process().  It is left to the maintainer to
		fathom why it was done this way.
	*/
}
/*	This routine is not portable, and is used to hide printf	*/
/*	do a printf and return the value 1				*/

#include "manifs.h"

int message(format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
char *format;
int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
{
	fprintf (stdout, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
	fflush(stdout);
	return (1);
}
/*	make a data packet obeying compression rules
	input is in in_char for length in_len chars
	output is ready to send packet in out_pack	*/

#include "manifs.h"

mkdatapacket(in_char, out_pack, in_len)
char in_char[], out_pack[];
int in_len;
{
	extern int compress;
	
	char *cp, pair[2], *a_to_hex();
	int wx, px, ix, count;
	unsigned cksum;

	cksum = 0;
	for (ix = 0; ix < in_len; ++ix)
		cksum += in_char[ix];
	cksum %= 10000;
	sprintf(out_pack, "|%04d%03d{", cksum, in_len);
	cp = &(out_pack[D_BYTES]);
	px = ix = 0;
	while (ix < in_len)
	{
		if (compress && ((in_len - ix) > 2))
		{
			wx = ix;		/* in case of backout */

			while ((in_char[wx] == in_char[++ix]) && ix < in_len)
				;
			count = ix - wx;
			if (count > COM_LIMIT)
			{
				ix -= (count - COM_LIMIT);
				count = COM_LIMIT;
			}
			if (count > 2)
			{
				cp[px++] = X_GS;
				cp[px++] = (count | 0x40) & CM;
				a_to_hex(in_char[wx], pair);
				cp[px++] = pair[0];
				cp[px++] = pair[1];
				continue;
			}
			else
				ix = wx;	/* back off */
		}
					
		a_to_hex(in_char[ix++], pair);
		cp[px++] = pair[0];
		cp[px++] = pair[1];
	}
	cp[px] = X_ETX;
}
/*	output the appropriate nack	*/

#include "manifs.h"

int nack()

{
	sendline("n\n",2);
	return(0);
}
/*		process a command line from the stdin device		*/

#include "manifs.h"

int ourcmd(linebuf, inlength)
char linebuf[];
int inlength;
{
	extern char *optab[], *prtable[];
	extern int  linestate, progstate;
	extern char line_name[], line_save[], in_prompt[];
	extern char file_local[], file_remote[];
	extern unsigned zdebug;

	int cmd, argcount;
	char arglist[20][MAXSTRING];

	if (linebuf[1] == PROGESC)	/* two escapes - send it */
	{
		sendline (&linebuf[1], inlength - 1);
		return(2);		/* main will read line   */
	}

	argcount = tokenize(&linebuf[1], inlength-1, arglist);
	if (!argcount)
	{
		message ("%s - no valid arguments found", linebuf);
		return(1);
	}
	
	cmd = identify(optab, arglist[0]);
	
	switch (cmd)
	{

		case OPENLINE:		/* open the communications line   */
			if (lopen() == 0) {
				wrapup();	/* fatal error		  */
				return(0);
			}
			return(1);

		case SENDFILE:		/* send a file from here to there */
			if (progstate == SLAVE)
			{
				message("Send: MASTER state command ");
				message("but program is in SLAVE\n");
			}
			if (argcount != 3)
			{
				message ("Usage: Send localfile remotefile\n");
				return (1);
			}
			strcpy(file_local, arglist[1]);
			strcpy(file_remote, arglist[2]);
			return(send(file_local, file_remote));
			
		case GETFILE:		/* get a file from there to here  */
			if (progstate == SLAVE)
			{
				message ("Get: MASTER state command ");
				message ("but program is in SLAVE\n");
				return(1);
			}
			if (argcount != 3)
			{
				message ("Usage: Get remotefile localfile\n");
				return(1);
			}
			strcpy(file_remote, arglist[1]);
			strcpy(file_local, arglist[2]);
			return(receive(file_remote, file_local));

		case MAXLINE:		/* set the maximum number of bytes
					   that can be sent at one time   */
			if (argcount != 2)
			{
				message ("Usage: %s nn\n", optab[cmd]);
				return(1);
			}
			return(setmaxl(arglist[1]));

		case FINISHED:		/* close out program		  */
			wrapup();
			return(0);

		case MASTER:
		case SLAVE:
			if (progstate == cmd)
				message ("Already in %s state\n",
					optab[cmd]);
			else {
				progstate = cmd;
				if (cmd == MASTER)
				{
					strcpy (in_prompt, MASTERPROMPT);
					strcpy (line_name, line_save);
				}
				else
				{
					strcpy (in_prompt, SLAVEPROMPT);
					strcpy (line_name, LINETTY);
				}
				message ("%s state set\n", optab[cmd]);
			}
			return (1);

		case REMOTE:
			if (argcount != 2)
			{
				listopt (optab[cmd], prtable);
				return (1);
			}
			setremote(arglist[1]);
			return(1);

		case VIEWSETUP:
			showsetup();
			return(1);

		case CONTINUE:
			return(2);

		case ZDEBUG:
			zdebug = ((arglist[1][1] | ' ') == 'n');
			return(1);

		default:
			message ("%s not recognized as a command\n",
				arglist[0]);
			listopt("on-line", optab);
			return  (ifonline());
	}
}
/* make and output a data packet	*/

#include "manifs.h"

outdata(p, l)
char p[];
int l;
{
	extern char packet[];
	extern int errcount;

	mkdatapacket(p, packet, l);
	for (errcount = 0; errcount < ERR_LIMIT; ++errcount)
		if (sendpacket(packet))
			break;
}
/* manage an outbound file transfer					*/

/* this routine is the converse of inwards and is called with similar
   conditions (xfer_file is open, etc.).  After formatting the file
   header and having it accepted by the other program, fileout is
   called to transfer the data.						*/

#include "manifs.h"

int outwards (file_local, file_remote)
char file_local[], file_remote[];
{
	extern char packet[], *rawfgets(), fileformat[];
	extern int progstate;
	extern unsigned checksum();
	
	int err_cnt, l_length, r_length, i, q;
	unsigned cksum;
	char work[BUFFSIZE], *p;

	strcpy (packet, "|FILE");
	packet[F_DIRECTION] = (progstate == MASTER ? TO_SLAVE : TO_MASTER);
	packet[F_STX] = X_STX;
	l_length = strlen(file_local);
	r_length = strlen(file_remote);
	if (progstate == MASTER)
		sprintf( work, fileformat,
			l_length, r_length, file_local, file_remote, X_ETX);
	else
		sprintf( work, fileformat,
			r_length, l_length, file_remote, file_local, X_ETX);
	p = &(packet[F_SENDLENGTH]);
	q = strlen(work);
	for (i = 0; i < q; ++i)
		p[i] = work[i];
	cksum = checksum(&(packet[F_STX]));
	if (cksum == 0)
	{
		message("Error calculating checksum in outwards routine\n");
		kill (getpid(), 9);	/* program bug if happens */
	}
	sprintf(work, "%04d", cksum % 10000);
	p = &(packet[F_CKSUM]);
	for (i = 0; i < 4; ++i)
		p[i] = work[i];
	for (err_cnt = 0; err_cnt < ERR_LIMIT; ++err_cnt)
		if (sendpacket(packet))
			break;
	if (err_cnt >= ERR_LIMIT)
	{
		message("Error limit reached on file packet\n");
		return(0);
	}
	if (!fileout())
		message("Send: File transfer failed\n");
	else
		message("Send: Completed\n");
	return(1);
}
/* process the contents of linebuf and decide where to get the next line */

#include "manifs.h"

int process(i)
int i;
{
	extern char *rawfgets(), linebuf[];
	extern int progstate, linestate, filecmd;
	extern FILE *in_line;

	char c;

	if (i == EOF) {				/* wrap up, friends	*/
		wrapup();
		return (0);
	}
	if (i == 0)
		return(1);
	c = linebuf[0];
	if (progstate == SLAVE && c == X_SOH)
		if (linestate == DISCONNECTED)
		{
			message("Packet input, but line is not open\n");
			return(1);
		}
		else
		{
			while ((dopacket(linebuf, i) < 2)
				&& (filecmd != IN))
				i = strlen(rawfgets (linebuf, BUFFSIZE, in_line));
			if (filecmd == IN)
				if (!fileout())
				{
					message ("Too many errors - fileout\n");
					return(0);
				}
			return(2);
		}
	if (c == PROGESC)			/* something for us	*/
		return(ourcmd(linebuf,i));	/* returns 0, 1, 2	*/
	sendline(linebuf, i);			/* something for remote	*/
	return (2);				/* main should read line*/
}
/*	loop doing rawfgets and processing packets until an end of file
	packet is found.  This is the main inwards file transfer routine */

#include "manifs.h"

int procpack()
{
	extern char linebuff[], *rawfgets(), packet[];
	extern int progstate;
	extern FILE *in_line;
	
	int pkt_cnt = 0;
	int err_cnt = 0;
	char c;

	while (err_cnt < ERR_LIMIT)
	{
		rawfgets(packet, PACKSIZE, in_line);
		if (packet[0] != X_SOH)
		{
			nack();
			++err_cnt;
			continue;
		}
		switch ((c = packet[1] & CM))
		{
		case 'E':
			eofpacket(packet, strlen(packet));
			return(ack());
		default:
			if ( c >= '0' && c <= '9')
				if (xferpacket (packet, strlen(packet)))
				{
					err_cnt = 0;
					ack();
					if ( (progstate == MASTER) &&
						( (++pkt_cnt % WARM_FEEL) == 0) )
							message(".");
					continue;
				}
				else
				{
					++err_cnt;
					nack();		/* bad packet */
					continue;
				}
			else
			{
				++err_cnt;
				nack();			/* data packet expected */
				continue;
			}
				
		}
	}
	return(err_cnt < ERR_LIMIT);
}
	/* NON PORTABLE - UCB UNIX 4.x	*/
	
/* do an fgets on a line that is set to raw or cbreak mode using the newline
   table entry nltab[newline] as the end of input line character	*/

#include "manifs.h"

char *rawfgets(string, limit, unit)
char string[];
int  limit;
FILE *unit;
{
	extern char nltab[];
	extern int newline, progstate;
	extern unsigned zdebug;
	
	int i = 0;
	char c = NUL;

	while (c != nltab[newline] && i < limit)
	{
		c = (fgetc(unit)) & CM;	/* hangs on the read */

		if (zdebug && (progstate == MASTER))
		{
			char dc[3];

			dc[0] = ' ';
			dc[1] = c;
			dc[2] = NUL;
			if (c <= US || c == DEL)
			{
				dc[0] = '^';
				dc[1] = (c == DEL ? '?' : (c | '@'));
			}
			message ("Rawfgets: Debug: input char = %s\n", dc);
		}
			/* control character filter */

		if (NUL <= c && c <= US)
			if ((c = nltab[newline]) || (c = '\n'))
				;		/* do nothing */
			else
				continue;	/* scrag all except newline */
		if (c == DEL)
			continue;
		string[i++] = c;
	}
	string[i] = NUL;
	if (zdebug && progstate == MASTER)
		message ("Rawfgets: Debug: return string -->%s<--\n", string);
	return(string);
}
/* read characters from the comm_line and put them on stdout
   also accumulating each "line" in the string buffer until a
   prompt sequence is seen.  In cases where the entry in prtab[prompt]
   is a del, the end of the input is determined by absence of further input
   and is controlled by a non-portable routine (ccount)			*/

#include "manifs.h"

int readline(string, limit)
char string[];
int limit;
{
	extern int data_present, linestate, prompt, newline;
	extern FILE *in_line;
	extern char prtab[], nltab[];
	extern long ccount();

	register int i, j;
	int reset_line;
	register char c;
	long nchars;

		
	if (linestate == CONNECTED)
	{
		data_present = i = reset_line = 0;
		for (;;)
		{
			nchars = ccount(in_line);
			if (!nchars)
				break;
			data_present = 1;
			for (j = 0; j < nchars; ++j)
			{
				if (reset_line)
				{
					reset_line = 0;
					i = 0;
				}
				c = (fgetc(in_line)) & CM;

					/* control character filter */

				if ((c == DEL) || (NUL <= c && c <= US))
				{
					if ( c == nltab[newline] || c == '\n')
						reset_line = 1;
					else if (prtab[prompt] != DEL)
					{
						if (c == prtab[prompt])
							reset_line = 1;
					}
					else
						continue;
				}

				if (c == nltab[newline])
					c = NL;
				string[i++] = c;
				putchar(c);
				if (i >= limit)
					break;
			}
			fflush(stdout);
			if (i >= limit)
				break;
		}
		fflush(stdout);
	}
	else
	{
		message("Readline - line not connected\n");
		return(0);
	}
	string[i] = NUL;
	return(i);
}
/*	routine controls file transfers inwards		*/

#include "manifs.h"

int receive(remotename, localname)
char remotename[], localname[];
{
	extern int linestate, compress, filecmd;
	extern FILE *xfer_file;

	if (linestate == DISCONNECTED)
	{
		message ("Get: Can't run. Line disconnected\n");
		return(1);
	}

	xfer_file = fopen(localname, "w");
	if (xfer_file == NULL)
	{
		message ("Get: Can't open %s for write\n", localname);
		return(1);
	}
	filecmd = OUT;

	compress = setcomp();

	return(inwards(remotename, localname));
}
/*	routine controls outward file transfers	*/

#include "manifs.h"

int send (file_local, file_remote)
char file_local[],  file_remote[];
{
	extern int linestate, compress, filecmd;
	extern FILE *xfer_file;
	
	if (linestate == DISCONNECTED)
	{
		message("Send: Can't run.  Line disconnected`n");
		return(1);
	}
	
	xfer_file = fopen(file_local, "r");
	if (xfer_file == NULL)
	{
		message ("Send: Can't open %s for read\n", file_local);
		return(1);
	}
	
	filecmd = IN;
	
	compress = setcomp();
	
	return(outwards(file_local, file_remote));
}
/*	send a line out on the comm line		*/

#include "manifs.h"

int sendline(buffer, nchars)
char buffer[];
int  nchars;
{
	extern int linestate, newline;
	extern FILE *out_line;
	extern char nltab[];
	char c;
	register int i;

	if (linestate == CONNECTED)
	{
		for ( i = 0; i < nchars; ++i )
		{
			if (buffer[i] == NL)
				c = nltab[newline];
			else
				c = buffer[i];
			fputc(c, out_line);
		}
		fflush(out_line);
	}
	else
		message ("Communications line not opened\n");
	return(1);
}
/*	send the packet found in the incoming buffer	*/

#include "manifs.h"

int sendpacket(p)
char p[];

{

	extern FILE *in_line, *out_line;
	extern int newline, progstate;
	extern char nltab[], linebuf[], *rawfgets();
	extern unsigned zdebug;

	int i;
	char c;

	if (zdebug && (progstate == MASTER))
		showpacket("sendpacket", p);	
	i = 0;
	do 
	{
		c = p[i++];
		fputc(c, out_line);
	}
	while (c != X_ETB && c != X_ETX);
	fputc(nltab[newline], out_line);
	fflush(out_line);
	rawfgets(linebuf, BUFFSIZE, in_line);
	return (linebuf[0] == 'y' ? 1 : 0);
}
/*	offer to enable compression if other program is willing		*/

#include "manifs.h"

int setcomp()
{
	extern char handshake[], linebuf[];
	extern FILE *in_line;
	extern char *rawfgets();

	sendline(handshake, strlen(handshake));
	rawfgets(linebuf, BUFFSIZE, in_line);
	return (linebuf[0] == 'y' ? 1 : 0);
	
}
/*	set maximum transmission line length			*/

#include "manifs.h"

int setmaxl(string)
char *string;
{
	extern maxline;
	
	int state;

	state = sscanf(string, "%d", &maxline);
	if (state != 1)
		message ("Unable to set maxline. Current value = %d\n",
			maxline);
	else
		message ("Maximum line length set to %d\n", maxline);
	return(1);
}
/*		set remote system paramters				*/

#include "manifs.h"

int setremote(string)
char string[];
{

	extern char *prtable[];
	extern int newline, prompt, remote;
	
	int i;
	
	i = identify(prtable, string);
	if (prtable[i][0] == NUL) {
		message("Cannot set remote state to %s - not supported\n",
			string);
		return(1);
	}
	
	newline = prompt = remote = i;
	return(1);
}
	/* NON PORTABLE - UCB UNIX 4.X	*/

/*	setup - This routine performs whatever setup is necessary	*/

#include "manifs.h"

int setup (argc, argv)
int argc;
char *argv[];
{

	extern int  on_line, speed;
	extern char line_name[], line_save[];
	extern char syntax[], *upperc(), callname[];

	int i, p;

	p = 0;

	if (strlen(argv[0]) < 19)
		strcpy (callname, argv[0]);	/* copy call name for msgs */
	else
		strncpy (callname, argv[0], 19);
	upperc(callname);

	on_line = isatty(fileno(stdin));	/* control errors	*/

	speed = SPEEDEFAULT;			/* set defaults		*/
	strcat(line_name, LINEDEFAULT);
	for (i = 1; i < argc; ++i)
		switch (argv[i][0]) {
			case 'l':
			case 'L':
				p += linearg(argv[i]);
				continue;
			case 's':
			case 'S':
				if (argv[i][1] == 'l' || argv[i][1] == 'L')
				{
					ourcmd ("!slave", 6);
					ourcmd ("!open", 5);
					continue;
				}
				p += speedarg(argv[i]);
				continue;
			default:
				fprintf(stderr, "%s not understood\n%s",
					argv[i], syntax);
				return(0);
		}
	if (p != 0)
		return(0);

	return (1);
}
/*	debugging tool - can be dropped later		*/

#include "manifs.h"

void showpacket(who, packet)
char *who, packet[];
{
	char work[MAXSTRING], c;
	unsigned i;

	i = 0;
	do
	{
		c = work[i] = packet[i];
		++i;
	} while (( c != X_ETX) && (c != X_ETB));
	work[i] = NUL;
	message ("Debug: from %s: packet contents\n%s\n", who, work);
}
/*	routine to display the current state of the world		*/

#include "manifs.h"

int showsetup()
{

	extern int progstate, linestate, speed, compress, maxline;
	extern char line_name[];
	extern char *optab[], *prtable[];
	extern char nltab[], prtab[], callname[], line_name[];
	extern int remote, newline, prompt;
	
	message ("%s: Program Status\n", callname);
	message ("State is %s\n", optab[progstate]);
	message ("Communications line %s is %s\n", line_name,
		(linestate == CONNECTED ? "open" : "closed"));
	message ("Remote device is %s\n", prtable[remote]);
	showspeed(speed);
	message ("Remote newline is 0%o\n", nltab[newline]);
	message ("Remote prompt is 0%o\n", prtab[prompt]);
	message ("Maximum characters to transmit per packet is %d\n", maxline);
	message ("Compression is %s\n", compress ? "ON" : "OFF");
	return(1);
}
	/*	NON PORTABLE - UCB UNIX 4.X */

/*		print the interpreted value in speed		*/

#include "manifs.h"

int showspeed(speed)
{


	message("External line speed is ");
	
	switch(speed) {
	
		case B0:
			message("Null\n");
			return;
		case B50:
			message("50");
			break;
		case B75:
			message ("75");
			break;
		case B110:
			message ("110");
			break;
		case B134:
			message("134.5");
			break;
		case B150:
			message ("150");
			break;
		case B200:
			message("200");
			break;
		case B300:
			message ("300");
			break;
		case B600:
			message ("600");
			break;
		case B1200:
			message ("1200");
			break;
		case B1800:
			message ("1800");
			break;
		case B2400:
			message("2400");
			break;
		case B4800:
			message("4800");
			break;
		case B9600:
			message("9600");
			break;
		case EXTA:
		case EXTB:
			message("External\n");
			return;
		default:
			message("Erroneous\n");
			return;
	}
	message (" bits per second\n");
	return;
}
	/*	NON PORTABLE - UCB UNIX 4.X	*/

/*			Parse s=value					*/

#include "manifs.h"

int speedarg(string)
char *string;
{
	extern int speed;

	int i;

	int val;

	i = sscanf (string, "s=%4d", &val);
	if (i != 1) {
		message ("%s not understood\n", string);
		return(1);
	}

	switch (val) {
		case 110:
			speed = B110;
			return(0);
		case 300:
			speed = B300;
			return(0);
		case 1200:
			speed = B1200;
			return(0);
		case 1800:
			speed = B1800;
			return(0);
		case 2400:
			speed = B2400;
			return(0);
		case 4800:
			speed = B4800;
			return(0);
		case 9600:
			speed = B9600;
			return(0);
		default:
			message ("Speed %d is not supported\n", val);
			return(1);
	}
}
/*	this routine breaks the input string up into tokens in the
	list of strings given.						*/

#include "manifs.h"

int tokenize(string, limit, list)
char string[];
int limit;
char list[][MAXSTRING];
{
	int i, j, k;
	char c;

	j = k = 0;
	for (i = 0; i < limit; ++i) {
		c = string[i];
		switch(c) {
			case ' ':
			case '\t':
				if (k > 0) {
					list[j][k++] = NUL;
					++j;
					k = 0;
				}
				continue;
			case NUL:
			case '\r':
			case '\n':
				if ( k > 0)
					list[j][k++] = NUL;
				return (++j);
			default:
				list[j][k++] = c;
		}
	}
	list[j][k] = NUL;
	return(++j);
}
/*	uppercase an input string's alphabetic characters	*/
char *upperc(string)
char string[];
{
	int i, j;
	char c;

	j = strlen(string);
	for (i = 0; i <= j; ++i) {
		c = string[i];
		if ( c >= 'a' && c <= 'z')
			string[i] ^= 040;
	}
	return(string);
}
/*	routine to perform wrapup functions at end of run		*/

#include "manifs.h"

int wrapup()
{
	extern char callname[];
	extern int linestate;
	extern FILE *in_line, *out_line;
	extern struct sgttyb com_save;
	extern struct tchars com_1s;
	extern struct ltchars com_2s;
	extern char line_name[];

	if (linestate == CONNECTED)
	{
		ioctl(fileno(in_line), TIOCSETC, &com_1s);
		ioctl(fileno(in_line), TIOCSLTC, &com_2s);
		ioctl(fileno(in_line), TIOCSETP, &com_save);	/* restore */
		if (strcmp (line_name, LINETTY))	/* not the tty */
		{
			fclose(in_line);
			fclose(out_line);
		}
		else
			in_line = out_line = NULL;
		linestate = DISCONNECTED;
		message("%s: Communication line closed\n", callname);
	}
	return(1);
}
/*	process incoming data packet	*/

#include "manifs.h"

int xferpacket(ptr, len)
char ptr[];
int len;
{
	extern FILE *xfer_file;
	extern int compress;

	int ret_char, dx, px;
	unsigned value, nchars, cksum, count;
	char data[MAXSTRING], *p;

	sscanf(ptr, "|%4d%3d{", &value, &count);
	dx = px = 0;
	p = &(ptr[D_BYTES]);

	while ((p[px] != X_ETX) && (p[px] != X_ETB))
	{
		if (compress && p[px] == X_GS)
		{				/* de-compression */
			nchars = p[px + 1] ^ 0x40;
			px += 2;
			ret_char = hex_to_a(&(p[px]));
			if (ret_char == HEXERROR)
				return(0);
			while (nchars--)
				data[dx++] = ret_char;
			px += 2;
		}
		else
		{
			ret_char = hex_to_a(&(p[px]));
			if (ret_char == HEXERROR)
				return(0);
			data[dx++] = ret_char;
			px += 2;
		}
		if (dx >= count)
			break;
	}

	for (cksum = px = 0; px < dx; px++)
		cksum += data[px];
	cksum %= 10000;
	if (cksum != value)
		return(0);

	for (px = 0; px < dx; px++)
		putc(data[px], xfer_file);

	fflush(xfer_file);
	return(1);
}