Subject: General Machine-Machine Transfer Newsgroups: net.sources 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 #include #include #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 or */ 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); }