[comp.os.minix] XCOMM 2.2 for MINIX v1.3d

ward@netcom.UUCP (Mike Ward) (05/23/89)

Greetings ...

I was looking around for a communications program to use under MINIX
V1.3 and found XCOMM 2.2 by Larry Gensch on the COMPUSERVE UNIX forum.
It took a little hacking to get it to run properly under MINIX, and I
haven't tested everything thouroughly, but the things I have tried seem
to work, so I though it would be safe to post.  (Please send me info on
any problems you find). 

MINIX XCOMM 2.2 supports the XMODEM file transfer protocol with CRC or
CHECKSUM error detection, CompuServe Information Service protocol B,
text capturing, and a phone directory.  It also comes with
documentation (which I've updated to reflect the changes I made), and
should be fairly compatable with XCOMM 2.2 which Larry will support.

I'm posting the package in three shell archives. Enjoy ...

cut below the dotted line and place it in a file (xc22pX.sh) 
and then run through the MINIX v1.3 shell:

	sh xc22pX.sh

-------------------------------------------------------------------------------
echo x - historical.doc
sed '/^X/s///' > historical.doc << '/'
X
X	X C O M M    R E V I S I O N     H I S T O R Y
X
XVersion 2.2 MINIX	mike ward	20 May 89
X    Modified terminal mode special characters to IBM-PC ALT chars
X    Removed PUT_TAKE if defs and PUT_TAKE code
X    Removed HAVE_DUP2 if defs and DUP2 code
X    Removed HAVE_STRSTR if defs and STRSTR code
X    Removed DTR_DROPS_CARRIER if defs and associated code
X    Modified to use <sgtty.h> rather than <termio.h> however
X	XON/XOFF control is stubbed out since not controllable in RAW mode
X	Interruptability is not controllable in RAW mode
X    Removed text vs binary XMODEM transfers. Can only send/recieve XMODEM
X	one way. If you want to modify the file contents use a filter.
X    Modified open of /dev/tty to use w+, this required MINIX fopen mod
X    Removed reperences to getopt, opterr, optarg for command line args
X    Had to add fflush calls to get output to screen working in places
X    Had to shorten some external names to make them uniq in first 7/8 chars
X	s_set_* to ss_*, script_* to spt_*
X    Had to add code to define strdup
X    Added help message to terminal mode
X    Modified signal stuff since MINIX did not behave as expected
X	SIGALRM to SIGIOT
X	SIGUSR1 to SIGQUIT
X	SIGCLD  to SIGHUP
X	added signal SIGHUP from child to parent on child termination
X    Modified erasln to use ^[[0K to erase to end of line
X
XVersion 2.2	larry gensch	12/8/87		Major code restructure
X    Reduced program and command options
X    Added SET command for setting various parameters
X    Added HANGUP command for disconnecting modem
X    Modified xccisb.c code to support CIS "Quick B" Protocol
X    Added xcscrpt.c code for processing script files
X    Added SCRIPT= parameter to phonebook processing (auto-logon scripts)
X    Added CIS parameter for CIS <ENQ> auto transfers
X    Added NL parameter for newline translation
X    Added HANGUP command from terminal mode
X    Added SCRIPT command from terminal mode
X
XVersion 2.1b	larry gensch	11/11/87	Bug Fix release
X    (no bugs in the code, just some portability fixes)
X    Changed "sigset" in xccisb to "signal"
X    Removed #include <setjmp.h> from xcxmdm
X
XVersion 2.1a	larry gensch	10/28/87	General Update
X    Added CIS "B" Protocol (c, ct commands)
X    Added BAUD= & BITS= parameters to phonelist file.
X    Switched some command letters
X    Revised command line parsing to use getopt()
X
XVersion 2.0	larry gensch	10/19/87	Revisions for System V.3
X    Basically version 1.1 with minor modifications
X
XVersion 1.1	Eric E Coe	7/21/85
X    Autodial for Hayes-compatible modem.
X    General rearranging of the code
X
XVersion 1.0	Eric E Coe	4/12/85
X    Program created
/
echo x - phonelist.doc
sed '/^X/s///' > phonelist.doc << '/'
XFormat of the .phonelist file:
X
X1.  The .phonelist must exist with the file name ".phonelist" either in the
X    current directory, or your home directory as defined by the $HOME
X    environment variable.  The name of the phonelist file can be changed
X    using the SET PFILE command.
X
X2.  The .phonelist file is ASCII text (lines of text separated by newlines).
X    It can be created and maintained using emacs, vi, or even ed.
X
X3.  The first field of data in each line (after any whitespace and up to the
X    next occurance of whitespace) is assumed to be a phone number in a valid
X    format for the modem being dialed.
X
X4.  Any text may follow the phone number.
X
X5.  Special strings within each line:
X
X    BITS=x	(x=7|8) - Set the terminal mode mask to 7/8 bits.
X    BAUD=nnnn	(n=300|1200|2400|4800|9600) - Set the baud rate to the
X		specified value
X    SCRIPT=file	Immediately after sending the autodial string, execute the
X		script file specified. (Note that the specified filename is
X		CASE SENSITIVE!)
X
X6.  A sample entry is shown below:
X
X     687-0374	CompuServe		BITS=7	BAUD=1200, SCRIPT=cis.cmd
X
X    The above entry indicates that the number to be dialed is 687-0374; the
X    bit mask is to be set to seven bits (ignore high bits), the baud rate is
X    to be set to 1200 baud, and commands are to be taken from the file
X    "compusrv.cmd" in either the current or home directory.
X
X    A sample .phonelist file is included in the XCOMM source code
X    distribution.
/
echo x - readme
sed '/^X/s///' > readme << '/'
XXCOMM 2.2 MINIX:
X
XNew in this release...
X
X*   Modified XCOMM command line interface
X*   CIS "Quick B" Protocol support for faster up/downloads
X*   Brand new script processor with integration into the phonebook
X*   Some code cleanup
X*   Better "vanilla" Unix support (thanks to bug reports)
X
XRefer to "historical.doc" for a list of ALL new features.
X
XXCOMM is a PUBLIC DOMAIN (not shareware) communication program that works on 
XUnix System V.3 (and MINIX).  It probably works on other Unix systems with no 
Xmodification, since no machine specific coding was done.  
X
XMINIX XCOMM is distributed in three bundles:
X
XXCM22P1.SH	All documentation for XCOMM
XXCM22P2.SH	source code, header files, and makefile.
XXCM22P3.SH	source code
X
XThis README manifest file is included in both packages.
X
XContents of XCM22P1.SH:
X
Xreadme		This manifest file
Xscript.doc	Quick reference to the XCOMM script language
Xxcomm.doc	Latest documentation for the XCOMM program
Xhistorical.doc	Historical information about versions 1.0 and 1.1
Xphonelist.doc	Documentation on using XCOMM's phonelist
X
X
XContents of XCM22SRC.SH:
X
Xreadme		This manifest file
Xinstall.doc	Quick installation notes
Xmakefile	Make file for compiling XCOMM
Xcis.cmd		A sample XCOMM script file for logging onto CompuServe.
X.phonelist	A sample Phone List file
Xxcomm.h		XCOMM's header file
Xxcxmdm.c	XCOMM XMODEM protocol module
Xxccisb.c	XCOMM CIS-B/QuickB protocol module
X
X
XContents of XCM22SRC.SH:
X
Xreadme		This manifest file
Xxcomm.c		XCOMM main program
Xxcport.c	XCOMM Terminal I/O module
Xxcscrpt.c	XCOMM Script processor
Xxcsubs.c	XCOMM's subroutines
Xxcterm.c	XCOMM Terminal mode module
/
echo x - script.doc
sed '/^X/s///' > script.doc << '/'
X
X		    XCOMM SCRIPT QUICK REFERENCE
X			XCOMM Release 2.2
X
X
X
XIntroduction
X
X    Script files can automate some tedious tasks such as logging into a
X    system.  A script file is an Ascii text file and may be entered or
X    edited using any standard Unix text editor.
X
X    The script file is read line by line.  Empty lines (consisting of white
X    space only) are ignored.  Comments are lines whose first non-space
X    character is a pound sign (#).
X
X    The script processor reads each script line, ignoring leading white
X    space, into "words".  A word is defined as either:
X
X    - A sequence of characters delimited by white space; or
X    - A sequence of characters enclosed in single or double quotes.
X
X    The first word of a script file is considered the "command word."
X    If the last character of the command word is a colon (:), the line is
X    considered to be a LABEL (the object of a GOTO statement).  Otherwise,
X    it is assumed to be a script command and is interpreted as such.
X    Command words are case insensative.
X
X    Some commands take one or more arguments.  Each argument is parsed as a
X    single word as defined above.  If blanks are required in an argument,
X    the argument MUST be quoted using single or double quotes.
X
X    Enclosed in the XCOMM distribution is the file "compusrv.cmd".  This is
X    an example script file (No, that is not really my user ID or my
X    password in that file!), and shows the use of most of the available
X    script commands.  The script commands are self explanatory for the most
X    part.
X
X
XStartup Scripts
X
X    When XCOMM is started up, it looks for the file ".xcomm" in the current
X    or $HOME directory.  If it is found, it is executed.  This is useful for
X    setting your "basic" parameters without having to recompile XCOMM.  For
X    example, your startup file may turn CIS <ENQ> mode off, set your baud
X    rate to 9600, and set 7BIT translation.
X
X
XScript Command List
X
X    Below is the description of all commands that may be used in the XCOMM
X    script language:
X
X
XCAPTURE ON|OFF
X
X    The command CAPTURE ON will open the capture command; all characters
X    received during WAITFOR processing will be appended to the capture file.
X    The command CAPTURE OFF will close the capture file.
X
X    This setting does NOT currently extend to terminal mode.  This may be
X    offered in a later release (probably through the SET command).
X
X
XDIAL <number>
X
X    Dial the specified number.  XCOMM supports generic "Hayes" compatible
X    modems for dialing.  Note that this command requires an actual phone
X    number; the phonebook is not used for this function.
X
X
XECHO ON|OFF
X
X    If the argument to the ECHO command is ON, all subsequent command lines
X    that are processed will be displayed on the local screen.  The exception
X    to this is lines containing a TRANSMIT command.  These lines will just
X    print "TRANSMIT ...", so that passwords, etc. can be protected.
X
X    If the argument to the ECHO command is OFF, scripts will execute
X    quietly (this is the default setting).
X
X
XEXIT
X
X    Terminate the script file prior to the end of file.  Returns to terminal
X    mode.
X
X
XGOTO <label>
X
X    Go to the specified label in the script file and continue execution from
X    that point.  The label may either precede or follow the actual GOTO
X    statement.
X
X
XIF <condition>
X    <statements>
X[ ELSE
X    statements ]
XENDIf
X
X    Conditionally execute statements based on specified condition.  XCOMM
X    supports the following conditions:
X
X    WAITFOR		TRUE if the last WAITFOR command was successful
X    LINKED		TRUE if this script was executed from the phonebook
X
X    Conditions may be negated using the prefix NOT or the character "!":
X
X    !WAITFOR		TRUE If the last WAITFOR command timed out
X    NOT WAITFOR		Same as !WAITFOR above
X
X    The ELSE and ENDIF keywords must appear on their own lines.  IF
X    statements may not be nested.
X
X
XPAUSE <time>
X
X    Suspend execution of the script for the specified number of seconds.
X    This is usually used for timing considerations; for example, waiting a
X    couple of seconds after receiving the CONNECT message and typing ^C to
X    CompuServe.
X
X
XQUIT
X
X    Terminate the script AND the XCOMM program (return to the shell).
X
X
XREDIAL
X
X    Redial the last number dialed using the DIAL command OR the phonebook.
X
X
XSET <parameter> <value>
X
X    Sets the specified parameter to the specified value.  The SET parser
X    used in command mode is used, and the parameters/values are identical.
X    Refer to the main documentation for information on the SET command.
X
X    During script processing, internal status messages are NOT displayed on
X    the local terminal (eg, SET 7BIT ON in a script file does not display
X    the new value on the terminal).  This may be changed in the future
X    (probably tied to the ECHO setting).
X
X    During script processing, specifying SET alone is an ERROR... there is
X    no way currently display the current status of the SET-able parameters
X    during script processing.
X
X
XTRANSMIT <text>
X
X    Transmit the specified text to the remote.  The text argument should be
X    quoted (using single or double quotes) if there are spaces to be
X    transmitted.  The text is transmitted AS IS (no case conversions are
X    performed).
X
X    Prefix characters:
X
X    ^	Control character prefix - The next character is made into a
X	control character.  
X	For example:  ^M = RETURN (0x0D); ^J = NEWLINE (0x0A).
X
X    \	Quote prefix - The next character is transmitted verbatim.  For
X	example, \^ would transmit a literal ^.
X
X
XTTY ON|OFF
X
X    The TTY command specifies whether or not characters received from the
X    modem will be displayed on the local terminal.  Since the only time that
X    the script processor looks at the receive queue is during WAITFOR
X    processing, the displays may look a bit erratic.
X
X    Use the TTY OFF command to disable local display of received characters
X    during script processing.
X
X
XWAITFOR <text> [timeout]
X
X    Wait for the specified text to appear from the modem.  The text argument
X    should be quoted (using single or double quotes) if there are spaces to
X    be transmitted.
X
X    Special characters are interpreted the same as for TRANSMIT.
X
X    If the timeout argument is specified, XCOMM will wait that number of
X    seconds for the string to appear.  If no timeout is given, XCOMM
X    defaults to 30 seconds.
X
X    During WAITFOR processing, characters received (up to and including the
X    last character found in the text or in the timeout) can be captured to a
X    disk file (if CAPTURE ON was specified), and/or displayed to the screen
X    (if TTY ON was specified).
X
X
XNotes to Next Release:
X
X    In the next release (2.4?), the script processor will support ALL
X    command mode options, including xmodem send and receive, CIS transfers,
X    local dump to terminal mode and back to script, shell commands, PUT and
X    TAKE, plus a DIVERT command to transmit the contents of a data file
X    through the modem (similar to the terminal mode <ESC> F command).
X
X    I am always open to reasonable suggestions for improvements to the XCOMM
X    program and its script language.
/
echo x - xcomm.doc
sed '/^X/s///' > xcomm.doc << '/'
XXCOMM is a public domain terminal program that currently features
X
Xo XMODEM and CIS "QuickB" file transfers with optional "text" translation
Xo Hayes Modem dialing directory with BAUD and BIT parameters
Xo Unix CU compatible PUT and TAKE commands
Xo Simple HOST communication mode
Xo Communication scripting with optional linkage from phone directory.
X
XXCOMM is offerred free of charge with NO requests for financial compensation
Xreqested (this is public domain, not shareware).  The source code may be
Xused or modified by anyone, but this code, or derivations of this code,
Xmay not be sold by anyone to anyone.
X
XDocumentation on the SCRIPT language and the XCOMM Phone directory is
Xcontained in separate files.
X
X
XXCOMM Command Summary
X---------------------
X
XXCOMM prompts for commands using the string:
X
X    XCOMM>
X
XWith version 2.2, XCOMM supports the following commands:
X
X
Xc		
X
XInitiate CIS QuickB File Transfer.  This command is used for both uploading and
Xdownloading from CompuServe.
X
X
Xg file
X
XExecute the XCOMM script file "file".  Returns to terminal mode when the
Xscript is complete.
X
X
Xxr file
X
XXMODEM receive.  Receive the specified file from the remote system using
Xthe XMODEM file transfer protocol (with or without CRC checking - see set).
X
X
Xxs file
X
XXMODEM transmit.  Transmit the specified file to the remote system using
Xthe XMODEM file transfer protocol (with or without CRC checking - see set).
X
X
Xset
X
XDisplay or set the transmission parameters used by the XCOMM program.  Refer
Xto the SET section of this document.
X
X
Xt
X
XEnter terminal mode.  Refer to the TERMINAL section of this document.
X
X
Xx
X
XExit program.  Return to invoking program/shell.
X
X
X! <cmd>
X
XExecute the specified command as a child process.  If <cmd> is ommitted,
Xexecute a local interactive shell.
X
X
X!!
X
XRe-execute the last shell command string.
X
X
X$
X
XExecute a shell command with stdin and stdout redirected to the modem port.
XThis effectively puts the computer into a "host" mode.
X
X
X?
X
XPrint a short XCOMM command summary.
X
X
X
XUsing the SET Command
X---------------------
X
XThe SET command is used to display and set/reset XCOMM's tunable parameters.
XThe usage is shown below:
X
X(1)   set
X
X      Display XCOMM's current parameters.
X
X
X(2)   set 7bit
X      set 8bit
X      set crc
X      set chk
X      set term
X      set cmd
X
X      Set the indicated parameter:
X
X      7bit	Modem high-bit masking.  All characters received from the
X		modem are masked so their values are between 0 and 127.
X		This is useful for remote systems that transmit parity
X		characters (the parity is ignored).
X
X      8bit	Disable Modem high-bit masking.  All characters received
X		from the modem are displayed verbatim.
X
X      crc	Set XMODEM CRC protocol.  All transferred blocks use a
X     		16-bit block check, which is more reliable than the older
X		"checksum" block check.
X
X      chk	Set XMODEM Checksum protocol.  All transferred blocks use an
X     		8-bit block check, which is not as reliable as the CRC
X		block check, but is compatible with older programs using
X		XMODEM.
X
X      term	Set auto-jumpback to terminal mode after all file transfers.
X
X
X      The above parameters are paired; that is, the "crc" parameter is
X      negated by the "chk" parameter.
X
X      XMODEM Transfers:  When using "crc" protocol on a transmit, the XCOMM
X      program will fall back to "checksum" block checks if the receiving
X      program does not support the special "crc" handshake.
X
X(3)   set cis   on|off
X      set mung  on|off
X      set purge on|off
X      set xoff  on|off
X      set baud  <value>
X      set cfile name
X      set pfile name
X      set nl
X
X      cis	Set CompuServe <ENQ> file transfer requests.  An "on" value
X     		specifies that when in terminal mode, an <ENQ> character
X		will perform an automatic CIS QuickB protocol transfer.
X		This parameter should be set "off" when not connected to
X		CompuServe, as phone line noise may cause a bogus file
X		transfer request.
X
X      mung	Set file overwrite flag.  If "on", files may be overwritten
X		when receiving data files.  If "off", files will not be
X		overwritten (will cause an error message to be displayed).
X
X      purge	Set Bad Telephone Line Purge mode.  If "on", removes
X      		spurious characters received through the phone line due to
X		noise before listening for an acknowledgement.  This
X		increases the amount of time spent transmitting each block,
X		but can improve throughput overall by reducing the number of
X		block retransmissions.
X
X      (XON/XOFF doesn't work under MINIX for RAW streams)
X      xoff	Set XON/XOFF flow control flag.  If "on", the program will
X		honor the XOFF control character and wait until an XON
X		character is received before transmitting any more
X		information.  If "off", the program will ignore XOFF/XON
X		requests.
X
X      baud	Set the desired baud rate.  Supported baud rates are 300,
X		1200, 2400, 4800, and 9600 baud.
X
X      cfile	Set the name of the terminal mode capture file to "name".
X
X      pfile	Set the name of the terminal mode phonelist file to "name".
X
X      nl	Set newline translation mode ON or OFF. When ON (the default)
X		all newlines will be translated to carriage returns in
X		terminal mode. When OFF all newlines are sent as newlines,
X		carriage returns are sent as carriage returns.
X
X
XA sample "set" command status display is shown below:
X
X    Modem port is '/dev/tty00'.
X    Speed is 1200 baud.
X    Seven-bit communication mask enabled.
X    XMODEM CRC protocol enabled.
X    Extra bad telephone line purging enabled.
X    Capture save file is 'capture.log'.
X    Phone number file is '.phonelist'.
X    XON/XOFF Flow control is OFF.
X    CIS <ENQ> Auto Download is ON.
X    Terminal NL Translation is ON.
X
X
XTERMINAL Mode operation
X-----------------------
X
XIn terminal mode, all characters typed at the keyboard are sent to the
Xmodem (except the special character lited below); all characters received
Xfrom the modem are displayed on the local terminal screen.
X
XNewline characters (0x0A) are translated to carriage returns (0x0D) when
XNewline mode is active.
X
XTerminal Mode Special Characters:
X
XALT-?	Help		Displays a short message describing these keys.
X
XALT-c	toggle Capture	Toggle capture file - If the file is not open, it is
X			opened in APPEND mode (text receive accumulates at
X			the end of the file).  If the file is already open,
X			it is closed, instead.
X
XATL-d	Dial		Select a phone number and dial it.
X
XALT-g	script (GO)	Execute a script file.
X
XALT-h	Hangup		Disconnect from the remote system.
X
XALT-s	Send file	Send a file through the modem (ascii transfer).  An
X			option is available for waiting after each line is
X			sent to avoid overrunning the remote systems input
X			buffer.
X
XALT-x	eXit		Exit terminal mode back to XCOMM command mode.
X
X
X
XXCOMM Command Line switches
X---------------------------
X
X-l device
X
X    (Line option) Specifies the path name to be used for the modem device.  
X    This overrides the value of the MODEM environment variable.  
X
X    Note that if the MODEM environment variable is not set, the -l Line option 
X    is mandatory.  
X
X    The MODEM environment variable is used as the default path name to be used 
X    for the modem device.  If this environment variable is not set, the -l 
X    Line option is mandatory.  
X
X    The MODEM variable is usually set in a shell profile.  In the Bourne 
X    shell, this is placed in the .profile file of your home directory with 
X    statements similar to that shown below:  
X
X	export MODEM; set MODEM="/dev/tty00"
X
X    (assuming your modem device is /dev/tty00).  Using the C Shell, the 
X    following statement may be place in .cshrc in your home directory:  
X
X	setenv MODEM "/dev/tty00"
X
X
X-g file
X
X    (GO option) Specifies a script to be executed as soon as XCOMM is finished 
X    initializing.  
X
X
X-t
X
X    (Terminal option) Directs XCOMM to jump directly into terminal mode after 
X    initializing.  
X
X
XEntering XCOMM with an invalid option will display a summary of the valid
Xoptions supported.
X
X
XBUG REPORTS, ENHANCEMENTS, ETC...
X---------------------------------
X
XMy electronic addresses are:
X
XCompu$erve:		[72236,3516]   (UNIXFORUM)
XDelphi:			larryg
XBix:			lar3ry
XAndover CNode:		larry gensch
X			(This is a FIDO BBS dedicated to C Language
X			programming - phone number (617) 470-2548)
/

ward@netcom.UUCP (Mike Ward) (05/23/89)

Greetings ...

I was looking around for a communications program to use under MINIX
V1.3 and found XCOMM 2.2 by Larry Gensch on the COMPUSERVE UNIX forum.
It took a little hacking to get it to run properly under MINIX, and I
haven't tested everything thouroughly, but the things I have tried seem
to work, so I though it would be safe to post.  (Please send me info on
any problems you find). 

MINIX XCOMM 2.2 supports the XMODEM file transfer protocol with CRC or
CHECKSUM error detection, CompuServe Information Service protocol B,
text capturing, and a phone directory.  It also comes with
documentation (which I've updated to reflect the changes I made), and
should be fairly compatable with XCOMM 2.2 which Larry will support.

I'm posting the package in three shell archives. Enjoy ...

cut below the dotted line and place it in a file (xc22pX.sh) 
and then run through the MINIX v1.3 shell:

	sh xc22pX.sh

-------------------------------------------------------------------------------
echo x - .phonelist
sed '/^X/s///' > .phonelist << '/'
X1-301-459-3865  Ada IC                  BITS=7  BAUD=2400
X1-512-832-9224  Austinet (TIPC)         BITS=7  BAUD=2400
X      988-5366  CompuServe 2400	        BITS=7  BAUD=2400  SCRIPT=cis.cmd
X1-301-277-9408  Mars Hotel (MINIX)      BITS=7  BAUD=2400
X      997-9175  Netcom (UNIX)           BITS=7  BAUD=2400
X1-301-765-5555  PIWG                    BITS=7  BAUD=2400
X1-317-742-5533  Progrs Room (EMACS)     BITS=7  BAUD=2400
/
echo x - cis.cmd
sed '/^X/s///' > cis.cmd << '/'
X# Ensure we are in 7-bit mode (high bit ignored), since CIS defaults to
X# seven-bit words (this ensures the waitfor commands will find what they
X# are looking for).
X
X    set 7bit on
X
X# The following IF condition tests to see if we are linked from a .phonelist
X# entry.  If not, we manually dial the local COMPUSERVE node:
X
X    if !linked
X	dial	687-0374	# Note anything past a full command is ignored
X    endif
X
X# Wait for a CONNECT message from the modem.  If we time out (20 seconds),
X# redial the number (it is probably busy).
X
Xconnect:
X    waitfor "CONNECT" 20
X    if !waitfor
X	redial
X	goto connect
X    endif
X
X# Pause a bit to let CIS catch up with us
X
X    pause 2
X
X# Transmit a Control-C (^C) to CompuServe, and then wait for the
X# prompt "User ID:".  If this is not seen in 10 seconds, we try again.
X
Xcis:
X    transmit "^C"
X    waitfor "User ID:"  10
X    if !waitfor
X	goto cis
X    endif
X
X# Transmit the user id string (remember the ^M at the end for ENTER)...
X
X    transmit "72236,3516^M"
X
X# Wait for CompuServe to ask for a password...
X
X    waitfor "Password:"
X
X# Transmit our password
X# (You don't think for a moment that this is my REAL password, do you?????)
X
X    transmit "YELLOW*BANANA^M"
X    
X# Wait for CompuServe header, and then transmit "G UNIXFORUM^M^O"
X# (The ^M is an ENTER, the ^O tells CompuServe to shut up until we
X# get there.)
X
X    waitfor "Compuserve"
X
X    transmit "G UNIXFORUM^M^O"
/
echo x - install.doc
sed '/^X/s///' > install.doc << '/'
XXCOMM Installation
X
XXCOMM 2.2 required some minor modifications to work under minix. Most of
Xthese had to do with using MINIX signals (MINIX has no SIGUSR1, SIGCLD,
Xand SIGALRM does not work as expected) and with using sgtty.h (sys 7, BSD)
Xrather than termio.h (sys III, V). One modification of MINIX software was
Xrequired to get this to work, namely, add mode w+ to the c library routine
Xfopen. My new routine looks like:
X
X-------------------------------------------------------------------------------
X#include <stdio.h>
X#include <errno.h>
X#include "lib.h"
X
X#define  PMODE    0666
X
X
XFILE *fopen(name,mode)
Xchar *name , *mode;
X{
X	register int i;
X	FILE *fp;
X	char *malloc();
X	int fd,omode,
X	flags = 0;
X
X	for (i = 0; _io_table[i] != 0 ; i++) 
X		if ( i >= NFILES )
X			return(NULL);
X
X	switch(*mode){
X
X	case 'w':
X		flags |= WRITEMODE;
X		if (( fd = creat (name,PMODE)) < 0)
X			return(NULL);
X		if (*(mode+1) == '+') {
X			flags |= READMODE;
X			omode = 2;
X			if (close(fd) < 0 || (fd = open(name,omode)) < 0)
X				return(NULL);
X		}
X		break;
X
X	case 'a': 
X		flags |= WRITEMODE;
X		omode = 1;
X		if (*(mode+1) == '+') {
X			flags |= READMODE;
X			omode = 2;
X		}
X		if (( fd = open(name,1)) < 0 )
X			if(errno != ENOENT || (fd = creat(name, PMODE)) < 0)
X				return(NULL);
X		lseek(fd,0L,2);
X		break;         
X
X	case 'r':
X		flags |= READMODE;
X		omode = 0;
X		if (*(mode+1) == '+') {
X			flags |= WRITEMODE;
X			omode = 2;
X		}
X		if (( fd = open (name,omode)) < 0 )
X			return(NULL);
X		break;
X
X	default:
X		return(NULL);
X	}
X
X
X	if (( fp = (FILE *) malloc (sizeof( FILE))) == NULL )
X		return(NULL);
X
X
X	fp->_count = 0;
X	fp->_fd = fd;
X	fp->_flags = flags;
X	fp->_buf = malloc( BUFSIZ );
X	if ( fp->_buf == NULL )
X		fp->_flags |=  UNBUFF;
X	else 
X		fp->_flags |= IOMYBUF;
X
X	fp->_ptr = fp->_buf;
X	_io_table[i] = fp;
X	return(fp);
X}
X-------------------------------------------------------------------------------
X
X
XTo compile:
X
X1.  Read xcomm.h and, if necessary, make any necessary changes.  The following 
X    #define's affect compilation:  
X
XCAPTFILE		"capture.log"
X
X    This is the default name given to the capture buffer in terminal mode
X    (and during script processing).
X
X
XPHFILE			".phonelist"
X
X    This is the default name given to the phone directory in terminal mode.
X
X
XSTARTUP			".xcomm"
X
X    This is the name of the startup script for XCOMM.  If this file is found
X    in your current or home directory, it will be executed immediately on
X    entrance into the XCOMM program.
X
X
XDRIBBLE			2
X
X    This is the default number of seconds to wait after transmitting a
X    newline when sending a file (using the <ESC> F command) in terminal
X    mode.
X
X
XCIS_INIT		1
X
X    Default CIS mode on startup.  Basically, this is no longer necessary,
X    as the startup script can override this on entry to the program.  This
X    value should be 1 if CIS <ENQ> Transfer mode is desired; 0 if it is not
X    desired.
X
X
Xindex			strchr
Xrindex			strrchr
X
X    In the string(3) library on AT&T's Unix system, the functions "strchr"
X    and "strrchr" are used to find a character within a string.  On Berkley
X    and some Xenix systems, "index" and "rindex" are used (these are defined
X    this way in K & R, also).  The original XCOMM code used the index/rindex
X    functions, and this define helped make the program compile.
X
X    Note:  If your system does not support strchr and strrchr, define these
X	   functions oppositely:
X
X	   #define strchr  index
X	   #define strrchr rindex
X
X	   Since I tend to use the latter functions.  "The wonderful thing
X	   about standards is that there are SO many to choose from!"
X
X
X2.  Read the makefile.  You may wish to edit the INSTLIB reference to wherever 
X    your system stores user-created commands.  On some systems, /usr/bin is 
X    used, other systems have other standards.  On my system, I use /usr/comm.
X
X
X3.  Type "make" to compile xcomm.
X
X
X4.  Type "make install" to install as a permanent command.
X
X
X
XTo execute:
X
X    xcomm -l /dev/ttyN
X
XWhere "/dev/ttyN" is the device name that is used to access your modem.
X
XThe "-l /dev..." option may be defaulted by setting the environment variable
XMODEM.  This may be set by your login profile to the name of the device that
Xis used to access the modem.  In /bin/sh, use the following to set your
Xdevice to /dev/tty1:
X
X    set MODEM="/dev/tty1"; export MODEM
X
XIn "csh", this would be done as follows:
X	
X    setenv MODEM /dev/tty1
X
Xshould be used.  (My appologies to "ksh" users, but I do not have that
Xshell.)
X
X
XImplementation:
X
XOn MY system, I install xcomm into its own directory (/usr/comm), and perform 
Xall uploads and downloads into this directory.  This helps keep production 
Xfiles from getting crashed.  I execute XCOMM from a shell script that changes
Xthe current directory to /usr/comm and executes the xcomm program there.
XThis keeps all uploads and downloads in that directory, keeping production
Xfiles from getting overwritten inadvertantly.  The file xcomm.sh included in
Xthe source distribution is the script that I use.
X
X
XNotes:
X
XXCOMM does not honor or set the LCK..tty files used by cu and uucp, so
Xthere may be some contention for the use of the modem.  If anybody would be
Xso kind as to write a routine that performs the lock, I will consider
Xplacing it into a future release.
X
X
XBUG REPORTS, FIXES, IDEAS, etc:
X
XI am very receptive to reasonable requests, suggestions, and bug reports.
XMy "electronic" addresses are listed below:
X
X	CompuServe		[72236,3516]
X	Delphi			LARRYG
X	Bix			LAR3RY
X	Andover CNode		larry gensch
X
X(The Andove CNode is a FIDO bulletin board that supports C programming
Xand can be reached at 617/470-2548 - 300/1200/2400 baud)
X
X
Xlarry gensch
/
echo x - makefile
sed '/^X/s///' > makefile << '/'
XCFLAGS		= -F -T. -i
XINSTLIB		= /usr/comm
X
XOBJS		= xcomm.s xcterm.s xcxmdm.s xcsubs.s xcport.s xccisb.s \
X		  xcscrpt.s
X
Xxcomm:  	$(OBJS)
X		$(CC) $(CFLAGS) $(OBJS) -o xcomm
X		chmem =8192 xcomm
X
X$(OBJS):	xcomm.h
X
Xclean:;		rm -f $(OBJS)
X
Xinstall:	xcomm
X		chmod u=rx,g=x,o=x xcomm
X		rm -f $(INSTLIB)/xcomm
X		ln xcomm $(INSTLIB)/xcomm
/
echo x - readme
sed '/^X/s///' > readme << '/'
XXCOMM 2.2 MINIX:
X
XNew in this release...
X
X*   Modified XCOMM command line interface
X*   CIS "Quick B" Protocol support for faster up/downloads
X*   Brand new script processor with integration into the phonebook
X*   Some code cleanup
X*   Better "vanilla" Unix support (thanks to bug reports)
X
XRefer to "historical.doc" for a list of ALL new features.
X
XXCOMM is a PUBLIC DOMAIN (not shareware) communication program that works on 
XUnix System V.3 (and MINIX).  It probably works on other Unix systems with no 
Xmodification, since no machine specific coding was done.  
X
XMINIX XCOMM is distributed in three bundles:
X
XXCM22P1.SH	All documentation for XCOMM
XXCM22P2.SH	source code, header files, and makefile.
XXCM22P3.SH	source code
X
XThis README manifest file is included in both packages.
X
XContents of XCM22P1.SH:
X
Xreadme		This manifest file
Xscript.doc	Quick reference to the XCOMM script language
Xxcomm.doc	Latest documentation for the XCOMM program
Xhistorical.doc	Historical information about versions 1.0 and 1.1
Xphonelist.doc	Documentation on using XCOMM's phonelist
X
X
XContents of XCM22SRC.SH:
X
Xreadme		This manifest file
Xinstall.doc	Quick installation notes
Xmakefile	Make file for compiling XCOMM
Xcis.cmd		A sample XCOMM script file for logging onto CompuServe.
X.phonelist	A sample Phone List file
Xxcomm.h		XCOMM's header file
Xxcxmdm.c	XCOMM XMODEM protocol module
Xxccisb.c	XCOMM CIS-B/QuickB protocol module
X
X
XContents of XCM22SRC.SH:
X
Xreadme		This manifest file
Xxcomm.c		XCOMM main program
Xxcport.c	XCOMM Terminal I/O module
Xxcscrpt.c	XCOMM Script processor
Xxcsubs.c	XCOMM's subroutines
Xxcterm.c	XCOMM Terminal mode module
/
echo x - xccisb.c
sed '/^X/s///' > xccisb.c << '/'
X/*
X    xccisb.c	CIS "B" Routines for XCOMM
X
X    The following code was modified from "BP.C", a "generic" B-protocol
X    handler available from CompuServe.
X
X*/
X/**
X * Copyright (c) 1985 by Steve Wilhite, Worthington, Ohio
X *
X * Permission is granted to use or distribute this software without any
X * restrictions as long as this entire copyright notice is included intact.
X * You may include it in any software product that you sell for profit.
X *
X * This software is distributed as is, and is not guaranteed to work on any
X * given hardware/software configuration.  Furthermore, no liability is
X * granted with this software.
X *
X * ABSTRACT:
X *
X *      The function, Transfer_File, implements error-free file transfer using
X *      CompuServe's "B" protocol.
X *
X *      It has been assumed that the start-of-packet sequence, DLE "B", has
X *      been detected and the next byte not received yet is the packet
X *      sequence number (an ASCII digit).
X *
X * AUTHOR: Steve Wilhite, CREATION DATE: 21-Jul-85
X *
X * REVISION HISTORY:
X *
X * 4 Dec 1987 -- larry gensch -- Supports QUICK-B Protocol 
X **/
X
X#include <stdio.h>
X#include <fcntl.h>
X#include <signal.h>
X#include <unistd.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <time.h>
X
X#include "xcomm.h"
X
Xextern FILE *tfp;		/* Generic tty pointer */
X
Xstatic int Abort_Flag = 0;	/* Equals 1 if user wants to abort */
Xstatic int Text_Mode = 0;	/* 1 = Convert /r/n to /n and back */
Xstatic int ACK_Count = 0;	/* Number of successful packets */
Xstatic int XMIT_Count = 0;	/* Number of successful packets */
Xstatic int NAK_Count = 0;	/* Number of consecutive NAKS */
Xstatic long io_count = 0L;	/* Number of bytes transferred */
Xstatic jmp_buf err_buf;		/* setjmp() buffer */
Xstatic int Last_Chr = 0;	/* Last char read from file */
Xstatic long start_time = 0L;	/* Start time of transfer */
Xstatic long bytes_sec = 0L;	/* Number of bytes transferred/sec */
X
Xstatic int Failure_Sent = 0;	/* CIS knows we aborted? */
X
X#define	Wants_To_Abort()	(Abort_Flag)
X#define MAX_WINDOW		2
X#define	min(a,b)		((a)<(b) ? (a) : (b))
X
X/*
X    The following define allows a hex transcript of the file transfer
X    to be generated.  Uncomment this define if you want the file XCOMM.LOG
X    generated.  (This really increases overhead, but handy for debugging!)
X*/
X
X/* #define CIS_B_DEBUG 	/* Uncomment if you want XCOMM.LOG generated */
X
X#define ETX     0x03
X#define ENQ     0x05
X#define DLE     0x10
X#define XON     0x11
X#define XOFF    0x13
X#define NAK     0x15
X
X#ifndef TRUE
X#define TRUE            1
X#define FALSE           0
X#endif
X
X#define Success         -1
X#define Failure         0
X
X#define MAX_PACKET      1024
X#define Max_Errors      10
X#define Max_Time        10
X#define WACK            ';'             /* wait acknowledge */
X
X/* Sender actions */
X
X#define S_Send_Packet   0
X#define S_Get_DLE       1
X#define S_Get_Num       2
X#define S_Get_Seq       3
X#define S_Get_Data      4
X#define S_Get_Checksum  5
X#define S_Timed_Out     6
X#define S_Send_NAK      7
X#define S_Got_NAK       8
X#define S_Got_ACK       9
X
X/* Receiver actions */
X
X#define R_Get_DLE       0
X#define R_Get_B         1
X#define R_Get_Seq       2
X#define R_Get_Data      3
X#define R_Get_Checksum  4
X#define R_Send_NAK      5
X#define R_Send_ACK      6
X#define R_Send_CTL      6
X
Xstatic int
XCh,
XChecksum,
XSeq_Num,
XR_Size,					/* Size of receiver buffer */
XXOFF_Flag,
XSeen_ETX;
X
Xstatic char S_Buffer[MAX_PACKET+1];	/* Sender buffer */
Xstatic char R_Buffer[MAX_PACKET+1];	/* Receiver buffer */
X
Xstatic int QBP_ws;
Xstatic int QBP_wr;
Xstatic int QBP_bs;
Xstatic int QBP_cm;
Xstatic int QBP_dq;
X
Xstatic int QBP_Active;			/* Signifies windowing in effect */
Xstatic unsigned QBP_Packet;		/* QB Packet Length */
Xstatic int QB_Out = 0;			/* Count of outstanding packets */
Xstatic int QB_Old = 0;			/* Oldest packet */
Xstatic int QB_Current = 0;		/* Current packet */
X
Xstatic struct QB_Hist {			/* Packet Send-ahead buffer */
X    int		block;			/* Block number */
X    int		size;			/* Size of the packet */
X    int		chk;			/* Checksum of packet */
X    char	seq;			/* Sequence number of packet */
X    char	buf[MAX_PACKET];	/* History buffer */
X} QB_Buf[MAX_WINDOW];	
X
X#ifdef CIS_B_DEBUG
Xstatic void xclog(dir, val)
X char dir;
X int val;
X{
X    static int cnt = 0;
X    static FILE *fp = NULL;
X    static int lastdir = 0;
X
X    if (fp == NULL) {
X	fp = fopen("xcomm.log", "w");
X	cnt = 0;
X	lastdir = dir;
X    }
X
X    if (dir == 0) {
X	fprintf(fp, "\n");
X	fclose(fp);
X	return;
X    }
X
X    if (++cnt > 20 || lastdir != dir) {
X	fprintf(fp, "\n");
X	cnt = 1;
X    }
X
X    if (lastdir != dir)
X	fprintf(fp, "\n");
X
X    fprintf(fp, "%c%1x%1x ", dir, val/16, val % 16);
X    lastdir = dir;
X}
X#else 
X#define xclog(dir,val)	/* */
X#endif
X
Xstatic long filelength(fp)
X FILE *fp;
X{
X    struct stat statbuf;
X
X    fstat(fileno(fp), &statbuf);
X
X    return statbuf.st_size;
X}
X
Xstatic Put_Msg(Text)
Xchar *Text;
X{
X    fprintf(tfp,"\r\n%s\r\n", Text);
X}
X
Xstatic char *status_msg = "%-7.7s Blk %5d  %7ld bytes %-30.30s\r";
X
Xstatic Show_ACK()
X{
X    ACK_Count++;
X    NAK_Count = 0;
X    fprintf(tfp,status_msg, "ACK", ACK_Count, io_count, " ");
X    fflush(tfp);
X}
X
Xstatic Show_XMIT(msg)
X char *msg;
X{
X    fprintf(tfp,status_msg, msg, QB_Buf[QB_Current].block, io_count, " ");
X    fflush(tfp);
X}
X
Xstatic Show_W_ACK()
X{
X    fprintf(tfp,status_msg, "ACK", QB_Buf[QB_Old].block, io_count, " ");
X    fflush(tfp);
X}
X
Xstatic Show_NAK(msg)
X{
X    char NAK_msg[8];
X
X    sprintf(NAK_msg, "NAK(%d)", NAK_Count++);
X    fprintf(tfp,status_msg, NAK_msg, ACK_Count, io_count, msg);
X    fflush(tfp);
X}
X
Xstatic Send_Byte(Ch)
X{
X    Ch &= 0xff;
X
X    sendbyte(Ch);
X    xclog('>', Ch);
X}
X
Xstatic Send_Masked_Byte(Ch)
X{
X    /* Mask any protocol or flow characters */
X
X    Ch &= 0xff;
X
X    if (Ch == ETX || Ch == ENQ || Ch == DLE || 
X	Ch == NAK || Ch == XON || Ch == XOFF)
X    {
X	Send_Byte(DLE);
X	Send_Byte(Ch + '@');
X    }
X    else Send_Byte(Ch);
X}
X
Xstatic Send_ACK()
X{
X    Send_Byte(DLE);
X    Send_Byte(Seq_Num + '0');
X}
X
Xstatic Read_Byte()
X{
X    if ((Ch = readbyte(Max_Time)) < 0 || Wants_To_Abort())
X	return Failure;
X    else {
X	xclog('<', Ch);
X	return Success;
X    }
X}
X
Xstatic Read_Masked_Byte()
X{
X    Seen_ETX = FALSE;
X    if (Read_Byte() == Failure) return Failure;
X
X    if (Ch == DLE) {
X	if (Read_Byte() == Failure)
X	    return Failure;
X	Ch -= '@';
X    } else if (Ch == ETX)
X	Seen_ETX = TRUE;
X
X    return Success;
X}
X
Xstatic unsigned short crctab[256] = {
X    0x0000,  0x1021,  0x2042,  0x3063,  0x4084,  0x50a5,  0x60c6,  0x70e7,
X    0x8108,  0x9129,  0xa14a,  0xb16b,  0xc18c,  0xd1ad,  0xe1ce,  0xf1ef,
X    0x1231,  0x0210,  0x3273,  0x2252,  0x52b5,  0x4294,  0x72f7,  0x62d6,
X    0x9339,  0x8318,  0xb37b,  0xa35a,  0xd3bd,  0xc39c,  0xf3ff,  0xe3de,
X    0x2462,  0x3443,  0x0420,  0x1401,  0x64e6,  0x74c7,  0x44a4,  0x5485,
X    0xa56a,  0xb54b,  0x8528,  0x9509,  0xe5ee,  0xf5cf,  0xc5ac,  0xd58d,
X    0x3653,  0x2672,  0x1611,  0x0630,  0x76d7,  0x66f6,  0x5695,  0x46b4,
X    0xb75b,  0xa77a,  0x9719,  0x8738,  0xf7df,  0xe7fe,  0xd79d,  0xc7bc,
X    0x48c4,  0x58e5,  0x6886,  0x78a7,  0x0840,  0x1861,  0x2802,  0x3823,
X    0xc9cc,  0xd9ed,  0xe98e,  0xf9af,  0x8948,  0x9969,  0xa90a,  0xb92b,
X    0x5af5,  0x4ad4,  0x7ab7,  0x6a96,  0x1a71,  0x0a50,  0x3a33,  0x2a12,
X    0xdbfd,  0xcbdc,  0xfbbf,  0xeb9e,  0x9b79,  0x8b58,  0xbb3b,  0xab1a,
X    0x6ca6,  0x7c87,  0x4ce4,  0x5cc5,  0x2c22,  0x3c03,  0x0c60,  0x1c41,
X    0xedae,  0xfd8f,  0xcdec,  0xddcd,  0xad2a,  0xbd0b,  0x8d68,  0x9d49,
X    0x7e97,  0x6eb6,  0x5ed5,  0x4ef4,  0x3e13,  0x2e32,  0x1e51,  0x0e70,
X    0xff9f,  0xefbe,  0xdfdd,  0xcffc,  0xbf1b,  0xaf3a,  0x9f59,  0x8f78,
X    0x9188,  0x81a9,  0xb1ca,  0xa1eb,  0xd10c,  0xc12d,  0xf14e,  0xe16f,
X    0x1080,  0x00a1,  0x30c2,  0x20e3,  0x5004,  0x4025,  0x7046,  0x6067,
X    0x83b9,  0x9398,  0xa3fb,  0xb3da,  0xc33d,  0xd31c,  0xe37f,  0xf35e,
X    0x02b1,  0x1290,  0x22f3,  0x32d2,  0x4235,  0x5214,  0x6277,  0x7256,
X    0xb5ea,  0xa5cb,  0x95a8,  0x8589,  0xf56e,  0xe54f,  0xd52c,  0xc50d,
X    0x34e2,  0x24c3,  0x14a0,  0x0481,  0x7466,  0x6447,  0x5424,  0x4405,
X    0xa7db,  0xb7fa,  0x8799,  0x97b8,  0xe75f,  0xf77e,  0xc71d,  0xd73c,
X    0x26d3,  0x36f2,  0x0691,  0x16b0,  0x6657,  0x7676,  0x4615,  0x5634,
X    0xd94c,  0xc96d,  0xf90e,  0xe92f,  0x99c8,  0x89e9,  0xb98a,  0xa9ab,
X    0x5844,  0x4865,  0x7806,  0x6827,  0x18c0,  0x08e1,  0x3882,  0x28a3,
X    0xcb7d,  0xdb5c,  0xeb3f,  0xfb1e,  0x8bf9,  0x9bd8,  0xabbb,  0xbb9a,
X    0x4a75,  0x5a54,  0x6a37,  0x7a16,  0x0af1,  0x1ad0,  0x2ab3,  0x3a92,
X    0xfd2e,  0xed0f,  0xdd6c,  0xcd4d,  0xbdaa,  0xad8b,  0x9de8,  0x8dc9,
X    0x7c26,  0x6c07,  0x5c64,  0x4c45,  0x3ca2,  0x2c83,  0x1ce0,  0x0cc1,
X    0xef1f,  0xff3e,  0xcf5d,  0xdf7c,  0xaf9b,  0xbfba,  0x8fd9,  0x9ff8,
X    0x6e17,  0x7e36,  0x4e55,  0x5e74,  0x2e93,  0x3eb2,  0x0ed1,  0x1ef0
X};
X
X#define updcrc(ch, crc) (crctab[((crc >> 8) ^ ch) & 0xff] ^ (crc << 8))
X
Xstatic Do_Checksum(Ch)
X unsigned Ch;
X{
X    if (QBP_cm == 0) {		/* Normal B Checksum */
X	Checksum <<= 1;
X	if (Checksum > 0xff)
X	    Checksum = (Checksum & 0xff) + 1;
X
X	Checksum += (Ch & 0xff);
X	if (Checksum > 0xff)
X	    Checksum = (Checksum & 0xff) + 1;
X    } else 			/* XMODEM CRC-16 */
X	Checksum = (updcrc(Ch & 0xff, Checksum) & 0xffff);
X				/* The 0xffff is due to the fact that my
X				   machine uses four-byte integers! */
X}
X
Xstatic int Read_Packet(Action)
Xint Action;
X{
X    int Next_Seq;
X    int Errors = 0;
X    char *msg = "Timeout";
X    int his_chk;
X    char mesag[30];
X
X    while (Errors < Max_Errors && !Wants_To_Abort())
X	switch (Action) {
X	case R_Get_DLE:
X	    if (Read_Byte() == Failure)
X		Action = R_Send_NAK;
X	    else if (Ch == DLE)
X		Action = R_Get_B;
X	    else if (Ch == ENQ)
X		Action = R_Send_ACK;
X	    break;
X
X	case R_Get_B:
X	    if (Read_Byte() == Failure)
X	        Action = R_Send_NAK;
X	    else if (Ch == 'B')
X		Action = R_Get_Seq;
X	    else
X		Action = R_Get_DLE;
X	    break;
X
X	case R_Get_Seq:
X	    if (Read_Byte() == Failure)
X		Action = R_Send_NAK;
X	    else {
X		Checksum = QBP_cm ? -1 : 0;
X		Next_Seq = Ch - '0';
X		Do_Checksum(Ch);
X		R_Size = 0;
X		Action = R_Get_Data;
X	    }
X
X	case R_Get_Data:
X	    if (Read_Masked_Byte() == Failure)
X		Action = R_Send_NAK;
X	    else if (Seen_ETX)
X		Action = R_Get_Checksum;
X	    else if (R_Size > QBP_Packet) {
X		msg = "Overrun";
X		Action = R_Send_NAK;
X	    } else {
X		R_Buffer[R_Size++] = Ch;
X		Do_Checksum(Ch);
X	    }
X
X	    break;
X
X	case R_Get_Checksum:
X	    Do_Checksum(ETX);
X
X	    if (Read_Masked_Byte() == Failure) {
X		Action = R_Send_NAK;
X		break;
X	    }
X
X	    if (QBP_cm) {
X		his_chk = Ch<<8;
X		if (Read_Masked_Byte() == Failure) {
X		    Action = R_Send_NAK;
X		    break;
X		}
X		his_chk |= Ch;
X	    } else
X		his_chk = Ch;
X
X	    if (Checksum != his_chk) {
X		sprintf(mesag, "Checksum %4x s/b %4x", his_chk, Checksum);
X		msg = mesag;
X		Action = R_Send_NAK;
X	    } else if (Next_Seq == Seq_Num)
X		Action = R_Send_ACK;
X	    else if (Next_Seq != (Seq_Num + 1) % 10) {
X		msg = "Bad Block";
X		Action = R_Send_NAK;
X	    } else {
X		Seq_Num = Next_Seq;
X		return Success;
X	    }
X
X	    break;
X
X	case R_Send_NAK:
X	    Errors++;
X	    Send_Byte(NAK);
X	    Show_NAK(msg);
X	    Action = R_Get_DLE;
X	    break;
X
X	case R_Send_ACK:
X	    Send_ACK();
X	    Action = R_Get_DLE;
X	    break;
X    }
X
X    return Failure;
X}
X
X#define Next_W(ind)	(ind++, ind %=QBP_ws)
X
Xstatic int Send_Window(Size)
X int Size;                      /* size of packet to send */
X{
X    int Action;
X    int I;
X    char *ptr;
X    struct QB_Hist *QB_Ptr;
X
X    if (QB_Out == QBP_ws) {
X	if (get_ACK() == Failure)
X	    return Failure;
X    }
X
X    QB_Ptr = &QB_Buf[QB_Current];
X
X    Seq_Num = (Seq_Num + 1) % 10;
X
X    Checksum = QBP_cm ? -1 : 0;
X    QB_Ptr->block = ++XMIT_Count;
X    QB_Ptr->seq = Seq_Num + '0';
X    Do_Checksum(Seq_Num + '0');
X
X    QB_Ptr->size = Size;
X    for (ptr = QB_Ptr->buf, I = 0; I < Size; I++) {
X	Do_Checksum(S_Buffer[I] + 0);
X	*ptr++ = S_Buffer[I];
X    }
X    Do_Checksum(ETX);
X    QB_Ptr->chk = Checksum;
X
X    Send_Buf(QB_Current);
X    QB_Out++;
X    Next_W(QB_Current);
X
X    return Success;
X}
X
XSend_Buf(Ind)
X{
X    char *ptr;
X    int i;
X
X    struct QB_Hist *QB_Ptr = &QB_Buf[Ind];
X
X    Show_XMIT(XMIT_Count == QB_Ptr->block?"Trans":"Retrans");
X
X    Send_Byte(DLE);
X    Send_Byte('B');
X    Send_Byte(QB_Ptr->seq);
X    for (ptr = QB_Ptr->buf, i = QB_Ptr->size; i > 0; i--)
X	Send_Masked_Byte(*ptr++);
X
X    Send_Byte(ETX);
X    if (QBP_cm)
X	Send_Masked_Byte(QB_Ptr->chk >> 8);
X
X    Send_Masked_Byte(QB_Ptr->chk & 0xff);
X
X}
X
Xget_ACK()
X{
X    int Action = S_Get_DLE;
X    int Errors = 0;
X    int hold, cnt;
X
X    Errors = 0;
X
X    while (QB_Out >= QBP_ws) {
X	if (Errors > Max_Errors || Wants_To_Abort())
X	    return Failure;
X
X	switch (Action) {
X	case S_Get_DLE:
X	    if (Read_Byte() == Failure) {
X		Action = S_Timed_Out;
X		break;
X	    }
X
X	    if (Ch == DLE) {
X		Action = S_Get_Num;
X		break;
X	    }
X
X	    if (Ch == NAK) {
X		Action = S_Got_NAK;
X		break;
X	    }
X
X	    break;
X
X	case S_Timed_Out:
X	    Errors++;
X	    Action = S_Get_DLE;
X	    break;
X
X	case S_Get_Num:
X	    if (Read_Byte() == Failure) {
X		Action = S_Timed_Out;
X		break;
X	    }
X
X	    if (Ch < '0' || Ch > '9') {
X		Errors++;
X		Action = S_Get_DLE;
X		break;
X	    }
X					/* Figure out which block was ACK'd */
X	    while (QB_Buf[QB_Old].seq != Ch && QB_Out > 0) {
X		QB_Out--;
X		Next_W(QB_Old);
X	    }
X
X	    if (QB_Buf[QB_Old].seq != Ch)
X		return Failure;		/* total loss of sync! */
X
X	    Show_W_ACK();
X	    Action = S_Get_DLE;
X	    break;
X
X	case S_Got_NAK:
X	    Show_NAK();
X
X	    QB_Current = QB_Old;	/* Reset currency point */
X	    for (cnt = 0; cnt < QB_Out; cnt++, Next_W(QB_Current)) {
X		if (Wants_To_Abort())
X		    return Failure;
X		Send_Buf(QB_Current);
X	    }
X
X	    Action = S_Get_DLE;
X	    break;
X	}
X    }
X
X    return Success;
X}
X
Xstatic int Send_Packet(Size)
Xint Size;                           /* size of packet to send */
X{
X    int Action, Next_Seq, RCV_Num, I, Errors, his_chk;
X    char *msg = "Timeout";
X    char mesag[30];
X
X    Next_Seq = (Seq_Num + 1) % 10;
X    Errors = 0;
X    Action = S_Send_Packet;
X
X    while (Errors < Max_Errors && !Wants_To_Abort())
X	switch (Action) {
X	case S_Send_Packet:
X	    Checksum = 0;
X	    Send_Byte(DLE);
X	    Send_Byte('B');
X	    Send_Byte(Next_Seq + '0');
X	    Do_Checksum(Next_Seq + '0');
X
X	    for (I = 0; I < Size; I++) {
X		Send_Masked_Byte(S_Buffer[I]);
X		Do_Checksum(S_Buffer[I] + 0);
X	    }
X
X	    Send_Byte(ETX);
X	    Do_Checksum(ETX);
X	    Send_Masked_Byte(Checksum & 0xff);
X	    Action = S_Get_DLE;
X	    break;
X
X	case S_Get_DLE:
X	    if (Read_Byte() == Failure)
X	        Action = S_Timed_Out;
X	    else if (Ch == DLE)
X		Action = S_Get_Num;
X	    else if (Ch == NAK) {
X		Errors++;
X		Action = S_Send_Packet;
X		Show_NAK("Unknown");
X	    }
X
X	    break;
X
X	case S_Get_Num:
X	    if (Read_Byte() == Failure) Action = S_Timed_Out;
X	    else if (Ch >= '0' && Ch <= '9')
X	    {
X		if (Ch == Seq_Num + '0')
X			Action = S_Get_DLE;
X		else if (Ch == Next_Seq + '0')
X		{
X			Seq_Num = Next_Seq;
X			return Success;
X		}
X		else if (Errors == 0) Action = S_Send_Packet;
X		else Action = S_Get_DLE;
X	    }
X	    else if (Ch == WACK)
X	    {
X		sleep(5);        /* Sleep for 5 seconds */
X		Action = S_Get_DLE;
X	    }
X	    else if (Ch == 'B') Action = S_Get_Seq;
X	    else Action = S_Get_DLE;
X	    break;
X
X	case S_Get_Seq:
X	/**
X	 * Start of a "B" protocol packet. The only packet that makes
X	 * any sense here is a failure packet.
X	 **/
X
X	    if (Read_Byte() == Failure)
X		Action = S_Send_NAK;
X	    else
X	    {
X		Checksum = 0;
X		RCV_Num = Ch - '0';
X		Do_Checksum(Ch);
X		I = 0;
X		Action = S_Get_Data;
X	    }
X
X	    break;
X
X	case S_Get_Data:
X	    if (Read_Masked_Byte() == Failure)
X		Action = S_Send_NAK;
X	    else if (Seen_ETX)
X		Action = S_Get_Checksum;
X	    else if (I > QBP_Packet) {
X		msg = "Overrun";
X		Action = S_Send_NAK;
X	    } else {
X		R_Buffer[I++] = Ch;
X		Do_Checksum(Ch);
X	    }
X
X	    break;
X
X	case S_Get_Checksum:
X	    Do_Checksum(ETX);
X
X	    if (Read_Masked_Byte() == Failure)
X		Action = S_Send_NAK;
X
X	    if (QBP_cm) {
X		his_chk = Ch<<8;
X		if (Read_Masked_Byte() == Failure) {
X		    Action = R_Send_NAK;
X		    break;
X		}
X		his_chk |= Ch;
X	    } else
X		his_chk = Ch;
X
X	    if (Checksum != his_chk) {
X		sprintf(mesag, "Checksum %4x s/b %4x", his_chk, Checksum);
X		msg = mesag;
X		Action = S_Send_NAK;
X	    }
X	    else if (RCV_Num != (Next_Seq + 1) % 10)
X	    {
X		msg = "Sequence";
X		Action = S_Send_NAK;
X	    }
X	    else
X	    {
X       /**
X	 * Assume the packet is failure packet. It makes no
X	 * difference since any other type of packet would be
X	 * invalid anyway. Return failure to caller.
X	 **/
X
X		Errors = Max_Errors;
X	    }
X
X	    break;
X
X	case S_Timed_Out:
X	    Errors++;
X	    Action = S_Get_DLE;
X	    break;
X
X	case S_Send_NAK:
X	    Errors++;
X	    Send_Byte(NAK);
X	    Action = S_Get_DLE;
X	    Show_NAK(msg);
X	    break;
X	}
X
X    return Failure;
X}
X
Xstatic Send_Failure()
X{
X    register i;
X
X    if (Failure_Sent++)
X	return;
X
X    Send_Byte(DLE);	/* Panic abort (usually works) */
X    Send_Byte(DLE);
X    Send_Byte(DLE);
X    Send_Byte(DLE);
X    Send_Byte(DLE);
X}
X
Xstatic int Receive_File(Name)
Xchar *Name;
X{
X    FILE *Data_File;
X    int exists = (access(Name, 0) == 0);
X
X    if (!mungmode && exists) {
X	Put_Msg("Transfer aborted due to file pre-existance.");
X	Send_Failure();
X	return Failure;
X    }
X
X    if ((Data_File = fopen(Name, "w")) == NULL) {
X	Put_Msg("Cannot create file");
X	Send_Failure();
X	return Failure;
X    }
X
X    if ((Data_File = fopen(Name, "w")) == NULL) {
X	Put_Msg("Cannot create file");
X	Send_Failure();
X	return Failure;
X    }
X
X    fprintf(tfp,"Receiving %s file %s\r\n", Text_Mode ? "Ascii":"Binary", Name);
X    Send_ACK();
X
X    for (;;)
X	if (Read_Packet(R_Get_DLE) == Success)
X	    switch (R_Buffer[0]) {
X	    case 'N':               /* Data packet */
X		if (Write(Data_File, &R_Buffer[1], R_Size - 1)) {
X		    Put_Msg("Disk write error");
X		    Send_Failure();
X		    fclose(Data_File);
X		    return Failure;
X		}
X
X		if (Wants_To_Abort()) {
X		    Send_Failure();
X		    fclose(Data_File);
X		    return Failure;
X		}
X
X		Send_ACK();
X		Show_ACK();
X		break;
X
X	    case 'T':               /* Transfer packet */
X		if (R_Buffer[1] == 'C') {
X		    Send_ACK();
X		    fclose(Data_File);
X		    return Success;
X		} else {
X		    Put_Msg("Unexpected packet type");
X		    Send_Failure();
X		    fclose(Data_File);
X		    return Failure;
X		}
X
X	    case 'F':               /* Failure packet */
X		Send_ACK();
X		fclose(Data_File);
X		return Failure;
X	    }
X    else {
X	fclose(Data_File);
X	return Failure;
X    }
X}
X
Xstatic int Send_File(Name)
Xchar *Name;
X{
X    FILE *Data_File;
X    int N;
X    long size;
X
X    if ((Data_File = fopen(Name, "r")) == NULL)
X    {
X	Put_Msg("Cannot access that file");
X	Send_Failure();
X	return Failure;
X    }
X
X    size = filelength(Data_File);
X
X    fprintf(tfp,"Transmitting %s file %s  (%ld bytes -- %ldK)\r\n",
X	    Text_Mode ? "Ascii":"Binary", Name, size, size / 1024);
X
X    do {
X	S_Buffer[0] = 'N';
X	N = Read(Data_File, &S_Buffer[1], QBP_Packet);
X
X	if (N > 0) {
X	    if (Send_Window(N + 1) == Failure) {
X		fclose(Data_File);
X		return Failure;
X	    }
X
X	    if (Wants_To_Abort()) {
X		Send_Failure();
X		fclose(Data_File);
X		return Failure;
X	    }
X	}
X    }    while (N > 0);
X
X    if (N == 0) {
X	fclose(Data_File);
X	S_Buffer[0] = 'T';
X	S_Buffer[1] = 'C';
X	if (Send_Window(2) == Failure)
X	    return Failure;
X	return get_ACK();
X    } else {
X	Put_Msg("Disk read error");
X	Send_Failure();
X	return Failure;
X    }
X}
X
Xstatic int Transfer_File()
X{
X    XOFF_Flag = FALSE;
X    Seq_Num = 0;
X
X    for (;;) {
X	if (Read_Packet(R_Get_DLE) == Success)
X	    switch (R_Buffer[0]) {
X	    case '+':			/* Get/Set QB Parameters */
X		Get_QB_Parms();
X		break;
X
X	    case 'T':			/* Transfer packet */
X		return do_transfer();
X		break;
X
X	    default:
X		Send_Failure();	/* Undefined packet */
X		return Failure;
X	    }
X	else
X	    return Failure;
X    }
X}
X
Xstatic int Get_QB_Parms()
X{
X    S_Buffer[0] = '+';
X    S_Buffer[1] = QBP_wr = min(1, R_Buffer[1]);
X    S_Buffer[2] = QBP_ws = min(MAX_WINDOW - 1, R_Buffer[2]);
X    S_Buffer[3] = QBP_bs = min(MAX_PACKET / 128, R_Buffer[3]);
X    S_Buffer[4] = min(1, R_Buffer[4]);
X    S_Buffer[5] = QBP_dq = 1;			/* Normal B Protocol Quoting */
X    
X    QBP_ws++;					/* Actual window size */
X
X    if (Send_Packet(6) != Success)
X	return Failure;
X
X    QBP_cm = S_Buffer[4];
X    QBP_Active = TRUE;
X    QBP_Packet = QBP_bs * 128;
X}
X
Xdo_transfer()
X{
X    int I, N;
X    char *Name;
X
X    /* Check data direction */
X
X    if (R_Buffer[1] != 'D' && R_Buffer[1] != 'U')
X    {
X	Send_Failure();      /* not implemented */
X	return Failure;
X    }
X
X    /* Check the file type */
X
X    if (R_Buffer[2] != 'A' && R_Buffer[2] != 'B')
X    {
X	Send_Failure();
X	return Failure;
X    }
X
X    /* Let CIS tell us if it is Ascii or not */
X
X    Text_Mode = (R_Buffer[2] == 'A');
X
X    /* Collect the file name */
X
X    Name = &R_Buffer[3];
X    Name[R_Size - 3] = '\0';
X
X    /* Do the transfer */
X
X    start_time = time(NULL);
X
X    if (R_Buffer[1] == 'U') 
X	return Send_File(Name);
X    else 
X	return Receive_File(Name);
X}
X
Xstatic Write(fp, buf, cnt)
X FILE *fp;
X char *buf;
X int cnt;
X{
X    for (; cnt-- > 0; buf++) {
X	io_count++;
X
X	if (Text_Mode && (*buf == '\r' && *(buf+1) == '\n'))
X	    continue;
X
X	if (putc(*buf, fp) == EOF)
X	    return -1;
X    }
X
X    return 0;
X}
X
Xstatic Read(fp, buf, max)
X FILE *fp;
X char *buf;
X int max;
X{
X    int c, cnt = 0;
X
X    while (max--)
X	switch (c = getc(fp)) {
X	case EOF:
X	    return cnt;
X
X	case '\n':
X	    if (Text_Mode && Last_Chr != '\r') {
X		ungetc(c, fp);
X		c = '\r';
X	    }
X	
X	default:
X	    Last_Chr = *buf++ = c;
X	    cnt++; io_count++;
X	    break;
X	}
X
X    return cnt;
X}
X
Xint B_Transfer(mode)
X{
X    int rc;
X    void newsigint();
X    long elapsed;
X
X    QBP_Active = FALSE;		/* Start in normal B Mode */
X    QBP_ws = 0;
X    QBP_wr = 0;
X    QBP_bs = 0;
X    QBP_cm = 0;
X    QBP_dq = 0;
X    QBP_Packet = 512;		/* Normal packet size maximum */
X
X    fprintf(tfp,"CIS QuickB Protocol Transfer\r\n");
X
X    Failure_Sent = Abort_Flag = Last_Chr = 0;
X    XMIT_Count = ACK_Count = NAK_Count = 0;
X    io_count = 0L;
X
X    signal(SIGINT, newsigint);
X    intdel(1);
X
X    if (setjmp(err_buf) == 0) {
X	xc_setxon(0);			/* turn off flow control */
X
X	Send_Byte(NAK);
X
X	if (startup() == Failure)
X	    Abort_Flag = 1;
X	else
X	    rc = Transfer_File();
X    }
X
X    if (Abort_Flag) {
X	Send_Failure();
X	purge();
X    }
X
X    if (Abort_Flag || rc == Failure)
X	fprintf(tfp,"\r\nFile Transfer aborted\r\n");
X    else
X	fprintf(tfp,"\r\nFile Transfer successful\r\n");
X
X    if (io_count) {
X	elapsed = time(NULL) - start_time;
X	if (elapsed > 0) {
X	    bytes_sec = io_count / start_time;
X	    fprintf(tfp,"Average %ld bytes/sec\r\n", bytes_sec);
X	}
X    }
X
X    xclog(0,0);
X
X    intdel(0);
X    signal(SIGINT, SIG_IGN);
X
X    return rc;
X}
X
Xstatic startup()
X{
X    int ch;
X
X    do {
X	Read_Byte();
X	if (Ch != ENQ)
X	    Send_Byte(NAK);
X	if (Wants_To_Abort())
X	    return Failure;
X    } while (Ch != ENQ);
X
X    Send_Byte(DLE); Send_Byte('+');	/* Quick B Indicator */
X    Send_Byte(DLE); Send_Byte('0');
X
X    return Success;
X}
X
Xstatic void newsigint()
X{
X    Abort_Flag = 1;
X
X    signal(SIGINT, SIG_IGN);
X
X    fprintf(tfp,"\r\nUser Abort...\r\n");
X}
/
echo x - xcomm.h
sed '/^X/s///' > xcomm.h << '/'
X/*
X *   XCOMM Global Definitions
X */
X
X#define ENQ		0x05		/* CIS ENQ */
X
X#define ESCAPE_CHR	0x1b
X
X#define HELPCHAR       0xBF	/* ALT-? Teminal Mode Help */
X#define ENDCHAR        0xF8	/* ALT-x Exit terminal mode */
X#define TOGCHAR        0xE3	/* ALT-c Toggle capture buffer */
X#define DIVCHAR        0xF3	/* ALT-s Send file through modem */
X#define PSELECT        0xE4	/* ALT-d Dial from phonelist */
X#define HUPCHAR        0xE8	/* ALT-h Hang up modem */
X#define	SCRPCHR	       0xE7	/* ALT-g Execute script file */
X
X#define	CAPTFILE	"capture.log"	/* Default capture file */
X#define PHFILE		".phonelist"	/* Default phonelist file */
X#define STARTUP		".xcomm"	/* XCOMM Startup Script */
X#define DRIBBLE		2		/* Text "dribble" delay */
X#define	CIS_INIT	1		/* cismode default 1=on/0=off */
X
X#define index           strchr		/* for SYSV */
X#define rindex          strrchr		/* for SYSV */
X
X/*
X    Other defines
X*/
X
X#ifndef TRUE
X#define TRUE    1
X#define FALSE   0
X#endif
X
X#define K       1024
X#define NMSIZE  256             /* file name buffer size */
X#define WBSIZE  256             /* input and working buffer size */
X
X/* globals */
X
X#include <setjmp.h>
X
Xextern jmp_buf erret;
Xextern int verbose, mungmode, bitmask, cbsiz, crcheck, badline, flowflag;
Xextern int cismode, linkflag, quote_flag, eof_flag, nlmode, scriptflag;
Xextern char captfile[NMSIZE], phonefile[NMSIZE], *mport();
Xextern char word[], *wptr, line[];
X
Xstruct kw {			/* Used by command parsing routines */
X    char *keyword;
X    void (*rtn)();
X};
/
echo x - xcxmdm.c
sed '/^X/s///' > xcxmdm.c << '/'
X/*
X *  Module:	xcxmdm.c	XMODEM Protocol Module
X *
X *  This source code is purely public domain
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <signal.h>
X#include "xcomm.h"
X
X#define SOH     001     /* ^A */
X#define EOT     004     /* ^D */
X#define ACK     006     /* ^F */
X#define NAK     025     /* ^U */
X#define CAN     030     /* ^X */
X#define CPMEOF  032     /* ^Z */
X#define WANTCRC 'C'
X#define OK       0
X#define TIMEOUT  -1     /* -1 is returned by readbyte() upon timeout */
X#define ERROR    -2
X#define WCEOT    -3
X#define RETRYMAX 10
X#define SECSIZ  128
X
X/* globals */
Xint badline         = FALSE;		/* bad telephone line?          */
Xint crcheck         = TRUE;		/* CRC check enabled?           */
Xextern FILE *tfp;			/* TTY File pointer */
X
X/* locals */
Xstatic FILE *xfp;			/* buffered file pointer        */
Xstatic int firstsec;			/* first sector of file or not? */
Xstatic char wcbuff[SECSIZ];		/* Ward Christensen sector buffer */
Xstatic unsigned short updcrc();
X
Xstatic void newsigint();		/* Our SIGINT handler */
Xstatic int  save_crc;			/* Saved crcheck value */
Xstatic jmp_buf our_env;
X
Xxreceive()
X{
X    if (word[0] == '\0') {
X	fprintf(tfp,"No file name specified for XMODEM receive\r\n");
X	return;
X    }
X
X    xc_setxon(0);				/* turn off XON/XOFF */
X    save_crc = crcheck;
X    signal(SIGINT, newsigint);			/* Install our handler */
X
X    if (!setmode()) {
X	if (setjmp (our_env) == 0) {
X	    crcheck = 0xff;
X	    do_xreceive(word);
X	}
X    }
X
X    signal(SIGINT, SIG_IGN);			/* Reinstall old handler */
X    crcheck = save_crc;
X    restmode();
X}
X
Xxsend()
X{
X    if (word[0] == '\0') {
X	fprintf(tfp,"No file name specified for XMODEM send\r\n");
X	return;
X    }
X
X    xc_setxon(0);				/* turn off XON/XOFF */
X    save_crc = crcheck;
X    signal(SIGINT, newsigint);			/* Install our handler */
X
X    if (!setmode()) {
X	if (setjmp (our_env) == 0) {
X	    crcheck = 0xff;
X	    do_xsend(word);
X	}
X    }
X
X    signal(SIGINT, SIG_IGN);			/* Reinstall old handler */
X    crcheck = save_crc;
X    restmode();
X}
X
Xstatic void newsigint()
X{
X    fprintf(tfp,"\r\nUser abort\r\n");
X    signal(SIGINT, SIG_IGN);	/* Ignore subsequent DEL's */
X    canit();			/* Abort the transmission */
X    longjmp(our_env, 0);
X}
X
Xstatic restmode()
X{
X    intdel(0);
X    xc_setflow();				/* restore XON/XOFF */
X}
X
Xstatic do_xreceive(p)
Xchar *p;
X{
X    long bytes, ftime;
X    short fmode;
X    char *ctime();
X
X    fprintf(tfp,"XCOMM: Ready to receive single file %s\r\n", p);
X    if (wcrx(p) == ERROR)
X	canit();
X    return;
X}
X
Xdo_xsend(p)
Xchar *p;
X{
X    if (wctx(p) == ERROR) {
X	fprintf(tfp,"Error transmitting file %s\r\n", p);
X	return;
X    }
X}
X
X/*
X *  Receive a file using XMODEM protocol
X */
Xstatic wcrx(name)
Xchar *name;
X{
X    register int sectnum, sectcurr, sendchar, eofseen;
X
X    if (!mungmode && !access(name, 0)) {
X        canit();
X	fprintf(tfp,"Receive of %s aborted due to pre-exsistence.\r\n");
X        return(ERROR);
X    }
X
X    if ((xfp = fopen(name, "w")) == NULL) {
X        canit();
X	fprintf(tfp,"Receive of %s aborted due to inabilty to open.\r\n");
X        return(ERROR);
X    }
X    firstsec = TRUE;
X    eofseen = FALSE;
X    sectnum = 0;
X    sendchar = crcheck ? WANTCRC : NAK;
X    fprintf(tfp,"Sync...\r\n");
X
X    while(TRUE) {
X        if (badline)
X            purge();
X        sendbyte(sendchar);
X        sectcurr = wcgetsec(6);
X        if (sectcurr == ((sectnum + 1) & 0xff)) {
X            sectnum++;
X	    putsec();
X	    fprintf(tfp,"Received sector #%d             \r", sectnum);
X	    fflush(tfp);
X
X            sendchar = ACK;
X            continue;
X        }
X
X        if (sectcurr == (sectnum & 0xff)) {
X	    fprintf(tfp,"Received duplicate sector #%d   \r", sectnum);
X	    fflush(tfp);
X            sendchar = ACK;
X            continue;
X        }
X
X	fprintf(tfp, "\r\n");
X        fclose(xfp);
X
X        if (sectcurr == WCEOT) {
X	    fprintf(tfp,"File recived OK\r\n");
X            sendbyte(ACK);
X            return(OK);
X        }
X
X        if (sectcurr == ERROR)
X            return(ERROR);
X
X	fprintf(tfp,"Sync error ... expected %d(%d), got %d\r\n",
X		    (sectnum + 1) & 0xff, sectnum, sectcurr);
X        return(ERROR);
X    }
X}
X
X/*
X *  Transmit a file using XMODEM protocol
X */
Xstatic wctx(name)
Xchar *name;
X{
X    register int sectnum, eoflg, c, attempts;
X
X    if ((xfp = fopen(name, "r")) == NULL) {
X	fprintf(tfp,"Can't open file %s for reading.\r\n", name);
X        return(ERROR);
X    }
X    firstsec = TRUE;
X    attempts = 0;
X    fprintf(tfp,"Sync...\r\n");
X
X    while((c = readbyte(30)) != NAK && c != WANTCRC && c != CAN)
X        if (c == TIMEOUT && ++attempts > RETRYMAX) {
X	    fprintf(tfp,"Receiver not responding.\r\n");
X            fclose(xfp);
X            return(ERROR);
X        }
X    if (c == CAN) {
X	fprintf(tfp,"Receiver CANcelled.\r\n");
X        fclose(xfp);
X        return(ERROR);
X    }
X    crcheck = (c == WANTCRC);
X    fprintf(tfp,"%s error checking requested.\r\n",
X		crcheck ? "CRC check" : "Checksum");
X    sectnum = 1;
X
X    do {
X        eoflg = getsec();
X	fprintf(tfp,"Transmitting sector #%d\r", sectnum);
X	fflush(tfp);
X
X        if (wcputsec(sectnum) == ERROR) {
X            fclose(xfp);
X            return(ERROR);
X        }
X        sectnum++;
X    } while(eoflg);
X
X    fprintf(tfp, "\r\n");
X    fclose(xfp);
X    attempts = 0;
X    sendbyte(EOT);
X    while(readbyte(5) != ACK && attempts++ < RETRYMAX)
X        sendbyte(EOT);
X    if (attempts >= RETRYMAX) {
X	fprintf(tfp,"Receiver not responding to completion.\r\n");
X        return(ERROR);
X    }
X
X    fprintf(tfp,"Transmission complete.\r\n");
X    return(OK);
X}
X
X/*   wcgetsec() inputs an XMODEM "sector".
X *  This routine returns the sector number encountered, or ERROR if a valid
X *  sector is not received or CAN received; or WCEOT if EOT sector.
X *
X *  Maxtime is the timeout for the first character, set to 6 seconds for
X *  retries.  No ACK is sent if the sector is received ok.  This must be
X *  done by the caller when it is ready to receive the next sector.
X */
Xstatic wcgetsec(maxtime)
Xint maxtime;
X{
X    register checksum, j, c;
X    register unsigned short oldcrc;
X    int sectcurr, sectcomp, attempts;
X
X    for(attempts = 0; attempts < RETRYMAX; attempts++) {
X        do {
X            c = readbyte(maxtime);
X        } while(c != SOH && c != EOT && c != CAN && c != TIMEOUT);
X
X        switch(c) {
X        case SOH:
X            sectcurr = readbyte(3);
X            sectcomp = readbyte(3);
X            if ((sectcurr + sectcomp) == 0xff) {
X                oldcrc = checksum = 0;
X                for(j = 0; j < SECSIZ; j++) {
X                    if ((c = readbyte(3)) < 0)
X                        goto timeout;
X                    wcbuff[j] = c;
X                    if (crcheck)
X                        oldcrc = updcrc(c, oldcrc);
X                    else
X                        checksum += c;
X                }
X                if ((c = readbyte(3)) < 0)
X                    goto timeout;
X                if (crcheck) {
X                    oldcrc = updcrc(c, oldcrc);
X                    if ((c = readbyte(3)) < 0)
X                        goto timeout;
X                    if (updcrc(c, oldcrc)) {
X			fprintf(tfp,"\r\nCRC error\r\n");
X                        break;
X                    }
X                }
X                else if (((checksum - c) & 0xff) != 0) {
X		    fprintf(tfp,"\r\nChecksum error\r\n");
X                    break;
X                }
X                firstsec = FALSE;
X                return(sectcurr);
X            }
X            else 
X                fprintf(tfp,"\r\nSector number garbled 0%03o 0%03o\r\n",
X			sectcurr, sectcomp);
X            break;
X        case EOT:
X            if (readbyte(3) == TIMEOUT)
X                return(WCEOT);
X            break;
X        case CAN:
X	    fprintf(tfp,"\r\nSender CANcelled.\r\n");
X            return(ERROR);
X        case TIMEOUT:
X            if (firstsec)
X                break;
Xtimeout:
X	    fprintf(tfp,"\r\nTimeout\r\n");
X            break;
X        }
X	fprintf(tfp,"\r\nTrying again on ths sector.\r\n");
X        purge();
X        if (firstsec)
X            sendbyte(crcheck ? WANTCRC : NAK);
X        else {
X            maxtime = 6;
X            sendbyte(NAK);
X        }
X    }
X    fprintf(tfp,"\r\nRetry count exceeded.\r\n");
X    canit();
X    return(ERROR);
X}
X
X/* wcputsec outputs a Ward Christensen type sector.
X * it returns OK or ERROR
X */
Xstatic wcputsec(sectnum)
Xint sectnum;
X{
X    register checksum, j, c, attempts;
X    register unsigned short oldcrc;
X
X    oldcrc = checksum = 0;
X    for(j = 0; j < SECSIZ; j++) {
X        c = wcbuff[j];
X        oldcrc = updcrc(c, oldcrc);
X        checksum += c;
X    }
X    oldcrc = updcrc(0, updcrc(0, oldcrc));
X
X    for(attempts = 0; attempts < RETRYMAX; attempts++) {
X        sendbyte(SOH);
X        sendbyte(sectnum);
X        sendbyte(-sectnum - 1);
X        for(j = 0; j < SECSIZ; j++)
X            sendbyte(wcbuff[j]);
X        if (badline)
X            purge();
X        if (crcheck) {
X            sendbyte((int) (oldcrc >> 8));
X            sendbyte((int) oldcrc);
X        }
X        else
X            sendbyte(checksum);
X        switch(c = readbyte(10)) {
X        case CAN:
X	    fprintf(tfp,"\r\nReceiver CANcelled.\r\n");
X            return(ERROR);
X        case ACK:
X            firstsec = FALSE;
X            return(OK);
X        case NAK:
X	    fprintf(tfp,"\r\nGot a NAK on sector acknowledge.\r\n");
X            break;
X        case TIMEOUT:
X	    fprintf(tfp,"\r\nTimeout on sector acknowledge.\r\n");
X            break;
X        default:
X	    fprintf(tfp,"\r\nGot 0%03o for sector acknowledge.\r\n", c);
X            do {
X                if ((c = readbyte(3)) == CAN) {
X		    fprintf(tfp,"\r\nReceiver CANcelled.\r\n");
X                    return(ERROR);
X                }
X            } while(c != TIMEOUT);
X            if (firstsec)
X                crcheck = (c == WANTCRC);
X            break;
X        }
X    }
X    fprintf(tfp,"\r\nRetry count exceeded.\r\n");
X    return(ERROR);
X}
X
X/* update the cyclic redundantcy check value
X */
Xstatic unsigned short updcrc(c, crc)
Xregister c;
Xregister unsigned crc;
X{
X    register int i;
X
X    for(i = 0; i < 8; i++) {
X        if (crc & 0x8000) {
X            crc <<= 1;
X            crc += (((c <<= 1) & 0400) != 0);
X            crc ^= 0x1021;
X        }
X        else {
X            crc <<= 1;
X            crc += (((c <<= 1) & 0400) != 0);
X        }
X    }
X    return(crc);
X}
X
Xstatic zerobuff()
X{
X    register int i;
X
X    for(i = 0; i < SECSIZ; i++)
X        wcbuff[i] = '\0';
X}
X
X/* send 10 CAN's to try to get the other end to shut up */
Xstatic canit()
X{
X    register int i;
X
X    for(i = 0; i < 20; i++)
X        sendbyte(CAN);
X}
X
Xstatic setmode()
X{
X    intdel(1);
X    return(0);
X}
X
X/* fill the CP/M sector buffer from the UNIX file
X * return 1 if more sectors are to be read, or 0 if this is the last
X */
Xstatic getsec()
X{
X    register int i, c;
X
X    i = 0;
X    while(i < SECSIZ && (c = getc(xfp)) != EOF) {
X        wcbuff[i++] = c;
X    }
X    /* make sure that an extra blank sector is not sent */
X    if (c != EOF && (c = getc(xfp)) != EOF) {
X        ungetc(c, xfp);
X        return(1);
X    }
X    /* fill up the last sector with 0's if binary mode */
X    while(i < SECSIZ)
X        wcbuff[i++] = '\0';
X    return(0);
X}
X
X/* Put received WC sector into a UNIX file
X * using text translations in nessesary.
X */
Xstatic putsec()
X{
X    register int i, c;
X
X    for(i = 0; i < SECSIZ; i++) {
X        c = wcbuff[i];
X        putc(c, xfp);
X    }
X    return(0);
X}
/

ward@netcom.UUCP (Mike Ward) (05/23/89)

Greetings ...

I was looking around for a communications program to use under MINIX
V1.3 and found XCOMM 2.2 by Larry Gensch on the COMPUSERVE UNIX forum.
It took a little hacking to get it to run properly under MINIX, and I
haven't tested everything thouroughly, but the things I have tried seem
to work, so I though it would be safe to post.  (Please send me info on
any problems you find). 

MINIX XCOMM 2.2 supports the XMODEM file transfer protocol with CRC or
CHECKSUM error detection, CompuServe Information Service protocol B,
text capturing, and a phone directory.  It also comes with
documentation (which I've updated to reflect the changes I made), and
should be fairly compatable with XCOMM 2.2 which Larry will support.

I'm posting the package in three shell archives. Enjoy ...

cut below the dotted line and place it in a file (xc22pX.sh) 
and then run through the MINIX v1.3 shell:

	sh xc22pX.sh

-------------------------------------------------------------------------------

echo x - readme
sed '/^X/s///' > readme << '/'
XXCOMM 2.2 MINIX:
X
XNew in this release...
X
X*   Modified XCOMM command line interface
X*   CIS "Quick B" Protocol support for faster up/downloads
X*   Brand new script processor with integration into the phonebook
X*   Some code cleanup
X*   Better "vanilla" Unix support (thanks to bug reports)
X
XRefer to "historical.doc" for a list of ALL new features.
X
XXCOMM is a PUBLIC DOMAIN (not shareware) communication program that works on 
XUnix System V.3 (and MINIX).  It probably works on other Unix systems with no 
Xmodification, since no machine specific coding was done.  
X
XMINIX XCOMM is distributed in three bundles:
X
XXCM22P1.SH	All documentation for XCOMM
XXCM22P2.SH	source code, header files, and makefile.
XXCM22P3.SH	source code
X
XThis README manifest file is included in both packages.
X
XContents of XCM22P1.SH:
X
Xreadme		This manifest file
Xscript.doc	Quick reference to the XCOMM script language
Xxcomm.doc	Latest documentation for the XCOMM program
Xhistorical.doc	Historical information about versions 1.0 and 1.1
Xphonelist.doc	Documentation on using XCOMM's phonelist
X
X
XContents of XCM22SRC.SH:
X
Xreadme		This manifest file
Xinstall.doc	Quick installation notes
Xmakefile	Make file for compiling XCOMM
Xcis.cmd		A sample XCOMM script file for logging onto CompuServe.
X.phonelist	A sample Phone List file
Xxcomm.h		XCOMM's header file
Xxcxmdm.c	XCOMM XMODEM protocol module
Xxccisb.c	XCOMM CIS-B/QuickB protocol module
X
X
XContents of XCM22SRC.SH:
X
Xreadme		This manifest file
Xxcomm.c		XCOMM main program
Xxcport.c	XCOMM Terminal I/O module
Xxcscrpt.c	XCOMM Script processor
Xxcsubs.c	XCOMM's subroutines
Xxcterm.c	XCOMM Terminal mode module
/
echo x - xcomm.c
sed '/^X/s///' > xcomm.c << '/'
X/*
X	xcomm		External (and XMODEM) Communications
X			By Eric E. Coe
X
X			Version 2.0 and above by larry gensch, ESQ
X			(modified for MINIX by mike ward)
X
X   This program is in the public domain, and anyone can use it for
X   personal or non-profit use.  Feel free to study the code and borrow
X   from it (I borrowed from other programs to create it).
X
X   I am supporting versions 2.0 and higher of XCOMM.  Information (bug
X   fixes, requests, etc.) may be sent to any one of the following electronic
X   addresses:
X
X   CompuServe				[72236,3516]	UNIXFORUM
X   Delphi				LARRYG
X   Bix					lar3ry
X   Andover CNode (BBS) [617-470-2548]	larry gensch
X
X   -lar3ry gensch
X    programmer extraordinaire
X
X*/
X
X#define  VERSION   "2.2"
X
X/*
X		R E V I S I O N     H I S T O R Y
X
XVersion 2.2 MINIX	mike ward	20 May 89
X    Modified terminal mode special characters to IBM-PC ALT chars
X    Removed PUT_TAKE if defs and PUT_TAKE code
X    Removed HAVE_DUP2 if defs and DUP2 code
X    Removed HAVE_STRSTR if defs and STRSTR code
X    Removed DTR_DROPS_CARRIER if defs and associated code
X    Modified to use <sgtty.h> rather than <termio.h> however
X	XON/XOFF control is stubbed out since not controllable in RAW mode
X	Interruptability is not controllable in RAW mode
X    Removed text vs binary XMODEM transfers. Can only send/recieve XMODEM
X	one way. If you want to modify the file contents use a filter.
X    Modified open of /dev/tty to use w+, this required MINIX fopen mod
X    Removed reperences to getopt, opterr, optarg for command line args
X    Had to add fflush calls to get output to screen working in places
X    Had to shorten some external names to make them uniq in first 7/8 chars
X	s_set_* to ss_*, script_* to spt_*
X    Had to add code to define strdup
X    Added help message to terminal mode
X    Modified signal stuff since MINIX did not behave as expected
X	SIGALRM to SIGIOT
X	SIGUSR1 to SIGQUIT
X	SIGCLD  to SIGHUP
X	added signal SIGHUP from child to parent on child termination
X    Modified erasln to use ^[[0K to erase to end of line
X
XVersion 2.2	larry gensch	12/8/87		Major code restructure
X    Reduced program and command options
X    Added SET command for setting various parameters
X    Added HANGUP command for disconnecting modem
X    Modified xccisb.c code to support CIS "Quick B" Protocol
X    Added xcscrpt.c code for processing script files
X    Added SCRIPT= parameter to phonebook processing (auto-logon scripts)
X    Added CIS parameter for CIS <ENQ> auto transfers
X    Added NL parameter for newline translation
X    Added HANGUP command from terminal mode
X    Added SCRIPT command from terminal mode
X
XVersion 2.1b	larry gensch	11/11/87	Bug Fix release
X    (no bugs in the code, just some portability fixes)
X    Changed "sigset" in xccisb to "signal"
X    Removed #include <setjmp.h> from xcxmdm
X
XVersion 2.1a	larry gensch	10/28/87	General Update
X    Added CIS "B" Protocol (c, ct commands)
X    Added BAUD= & BITS= parameters to phonelist file.
X    Switched some command letters
X    Revised command line parsing to use getopt()
X
XVersion 2.0	larry gensch	10/19/87	Revisions for System V.3
X    Basically version 1.1 with minor modifications
X
XVersion 1.1	Eric E Coe	7/21/85
X    Autodial for Hayes-compatible modem.
X    General rearranging of the code
X
XVersion 1.0	Eric E Coe	4/12/85
X    Program created
X*/
X
X#include <stdio.h>
X#include <signal.h>
X#include <ctype.h>
X#include <sgtty.h>
X
X#include "xcomm.h"
X
X/* globals */
X
Xjmp_buf erret;			/* non-local error return */
Xint mungmode = FALSE;		/* ok to overwrite files? */
Xint nlmode = TRUE;		/* Map newlines to carriage returns */
Xint stat_flag = FALSE;		/* Flag for status display */
X
X/* locals */
Xstruct sgttyb newbmode, oldbmode, sigbmode;
Xstruct tchars newcmode, oldcmode, sigcmode;
X
Xstatic int termflag = 0;
Xstatic int reterm = 0;
X
Xint tfd;
XFILE *tfp;
X
Xvoid s_cis(), s_script(), s_xmodem(), s_set(), s_term(), s_exit();
Xvoid s_shell(), s_help(), hangup();
X
X/*
X    Command table
X*/
X
Xstatic struct kw cmds[] = {
X	{ "c",		s_cis },
X	{ "g",		s_script },
X	{ "hangup",	hangup },
X	{ "xr",		s_xmodem },
X	{ "xs",		s_xmodem },
X	{ "set",	s_set },
X	{ "t",		s_term },
X	{ "x",		s_exit },
X	{ "exit",	s_exit },
X	{ "!",		s_shell },
X	{ "!!",		s_shell },
X	{ "$",		s_shell },
X	{ "help",	s_help },
X	{ "?",		s_help },
X	{ NULL,		NULL }};
X
Xstatic char oldshell[WBSIZE];
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X    extern char *optarg;
X    extern int optind, opterr;
X
X    struct kw *ptr;
X    void catch();
X    int i,c;
X    char *script = NULL;
X
X    if ((tfp = fopen("/dev/tty", "w+")) == NULL) {
X	fprintf(stderr, "Cannot access local terminal!\n");
X	exit(1);
X    }
X
X    tfd = fileno(tfp);
X
X    ioctl(tfd, TIOCGETP, &oldbmode);	/* get current console tty mode	*/
X    ioctl(tfd, TIOCGETC, &oldcmode);
X    ioctl(tfd, TIOCGETP, &newbmode);
X    ioctl(tfd, TIOCGETC, &newcmode);
X    ioctl(tfd, TIOCGETP, &sigbmode);
X    ioctl(tfd, TIOCGETC, &sigcmode);
X
X    newbmode.sg_flags = (newbmode.sg_flags & 01700) | RAW | XTABS;
X    sigbmode.sg_flags = (sigbmode.sg_flags & 01700) | RAW | XTABS;
X
X    nlmode = TRUE;
X
X    oldshell[0] = '\0';		/* set last command to blank    */
X    reterm = FALSE;		/* no direct jump into terminal mode */
X    if(setjmp(erret))		/* set error handler to exit    */
X        exit(0);		/* while parsing command line   */
X    signal(SIGINT, catch);	/* catch break & quit signals/keys */
X    signal(SIGQUIT, catch);
X
X    for (i=1; i<argc; i++) {
X	if (*argv[i] != '-')
X	    break;
X	switch (*(argv[i]+1)) {
X	case 'l':			/* set modem port name          */
X	    mport(argv[++i]);
X	    break;
X	case 'g':			/* Execute SCRIPT file		*/
X	    script = argv[++i];
X	    break;
X	case 't':			/* jump into terminal mode	*/
X	    reterm = TRUE;
X	    break;
X	default:			/* Bad command .. print help	*/
X	    usage();
X	    exit(1);
X	}
X    }
X
X    if (lock_tty()) {
X	fprintf(stderr, "Modem device is locked -- try later.\r\n");
X	exit(1);
X    }
X
X    if(mopen() < 0) {			/* open modem port and configure */
X        fprintf(stderr, "Error in modem initialization routine.\r\n");
X        fprintf(stderr, "Check environment variable MODEM for modem port.\r\n");
X        exit(1);
X    }
X
X    intdel(0);
X
X    do_startup();
X
X    status();       /* display all parameters */
X
X    fprintf(tfp, "Type \"help\" or ? for help\r\n");
X
X    for (;;) {
X        setjmp(erret);
X	ioctl(tfd, TIOCSETP, &newbmode);
X	ioctl(tfd, TIOCSETC, &newcmode);
X	eof_flag = 0;
X
X	if (script) {
X	    do_script(script);
X	    script = NULL;
X	    reterm = 1;
X	}
X
X	if (reterm) {
X	    reterm = 0;
X	    s_term();
X	    continue;
X	}
X
X	intdel(1);
X        fprintf(tfp, "XCOMM> ");	/* command prompt               */
X
X	getsome(line);
X	intdel(0);
X        fprintf(tfp, "\r\n");
X	fflush(tfp);
X
X	getword();
X	if (word[0] == '\0')		/* If blank line... reprompt	*/
X	    continue;
X
X	for (ptr = cmds; ptr->keyword != NULL; ptr++) 
X	    if (strcmp(word, ptr->keyword) == 0)
X		break;
X
X	if (ptr->keyword)
X	    (*ptr->rtn)();
X	else 
X	    fprintf(tfp, "Unrecognized keyword: %s\r\n", word);
X    }
X
X}
X
Xstatic char *babble[] = {
X"Usage:  xcomm [-l device] [-t] [-g file]",
X"",
X"-l device      Use 'device' as the modem port (eg, -l /dev/tty1)",
X"-t             Enter terminal mode immediately",
X"-g file        Execute script file 'file' immediately",
X"",
XNULL };
X
Xusage()
X{
X    char **ptr;
X
X    for (ptr = babble; *ptr != NULL; ptr++)
X	fprintf(stderr, "%s\r\n", *ptr);
X}
X    
Xvoid s_script()
X{
X    getword();
X
X    if (word[0] == '\0') {
X	fprintf(tfp, "Script file not specified\r\n");
X	return;
X    }
X
X    do_script(word);
X    reterm = 1;
X}
X
Xvoid s_xmodem()
X{
X    char d = word[1];
X
X    getword();
X    if (word[0] == '\0')
X	fprintf(tfp, "No transfer file specified\r\n");
X    else if (d == 's')
X	xsend();
X    else
X	xreceive();
X
X    reterm = termflag;
X}
X
Xvoid s_exit()
X{
X    signal(SIGINT, SIG_IGN);
X    signal(SIGQUIT, SIG_IGN);
X
X    do_exit(0);
X}
X
Xvoid s_term()
X{
X    for (;;) {
X	terminal();
X	if (cismode != 2)
X	    return;
X	cismode = 1;
X	B_Transfer();
X    }
X}
X
Xvoid s_cis()
X{
X    B_Transfer();
X    reterm = termflag;
X}
X
Xvoid s_shell()
X{
X    char c = word[0];
X    char *ptr;
X    static char *shell = NULL;
X    char *getenv();
X    int child;
X
X    if (word[0] == word[1])
X	strcpy(wptr=word, oldshell);
X    else {
X	getword();
X	if (*wptr)
X	    strcpy(oldshell, wptr);
X    }
X	
X    if (!shell) {
X	shell = getenv("SHELL");
X	if (!shell)
X	    shell = "/bin/sh";
X    }
X
X    ioctl(tfd, TIOCSETP, &oldbmode);
X    ioctl(tfd, TIOCSETC, &oldcmode);
X
X    if ((child = forkem()) == 0) {
X	if (c == '$')	/* Attach modem to stdin, stdout */
X	    mattach();
X	if (word[0] == '\0')
X	    execl(shell, shell, "-i", (char *) NULL);
X	else
X	    execl(shell, shell, "-c", wptr, (char *) NULL);
X	fprintf(stderr, "Exec failed!");
X	exit(1);
X    }
X
X    while (wait((int *) 0) != child)
X	;
X
X    intdel(0);
X
X    strcpy(oldshell, wptr);
X}
X
Xstatic char *cmdlist[] = {
X"XCOMM Command Summary",
X"c               <-- Initiate CIS QuickB File Transfer (Upload and Download)",
X"g file          <-- Execute XCOMM Script file",
X"xr file         <-- XMODEM receive file",
X"xs file...      <-- XMODEM send file",
X"set ...         <-- XCOMM Parameter set",
X"t               <-- Enter terminal emulation",
X"x               <-- Exit program",
X"!               <-- Execute a local interactive shell",
X"! cmd           <-- Execute shell command string on the local system",
X"!!              <-- Re-execute the last shell command string",
X"$ cmd           <-- Shell command with stdin and stdout redirected to modem",
X"?               <-- Print this help message",
X"",
X"SET Keywords:",
X"set                     Display current XCOMM status",
X"set 7bit  on|off        Set/Reset 7-bit data mask (ignore high bits)",
X"set crc   on|off        Enable/Disable Xmodem CRC",
X"set term  on|off        Enable/Disable terminal mode after file transfer",
X"set cis   on|off        Set/Reset CIS <ENQ> mode (Auto up/download)",
X"set mung  on|off        Set/Reset file overwrite",
X"set purge on|off        Set/Reset bad phone line mode",
X"set xoff  on|off        Set/Reset XON/XOFF flow control",
X"set baud  <value>       Set baud rate to <value>",
X"set cfile name          Change name of capture file",
X"set pfile name          Change name of phonelist file",
XNULL };
X
Xvoid s_help()
X{
X    char **ptr = cmdlist;
X
X    for ( ; *ptr != NULL; ptr++)
X	if (**ptr)
X	    fprintf(tfp, "%s\r\n", *ptr);
X	else {
X	    fprintf(tfp, "PRESS ENTER: ");
X	    getsome(line);
X	    erasln();
X	}
X}
X
Xvoid ss_7bit(), ss_crc(), ss_term(), ss_cis(), ss_mung(), ss_cr();
Xvoid ss_purge(), ss_xoff(), ss_baud(), ss_cfile(), ss_pfile();
X
Xstatic struct kw setlist[] = {
X	{ "7bit",	ss_7bit },
X	{ "nl",		ss_cr },
X	{ "crc",	ss_crc },
X	{ "term",	ss_term },
X	{ "cis",	ss_cis },
X	{ "mung",	ss_mung },
X	{ "purge",	ss_purge },
X	{ "xoff",	ss_xoff },
X	{ "baud",	ss_baud },
X	{ "cfile",	ss_cfile },
X	{ "pfile",	ss_pfile },
X	{ NULL,		NULL }};
X
Xvoid s_set()
X{
X    struct kw *ptr;
X
X    getword();
X
X    if (word[0] == '\0' && !scriptflag) {
X	status();
X	return;
X    } else if (word[0] == '\0') {
X	fprintf(tfp, "SET keyword requires an argument\r\n");
X	eof_flag++;
X	return;
X    }
X
X    lc_word(word);
X
X    for (ptr = setlist; ptr->keyword != NULL; ptr++)
X	if (strcmp(ptr->keyword, word) == 0) {
X	    (*ptr->rtn)();
X	    return;
X	}
X
X    fprintf(tfp, "Invalid SET keyword: %s\r\n", word);
X    eof_flag++;
X}
X
Xstatic char *statfmt = "%-8s %30s is %s\r\n";
X
Xvoid ss_7bit()
X{
X    int val;
X
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "7bit", "Seven-bit Mask",
X		bitmask==0xff?"ON":"OFF");
X	return;
X    }
X
X    set_onoff(&val);
X    bitmask = val?0x7f:0xff;
X
X    if (!scriptflag)
X	fprintf(tfp, "<<%s-bit mask enabled>>\r\n", val?"Seven":"Eight");
X}
X
Xvoid ss_crc()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "crc", "Xmodem CRC",
X		crcheck?"ON":"OFF");
X	return;
X    }
X
X    set_onoff(&crcheck);
X
X    if (!scriptflag)
X	fprintf(tfp, "<<XMODEM %s enabled>>\r\n", 
X		crcheck?"CRC Check":"Checksum");
X}
X
Xvoid ss_cr()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "nl", "Terminal NL Translation",
X		nlmode?"ON":"OFF");
X	return;
X    }
X  
X    set_onoff(&nlmode);
X    if (!scriptflag)
X	fprintf(tfp, "<<%s mode enabled>>\r\n", 
X		nlmode?"Newline translation":"Carriage return");
X}
X
Xvoid ss_term()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "term", "Auto Term Mode",
X		termflag?"ON":"OFF");
X	return;
X    }
X
X    set_onoff(&termflag);
X
X    if (!scriptflag)
X	fprintf(tfp, "<<File transfers return to %s mode>>\r\n",
X		termflag?"terminal":"command");
X}
X
Xvoid ss_cis()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "cis", "CIS <ENQ> Auto Download",
X		cismode?"ON":"OFF");
X	return;
X    }
X
X    set_onoff(&cismode);
X
X    if (!scriptflag)
X	fprintf(tfp, "<<CIS <ENQ> Auto Download is %s>>\r\n", 
X		cismode?"ON":"OFF");
X}
X
Xvoid ss_mung()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "mung", "File Overwrite", mungmode?"ON":"OFF");
X	return;
X    }
X
X    set_onoff(&mungmode);
X
X    if (!scriptflag)
X	fprintf(tfp, "<<File Overwrite is %s>>\r\n", mungmode?"ON":"OFF");
X}
X
Xvoid ss_purge()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "purge", "Bad Telephone line",
X		badline?"ON":"OFF");
X	return;
X    }
X
X    set_onoff(&badline);
X
X    if (!scriptflag)
X	fprintf(tfp,"<<Bad telephone line purging is %s\r\n",
X		badline?"ON":"OFF");
X}
X
Xvoid ss_xoff()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "xoff", "Terminal mode XON/XOFF",
X		flowflag?"ON":"OFF");
X	return;
X    }
X
X    set_onoff(&flowflag);
X    xc_setflow();
X
X    if (!scriptflag)
X	fprintf(tfp, "<<XOFF/XON Flow control is %s>>\r\n",flowflag?"ON":"OFF");
X}
X
Xset_onoff(flag)
X int *flag;
X{
X    char *strdup();
X    char *ptr = strdup(word);
X
X    getword();
X    lc_word(word);
X
X    if (strcmp(word, "on") == 0) {
X	*flag = 1;
X    } else if (strcmp(word, "off") == 0) {
X	*flag = 0;
X    } else {
X	fprintf(tfp, "Set '%s' value must be 'on' or 'off'.\r\n", ptr);
X	eof_flag++;
X    }
X    
X    free(ptr);
X}
X
Xvoid ss_baud()
X{
X    if (stat_flag) {
X	char br[10];
X
X	sprintf(br, "%d", mbaud((char *) 0));
X	fprintf(tfp, statfmt, "baud", "Baud Rate", br);
X	return;
X    }
X
X    getword();
X    if (word[0] == '\0') {
X	fprintf(tfp, "Set Baud must have a baud rate.\r\n");
X	eof_flag++;
X	return;
X    }
X
X    if (mbaud(word)<0) {
X	fprintf(tfp, "Unsupported baud rate %s\r\n", word);
X	eof_flag++;
X    } else if (!scriptflag)
X	fprintf(tfp, "<<Baud rate set to %d>>\r\n", mbaud((char *) 0));
X}
X
Xvoid ss_cfile()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "cfile", "Capture file", captfile);
X	return;
X    }
X
X    getword();
X    if (word[0] == '\0') {
X	fprintf(tfp, "Set CFILE must have file name.\r\n");
X	eof_flag++;
X	return;
X    }
X
X    strcpy(captfile, word);
X
X    if (!scriptflag)
X	fprintf(tfp, "<<Capture file set to '%s'>>\r\n", captfile);
X}
X
Xvoid ss_pfile()
X{
X    if (stat_flag) {
X	fprintf(tfp, statfmt, "pfile", "Phone number file", phonefile);
X	return;
X    }
X
X    getword();
X    if (word[0] == '\0') {
X	fprintf(tfp, "Set PFILE must have file name.\r\n");
X	eof_flag++;
X	return;
X    }
X
X    strcpy(phonefile, word);
X
X    if (!scriptflag)
X	fprintf(tfp, "<<Phone number file set to '%s'>>\r\n", phonefile);
X}
X
X/*
X *   Catch a signal and jump to main.  Reset signal and do a longjmp
X */
Xvoid catch()
X{
X    fprintf(tfp,"\r\nXCOMM: Interrupt\r\n");
X
X    signal(SIGINT, catch);
X    signal(SIGQUIT, catch);
X    longjmp(erret, 1);
X}
X
X/*
X *   Print the status of the program
X */
Xstatus()
X{
X    struct kw *ptr;
X    void (*fct)() = NULL;
X
X    stat_flag = 1;
X
X    fprintf(tfp, "\fXCOMM Version %s  by larry gensch - modem port: %s\r\n\n", 
X	    VERSION, mport((char *) NULL));
X    fprintf(tfp, "Keyword              Description and Status\r\n");
X    fprintf(tfp, "-------  ----------------------------------------------\r\n");
X
X    for (ptr = setlist; ptr->keyword != NULL; ptr++)
X	if (ptr->rtn != fct) {
X	    fct = ptr->rtn;
X	    (*fct)();
X	}
X
X    fprintf(tfp, "\r\n");
X    stat_flag = 0;
X}
X
Xdo_exit(rc)
X{
X    unlock_tty();
X
X    fprintf(tfp, "\r\n");
X
X    ioctl(tfd, TIOCSETP, &oldbmode);
X    ioctl(tfd, TIOCSETC, &oldcmode);
X
X    fclose(tfp);
X
X    exit(rc);
X}
X
Xdo_startup()
X{
X    char *home, *getenv();
X    char file[WBSIZE];
X
X    if (access(STARTUP, 0) == 0) 
X	strcpy(file, STARTUP);
X    else {
X	if ((home = getenv("HOME")) == NULL)
X	    return;
X	sprintf(file, "%s/%s", home, STARTUP);
X	if (access(file, 0))
X	    return;
X    }
X
X    linkflag = 2;
X    do_script(file);
X}
/
echo x - xcport.c
sed '/^X/s///' > xcport.c << '/'
X/*
X *  Module:	xcport.c	XCOMM Modem Interface Routines
X *
X *  This code is purely public domain!
X */
X
X#include <stdio.h>
X#include <signal.h>
X#include <fcntl.h>
X#include <sgtty.h>
X
X#include "xcomm.h"
X
X/*
X    The dial() routine uses these two defines.
X
X    DIALSTR is a sprintf format string that assumes a HAYES-compatible
X    modem.
X
X    HAYSATT is the HAYES "attention" signal (used if DTR_DROPS_CARRIER
X    is not set to 1).
X
X    HAYSHUP is the HAYES "hangup" command (used if DTR_DROPS_CARRIER is not
X    set to 1).
X
X    MDELAY is the delay in the output because (on my modem) the command
X    would be ignored if sent at full speed.  Change for other setups.
X    (This setting is for U.S. Robotics Password Modem).
X*/
X
X#define DIALSTR "\rATDT %s\r"   /* format string for Hayes-type modem */
X#define HAYSATT "+++"		/* Hayes "attention" signal */
X#define	HAYSHUP	"ATH\r"		/* Hayes "hang up" command */
X#define MDELAY  20000           /* delay for output to modem itself */
X
X/* globals */
X
Xextern int tfp;			/* TTY file pointer */
Xint bitmask = 0xFF;             /* modem port i/o data mask */
Xint flowflag = 0;               /* modem port i/o data mask */
X
Xstatic int mfd = -1;            /* modem port file descriptor */
Xstatic struct sgttyb pbmode;	/* modem device control string */
Xstatic struct tchars pcmode;	/* modem device control string */
Xstatic char port[NMSIZE];       /* modem port device file string */
Xstatic int baudrate = B1200;	/* baud rate */
X
Xint cpmode()
X{
X    pbmode.sg_flags = RAW | BITS8;
X    pbmode.sg_ispeed = baudrate;
X    pbmode.sg_ospeed = baudrate;
X
X    xc_setflow();
X}
X
Xint xc_setxon(mode)
X{
X    cpmode();
X
X    if (mode) {
X	; /* turn on XON/XOFF processing */
X    } else {
X	; /* turn off XON/XOFF processing */
X    }
X
X    if(mfd != -1) {
X	ioctl(mfd, TIOCSETP, &pbmode);
X	ioctl(mfd, TIOCSETC, &pcmode);
X    }
X}
X
Xint xc_flowtoggle()
X{
X    flowflag = 1 - flowflag;
X
X    xc_setflow();
X}
X
Xxc_setflow()
X{
X    if (flowflag) {
X	; /* turn on XON/XOFF processing */
X    } else {
X	; /* turn off XON/XOFF processing */
X    }
X
X    if (mfd != -1) {
X	ioctl(mfd, TIOCSETP, &pbmode);
X	ioctl(mfd, TIOCSETC, &pcmode);
X    }
X}
X
Xchar *mport(s)  /* get/set port string */
Xchar *s;
X{
X    if(s != NULL && mfd == -1)
X        strcpy(port, s);
X    return(port);
X}
X
X/* Get/set the baud rate of the modem port. If the port hasn't been opened yet,
X * just store in pmode for mopen() to use when it opens the port.
X */
Xint mbaud(s)
Xchar *s;
X{
X    cpmode();
X
X    if(s != NULL){
X        /* this gives a more limited, realistic range than in sgtty.h */
X        switch (atoi(s)) {
X        case  300: baudrate = B300;  break;
X        case 1200: baudrate = B1200; break;
X        case 2400: baudrate = B2400; break;
X        case 9600: baudrate = B9600; break;
X        default:   return(-1);
X        }
X	pbmode.sg_flags &= ~BITS8;
X	pbmode.sg_ispeed = baudrate;
X	pbmode.sg_ospeed = baudrate;
X
X        if(mfd != -1) {
X            ioctl(mfd, TIOCSETP, &pbmode);
X            ioctl(mfd, TIOCSETC, &pcmode);
X	}
X    }
X
X    switch(pbmode.sg_ospeed){
X    case  B300: return(300);
X    case B1200: return(1200);
X    case B2400: return(2400);
X    case B9600: return(9600);
X    }
X
X    fprintf(tfp,"Impossible error in baud rate.\n");
X    return(0);
X}
X
X/*
X *  The following routine is used to hang up the modem.  This is
X *  accomplished by setting the baud rate to 0.  According to my
X *  documentation on termio, setting the baud rate to zero will
X *  result in DTR not being asserted.  This hangs up some (most?)
X *  modems.  If not, the second part of the routine sends the Hayes
X *  modem "escape" and then a hangup command.
X */
X
Xvoid hangup()
X{
X    if (mfd == -1)
X	return;
X
X    fprintf(tfp,"<< HANGUP >>\r\n");
X    fflush(stdout);
X
X    sleep(2);			/* Allow for "escape guard time" */
X    send_slowly(HAYSATT);	/* Send modem escape command */
X
X    sleep(3);			/* More "escape guard time" */
X    send_slowly(HAYSHUP);	/* Send hangup command */
X}
X
X/* Opens the modem port and configures it. If the port string is
X * already defined it will use that as the modem port;  otherwise it
X * gets the environment variabele MODEM. Returns 0 for sucess
X * or -1 on error.
X */
X
Xstatic int	  opened = 0;
X
Xint mopen()
X{
X    char *p, *getenv();
X
X    cpmode();
X
X    if(port[0] == '\0'){
X        if((p = getenv("MODEM")) == NULL)
X            return(-1);
X        strcpy(port, p);
X    }
X
X    if (lock_tty())
X	return -1;
X
X    if((mfd = open(port, O_RDWR)) < 0)
X        return -1;
X
X    ioctl(mfd, TIOCSETP, &pbmode);
X    ioctl(mfd, TIOCSETC, &pcmode);
X
X    opened++;
X
X    return(0);
X}
X
X/* Attach standard input and output to the modem port. This only gets called
X * after a fork by the child process; which then exec's a program that uses
X * standard i/o for some data transfer protocol. (To put this here is actually
X * a kludge, but I wanted to keep the modem-specific stuff in a black box.)
X */
Xmattach()       /* attach standard i/o to port */
X{
X    dup2(mfd, 0);       /* close local stdin and connect to port */
X    dup2(mfd, 1);       /* close local stdout and connect to port */
X
X    close(mfd);         /* close the old port descriptor */
X}
X
Xint readbyte(seconds)
Xint seconds;
X{
X    return trminp(mfd, seconds);
X}
X
X/*
X *   Read a byte using bitmask
X */
X
Xread_mbyte(secs)
X{
X    int c;
X
X    return (c = readbyte(secs)) == -1 ? -1 : c & bitmask;
X}
X
X/* Output a byte to the modem port.
X * All data sent to the modem is output through this routine.
X */
Xsendbyte(ch)
Xint ch;
X{
X    char c = ch & 0xff;
X
X    write(mfd, &c, 1);
X}
X
Xsend_mbyte(ch)
X{
X    sendbyte(ch & bitmask);
X}
X
X/* Dial a phone number, using proper format and delay.
X */
X
Xstatic char *last_nbr = NULL;
Xchar *strdup();
X
Xdial(s)
Xchar *s;
X{
X    char buffer[WBSIZE];
X
X    if (last_nbr) 
X	free(last_nbr);
X
X    last_nbr = strdup(s);
X
X    sprintf(buffer, DIALSTR, s);
X    send_slowly(buffer);
X}
X
Xredial(s)
X char *s;
X{
X    char *s;
X
X    if (last_nbr == NULL) {
X	fprintf(tfp,"REDIAL FAILURE\r\n");
X	return 1;
X    }
X
X    s = strdup(last_nbr);
X    dial(s);
X    free(s);
X    return 0;
X}
X
Xsend_slowly(s)
X char *s;
X{
X    int i;
X
X/*
X *  This busy-waiting, normally a bad idea on a multi-tasking system,
X *  was used because sleep(1) is way too much of a delay.
X */
X    while(*s){
X        send_mbyte(*s++);
X        for(i = 0; i < MDELAY; i++)
X            ;
X    }
X}
X
X/*
X *  I have had requests to support the LCK..ttyxx files that CU and UUCP
X *  tend to support.  I do not have any need for such code, and am not
X *  exactly sure how the code should work.  I have therefore placed these
X *  two entry points for some enterprising coder to code so that the lock
X *  files may be created and destroyed.
X *
X *  lock_tty() returns non-zero if the lock file exists (prevents XCOMM from
X *  running).
X *
X *  unlock_tty() deletes the lock file.
X *
X *  Simple, eh?
X */
X
Xlock_tty()
X{
X    return 0;
X}
X
Xunlock_tty()
X{
X}
/
echo x - xcscrpt.c
sed '/^X/s///' > xcscrpt.c << '/'
X/*
X *    Program	xcscrpt.c	Script handler for xcomm
X *   Author	larry gensch, ESQ  December 4, 1987
X *
X *   This code is released to the public domain
X */
X
X#include <stdio.h>
X#include <time.h>
X#include <signal.h>
X
X#include "xcomm.h"
X
X#define	MAX_PATH	256
X#define	MAX_LINE	128
X
Xextern char *getenv();
Xextern FILE *tfp;
X
Xstatic FILE *spt_file, *cf;
Xstatic int waitfor_time = 0;
Xstatic int tty_flag = 1;
Xstatic int echo_flag = 0;
Xstatic int if_flag = 0;
Xstatic int waitflag = 0;
Xstatic int captflag = 0;
Xstatic jmp_buf here;
X
Xstatic void k_waitfor(), k_transmit(), k_pause(), k_exit(), k_quit();
Xstatic void k_if(), k_goto(), k_else(), k_endif(), k_redial();
Xstatic void k_dial(), s_set(), hangup(), k_echo(), k_tty(), k_capture();
X
X/* globals */
X
Xint linkflag = 0;
Xint scriptflag = 0;
X
Xstatic struct kw kw[] = {
X	{ "waitfor",    k_waitfor },
X	{ "transmit",	k_transmit },
X	{ "pause",	k_pause },
X	{ "exit",	k_exit },
X	{ "if",		k_if },
X	{ "else",	k_else },
X	{ "endif",	k_endif },
X	{ "hangup",	hangup },
X	{ "goto",	k_goto },
X	{ "dial",	k_dial },
X	{ "redial",	k_redial },
X	{ "quit",	k_quit },
X	{ "set",	s_set },
X	{ "echo",	k_echo },
X	{ "capture",	k_capture },
X	{ "tty",	k_tty },
X	{ NULL,		NULL }};
X
Xstatic void newsigint();
X
Xdo_script(file)
X char *file;
X{
X    int quiet = 0;
X
X    if (open_script(file)) {
X	fprintf(tfp, "Cannot find script file %s\r\n", file);
X	return;
X    }
X
X    if (linkflag == 2) {
X	quiet = 1;
X	linkflag = 0;
X    }
X
X    if (!quiet) {
X	fprintf(tfp, "<<< RUNNING SCRIPT >>>\r\n");
X	fflush(tfp);
X    }
X
X    if_flag = 0;
X    echo_flag = 0;
X    captflag = 0;
X    tty_flag = 1;
X    scriptflag = 1;
X
X    if (setjmp(here) == 0) {
X	signal(SIGINT, newsigint);
X	intdel(1);
X
X	for (eof_flag = 0; !eof_flag; )
X	    get_line();
X    }
X
X    fclose(spt_file);
X    if (captflag)
X	fclose(cf);
X
X    if (!quiet) {
X	fprintf(tfp, "<< SCRIPT COMPLETE >>\r\n");
X	fflush(tfp);
X    }
X
X    linkflag = 0;
X    scriptflag = 0;
X
X    signal(SIGINT, SIG_IGN);
X    intdel(0);
X
X    return;
X}
X
Xstatic void newsigint()
X{
X    signal(SIGINT, SIG_IGN);
X
X    eof_flag = 1;
X    fprintf(tfp, "\r\nUser Abort...\r\n");
X    fflush(tfp);
X    longjmp(here);
X}
X
Xstatic char path[MAX_PATH];
X
Xstatic open_script(file)
X char *file;
X{
X    char *home;
X
X    eof_flag = 0;
X
X    if (spt_file = fopen(file, "r"))
X	return 0;
X    
X    if ((home = getenv("HOME")) == NULL)
X	return 1;
X
X    sprintf(path, "%s/%s", home, file);
X
X    if (spt_file = fopen(path, "r"))
X	return 0;
X
X    return 1;
X}
X    
Xstatic char wf[MAX_LINE];
X
Xstatic get_line()
X{
X    int i;
X
X    getline(spt_file);
X
X    if (eof_flag)
X	return;
X
X    getword();
X    lc_word(word);
X
X    if (echo_flag) {
X	if (strcmp(word, "transmit"))
X	    fprintf(tfp, "%s\r\n", line);
X	else
X	    fprintf(tfp, "TRANSMIT...\r\n");
X	fflush(tfp);
X    }
X	
X    if (word[0] == '\0')		/* Ignore blank lines */
X	return;
X    if (word[0] == '#')			/* Ignore comments */
X	return;
X
X    if (word[strlen(word) - 1] == ':')	/* Ignore labels */
X	return;
X
X    if (if_flag == -1) {
X	if (strcmp(word, "else") && strcmp(word, "endif"))
X	    return;
X    }
X
X    for (i = 0; kw[i].keyword != NULL; i++)
X	if (strcmp(kw[i].keyword, word) == 0) {
X	    (*kw[i].rtn)();
X	    return;
X	}
X
X    fprintf(tfp, "UNDEFINED COMMAND: \"%s\"\r\n", word);
X    fflush(tfp);
X    eof_flag = 1;
X    return;
X}
X
Xstatic void k_waitfor()
X{
X    long t;
X    int c;
X    char *ptr = wf;
X
X    quote_flag = 1;
X    getword();
X    quote_flag = 0;
X    if (eof_flag)
X	return;
X
X    if (word[0] == '\0') {
X	fprintf(tfp, "No argument to WAITFOR command\r\n");
X	fflush(tfp);
X	eof_flag = 1;
X	return;
X    }
X
X    strcpy(wf, word);
X    lc_word(wf);
X
X    getword();
X    if (eof_flag)
X	return;
X
X    if (word[0])
X	waitfor_time = atoi(word);
X    else
X	waitfor_time = 30;
X
X    t = time(NULL) + waitfor_time;
X
X    ptr = wf;
X    while (t != time(NULL) && !eof_flag) {
X	if ((c = read_mbyte(1)) == -1)
X	    continue;
X
X	if (tty_flag)
X	    fputc(c, tfp);
X
X	if (captflag)
X	    fputc(c, cf);
X
X	if (mklow(c) != *ptr) {
X	    ptr = wf;
X	    continue;
X	}
X
X	if (*++ptr == '\0') {
X	    waitflag = 1;
X	    return;
X	}
X    }
X
X    waitflag = 0;
X}
X
Xstatic void k_transmit()
X{
X    char *ptr;
X
X    quote_flag = 1;
X    getword();
X    quote_flag = 0;
X    if (eof_flag)
X	return;
X
X    if (word[0] == '\0') {
X	fprintf(tfp, "No argument to TRANSMIT command\r\n");
X	fflush(tfp);
X	eof_flag = 1;
X	return;
X    }
X
X    send_slowly(word);
X}
X
Xstatic void k_pause()
X{
X    int pause_time;
X
X    getword();
X    if (eof_flag)
X	return;
X
X    if (word[0] == '\0')
X	pause_time = 5;
X    else
X	pause_time = atoi(word);
X
X    sleep(pause_time);
X}
X
Xstatic void k_quit()
X{
X    do_exit(0);		/* Terminate XCOMM */
X}
X
Xstatic void k_exit()
X{
X    eof_flag = 1;
X}
X
Xstatic char label[WBSIZE];
X
Xstatic void k_goto()
X{
X    int found = 0, i;
X
X    getword();
X    if (word[0] == '\0') {
X	fprintf(tfp, "No argument for GOTO: %s\r\n", line);
X	fflush(tfp);
X	eof_flag++;
X	return;
X    }
X
X    strcpy(label, word);
X    lc_word(label);
X
X    rewind(spt_file);
X    while (!found) {
X	getline(spt_file);
X	if (eof_flag)
X	    break;
X
X	getword();
X	if (word[0] == '\0' || word[0] == '#')
X	    continue;
X
X	if (word[i = (strlen(word)-1)] != ':')
X	    continue;
X
X	word[i] = '\0';
X
X	lc_word(word);
X	found = (strcmp(word, label) == 0);
X    }
X
X    if (eof_flag) {
X	fprintf(tfp, "Label %s not found\r\n", label);
X	fflush(tfp);
X	eof_flag++;
X	return;
X    }
X
X    if_flag = 0;			/* reset IF flag */
X}
X
Xstatic if_negate = 0;
X
Xstatic void k_if()
X{
X    char *ptr;
X
X    if (if_flag) {
X	fprintf(tfp, "Nested IF statements not allowed\r\n");
X	fflush(tfp);
X	eof_flag++;
X	return;
X    }
X
X    if_negate = 0;
X    getword();
X    if (word[0] == '\0') {
X	fprintf(tfp, "No condition on IF statement\r\n");
X	fflush(tfp);
X	eof_flag++;
X	return;
X    }
X
X    lc_word(word);
X    if (strcmp(word, "not") == 0) {
X	if_negate = 1;
X	getword();
X	if (word[0] == '\0') {
X	    fprintf(tfp, "No condition on IF statement\r\n");
X	    fflush(tfp);
X	    eof_flag++;
X	    return;
X	}
X    }
X
X    if (word[0] == '!') {
X	if_negate = 1;
X	ptr = word + 1;
X    } else
X	ptr = word;
X
X    if (strcmp(ptr, "waitfor") == 0) {
X	if_flag = if_test(waitflag);
X	return;
X    }
X
X    if (strcmp(ptr, "linked") == 0) {
X	if_flag = if_test(linkflag);
X	return;
X    }
X
X    fprintf(tfp, "Undefined IF condition %s\r\n", ptr);
X    fflush(tfp);
X    eof_flag++;
X    return;
X}
X
Xstatic if_test(cond)
X int cond;
X{
X    if (if_negate)
X	cond = !cond;
X
X    if (cond)
X	return 1;
X    else
X	return -1;
X}
X
Xstatic void k_else()
X{
X    if (!if_flag) {
X	fprintf(tfp, "ELSE not within IF\r\n");
X	fflush(tfp);
X	eof_flag++;
X	return;
X    }
X
X    if_flag = -if_flag;
X}
X
Xstatic void k_endif()
X{
X    if (!if_flag) {
X	fprintf(tfp, "ENDIF not wihtin IF\r\n");
X	fflush(tfp);
X	eof_flag++;
X	return;
X    }
X
X    if_flag = 0;
X}
X
Xstatic void k_dial()
X{
X    getword();
X
X    if (word[0] == '\0') {
X	fprintf(tfp, "DIAL command must have an argument\r\n");
X	fflush(tfp);
X	eof_flag++;
X	return;
X    }
X
X    dial(word);
X}
X
Xstatic void k_redial()
X{
X    if (redial()) {
X	eof_flag++;
X	return;
X    }
X}
X
Xstatic void k_echo()
X{
X    set_onoff(&echo_flag);
X    return;
X}
X
Xstatic void k_capture()
X{
X    int val = captflag;
X
X    set_onoff(&captflag);
X    if (eof_flag)
X	return;
X
X    if (val == captflag)
X	return;
X	
X    if (captflag == 0)
X	fclose(cf);
X    else {
X	if ((cf = fopen(captfile, "a")) == NULL) {
X	    fprintf(tfp, "Cannot open capture file %s\r\n");
X	    fflush(tfp);
X	    eof_flag++;
X	    return;
X	}
X    }
X}
X
Xstatic void k_tty()
X{
X    set_onoff(&tty_flag);
X    return;
X}
X
/
echo x - xcsubs.c
sed '/^X/s///' > xcsubs.c << '/'
X/*
X *  Module:	xcsubs.c	XCOMM Subroutines
X *
X *  Revisions:
X *
X *  2.2		lg	Now uses /dev/tty instead of stdin/stdout
X *  2.2		lg	Added command parser
X */
X
X#include <stdio.h>
X#include <ctype.h>
X#include <signal.h>
X#include <sgtty.h>
X
X#include "xcomm.h"
X
Xextern struct sgttyb newbmode, sigbmode;
Xextern struct tchars newcmode, sigcmode;
Xextern FILE *tfp;	/* Local terminal */
Xextern int tfd;		/* Local terminal */
X
Xchar line[WBSIZE];	/* Input line */
Xchar word[WBSIZE];	/* Parsed word */
Xchar *wptr, *lptr;	/* Word and line pointers */
Xint quote_flag = 0;	/* Indicates special processing during parse */
Xint eof_flag = 0;	/* Indicates EOF during getline() processing */
X
Xchar *strdup(str)	/* duplicate a string and return pointer to copy */
Xregister char *str;
X{
X    extern char *strcpy(),*malloc();
X    char *new;
X
X    if ((new = malloc(strlen(str))) != NULL)
X	strcpy(new,str);
X    return (new);
X}
X
Xsendstr(p)              /* send a string to the port */
Xregister char *p;
X{
X    while(*p)
X        send_mbyte(*p++);
X}
X
X/* Do the fork call, packaging the error return so that the caller
X * need not have code for it.
X */
Xforkem()
X{
X    int i;
X
X    if((i = fork()) < 0){
X        fprintf(tfp,"XCOMM: Fork failed");
X        longjmp(erret, 1);
X    }
X    return(i);
X}
X
X/* Convert uppercase characters to lowercase, (without
X * mangling non-uppercase characters), in a portable manner.
X */
Xmklow(c)
Xint c;
X{
X    if(isupper(c))
X        return(tolower(c));
X    return(c);
X}
X
X/* This is an string input routine to be used
X * when the raw terminal mode is in effect.
X */
Xgetsome(s)
Xchar *s;
X{
X    int c, i;
X
X    if (s == line) {
X	lptr = line;
X	memset(line, 0, WBSIZE);
X    }
X
X    i = 0;
X    while((c = coninp()) != '\r' && c != '\n') {
X        if(c == '\b'){
X            if(i > 0){
X                i--;
X                fprintf(tfp,"\b \b");
X            } else
X                putc(7, tfp);
X            continue;
X        }
X        s[i++] = c;
X        putc(c, tfp);
X    }
X    s[i] = '\0';
X    putc(' ', tfp);
X}
X
X/*
X *   Erase current line without moving downwards
X */
Xerasln()
X{
X    int i;
X
X    fprintf(tfp,"\r\033[0K\r");
X    fflush(tfp);
X}
X
X/* 
X *  Throw away all input characters until no more are sent.
X */
Xpurge()
X{
X    while(readbyte(1) != -1)
X        ;
X}
X
X/*
X *  Parse the "line" array for a word
X */
Xgetword()
X{
X    char *ptr = word;
X    char quote = '\0';
X    int bflag = 0, qflag = 0;
X    int c;
X
X    *ptr = '\0';
X    if (eof_flag || *lptr == '\0')
X	return;
X
X    while (isspace(*lptr))
X	lptr++;
X
X    wptr = lptr;
X
X    if (*lptr == '\0')
X	return;
X
X    if (*lptr == '\'' || *lptr == '\"')
X	quote = *lptr++;
X    else
X	quote = '\0';
X
X    for (; *lptr != '\0'; lptr++) {
X	if (quote) {
X	    if (*lptr == '\0') {
X		word[0] = '\0';
X		fprintf(tfp, "Unmatched quote: %s\r\n", line);
X		eof_flag = 1;
X		return;
X	    }
X	    if (*lptr == quote)
X		break;
X	} else if (!qflag && isspace(*lptr))
X	    break;
X
X	if (bflag)
X	    *ptr++ = *lptr & 0x1f;
X	else if (qflag)
X	    *ptr++ = *lptr;
X	else if (quote_flag && *lptr == '^')
X	    bflag = 1;
X	else if (quote_flag && *lptr == '\\')
X	    qflag = 1;
X	else
X	    *ptr++ = *lptr;
X    }
X
X    lptr++;
X    *ptr = '\0';
X}
X
X/*
X *  Make the specified word all lower case
X */
Xlc_word(ptr)
X char *ptr;
X{
X    while (*ptr) {
X	*ptr = mklow(*ptr);
X	ptr++;
X    }
X}
X
X/*
X *  Input a line from the specified file
X */
Xgetline(fp)
X FILE *fp;
X{
X    int l;
X
X    memset(line, 0, WBSIZE);
X
X    if ((fgets((lptr=line), WBSIZE, fp)) == NULL) {
X	eof_flag = 1;
X	line[0] = '\0';
X    }
X
X    l = strlen(line);		/* Purge newline if found */
X    if (l--) {
X	if (line[l] == '\n')
X	    line[l] = '\0';
X    }
X}
X
X/*
X *  trminp() is used as a single-character terminal input routine
X */
Xstatic alrm() {}  /* do nothing */
X
Xint trminp(fd, seconds)
Xint fd, seconds;
X{
X    static char rxbuf[BUFSIZ], *p;      /* BUFSIZ is defined in stdio.h */
X    static int count = 0;
X
X    if(count > 0){
X        count--;
X        return(*p++ & 0xff);
X    }
X    if(seconds > 0){
X        signal(SIGALRM, alrm);
X        alarm((unsigned)seconds);
X    }
X    if((count  = read(fd, p = rxbuf, BUFSIZ)) < 1)
X        return(-1);
X    if(seconds > 0)
X        alarm(0);
X    count--;
X    return(*p++ & 0xff);
X}
X
X/*
X *  coninp() gets a single character from the local terminal
X */
Xconinp()
X{
X    int c;
X
X    fflush(tfp);
X
X    while ((c = trminp(tfd, 0)) == -1)
X	;
X
X    return c;
X}
X
Xintdel(flag)
X{
X    if (flag) {
X	ioctl(tfd, TIOCSETP, &sigbmode);
X	ioctl(tfd, TIOCSETC, &sigcmode);
X    } else {
X	ioctl(tfd, TIOCSETP, &newbmode);
X	ioctl(tfd, TIOCSETC, &newcmode);
X    }
X}
/
echo x - xcterm.c
sed '/^X/s///' > xcterm.c << '/'
X/* xcterm.c             XCOMM terminal mode
X */
X
X#include <stdio.h>
X#include <ctype.h>
X#include <signal.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include "xcomm.h"
X
X/* globals */
Xchar captfile[NMSIZE]  = CAPTFILE;	/* capture file's name */
Xchar phonefile[NMSIZE] = PHFILE;	/* phone number file's name */
Xint cismode = CIS_INIT;			/* Respond to CIS "ENQ" */
Xextern FILE *tfp;			/* Local terminal pointer */
X
X/* locals */
Xstatic FILE *cfp;			/* capture file pointer */
Xstatic int ctop;			/* index top of capture buffer */
Xstatic int parent_pid = 0;		/* ID of parent process */
Xstatic int child_pid = 0;		/* ID of child process */
Xstatic int capture = FALSE;		/* are we capturing or not ? */
Xstatic char *cbuffp = NULL;		/* capture buffer pointer */
Xstatic jmp_buf  rtm;
Xstatic int frsttrm = 1;
X
X/* forward declarations */
Xvoid toggle(), cleanup(), newbmask(), cisbmode();
X
Xstatic int spt_flag = 0;
Xstatic char *spt_file;
Xstatic char spt_buf[100];
X
Xterminal()
X{
X    int c;
X
Xreterm:
X    setjmp(rtm);
X
X    if (cismode > 1)
X	return;
X
X    spt_flag = 0;		/* reset scripting flag */
X    parent_pid = getpid();
X
X    /* split into read and write processes */
X    if((child_pid = forkem()) == 0){
X	/* child, read proc: read from port and write to tty */
X        cfp = NULL;
X        signal(SIGIOT, toggle);
X        signal(SIGTERM, cleanup);
X        signal(SIGQUIT, newbmask);
X        while(1){
X            while((c = read_mbyte(0)) == -1)
X                ;
X	    if (cismode && c == ENQ) {
X		cismode = 2;
X		cleanup();
X	    }
X            fputc(c, tfp);
X	    fflush(tfp);
X            if(capture && c != '\r')
X		fputc(c, cfp);
X        }
X	/*NOTREACHED*/
X    }
X
X    /* parent, write proc: read from tty and write to port */
X    signal(SIGHUP, cisbmode);
X    fprintf(tfp,"Entering Terminal Mode\r\n");
X    if (frsttrm) {
X	termhelp();
X	frsttrm = 0;
X    }
X
X    do {
X        switch(c = coninp()) {
X        case TOGCHAR:		/* signal child to toggle buffer */
X	    kill(child_pid, SIGIOT);
X	    break;
X
X        case DIVCHAR:		/* divert a file through modem port */
X	    divert();
X	    break;
X
X	case SCRPCHR:		/* execute a script file */
X	    if (get_script())
X		break;
X	    spt_flag = 1;
X	    goto fillicide;
X
X        case PSELECT:		/* select and dial a phone number */
X	    pselect();
X	    if (!spt_flag) 
X		break;
X
X        case ENDCHAR:		/* signal child to cleanup and exit */
Xfillicide:
X	    c = ENDCHAR;
X	    signal(SIGHUP, SIG_IGN);
X	    kill(child_pid, SIGTERM);
X	    break;
X
X	case HUPCHAR:		/* Hangup */
X	    hangup();
X	    break;
X
X	case HELPCHAR:		/* Terminal Mode Help */
X	    termhelp();
X	    break;
X
X	case '\n':		/* See if NL translation in effect */
X	    if (nlmode)
X		c = '\r';
X
X        default:		/* just send the character to the port */
X	    send_mbyte(c);
X	    break;
X        }
X    } while(c != ENDCHAR);
X
X    while(wait((int *) 0) >= 0)		/* wait for the read process to die */
X        ;
X
X    if (spt_flag) {
X	do_script(spt_file);
X	goto reterm;
X    }
X
X    fprintf(tfp,"\r\nLeaving Terminal Mode\r\n");
X}
X
Xstatic void cisbmode()
X{
X    cismode = 2;
X    signal(SIGHUP, SIG_IGN);
X
X    longjmp(rtm);
X}
X
X/* The next three functions are only run by the port read process (child).
X * They handle the capture buffer.
X *
X * toggle capture status
X */
Xstatic void toggle()
X{
X    char *malloc();
X
X    signal(SIGIOT, toggle);	/* set signal for next toggle */
X
X    if(capture) {
X	fclose(cfp);
X        capture = FALSE;
X	fprintf(tfp, "\r\n<<CAPTURE BUFF CLOSED>>\r\n");
X    } else {
X	if ((cfp = fopen(captfile, "a")) == NULL) {
X	    fprintf(tfp,"\r\n<<CAN'T OPEN CAPTURE FILE>\r\n");
X	} else {
X	    capture = TRUE;
X	    fprintf(tfp,"\r\n<<CAPTURE BUFF OPEN>>\r\n");
X	}
X    }
X    fflush(tfp);
X}
X
X/* cleanup, flush and exit
X */
Xstatic void cleanup()
X{
X    if(capture){
X        fclose(cfp);
X	fprintf(tfp,"\r\n<<CAPTURE BUFF SAVED>>\r\n");
X    }
X
X    if (cismode == 2)
X	fprintf(tfp,"\r\n<<FILE TRANSFER REQUEST>>\r\n");
X
X    fflush(tfp);
X    kill(parent_pid, SIGHUP);
X    exit(0);
X}
X
Xstatic void newbmask()
X{
X    if (bitmask == 0xff)
X	bitmask = 0x7f;
X    else
X	bitmask = 0xff;
X    
X    if (child_pid)
X	kill(child_pid, SIGQUIT);
X    else
X	fprintf(tfp,"<<SET TO %d BIT MASK>>\r\n", bitmask == 0xff ? 8 : 7);
X}
X
Xstatic FILE *openfile(name)
Xchar *name;
X{
X    FILE *fp;
X    char *home, fullname[NMSIZE];
X
X    if (fp = fopen(name, "r"))
X	return fp;
X
X    if (home = (char *) getenv("HOME")) {
X	sprintf(fullname, "%s/%s", home, name);
X	return fopen(fullname, "r");
X    } else
X	return NULL;
X}
X
X/*
X *  Select a script file.  If the file exists, execute it, otherwise
X *  exit with a non-zero return.
X */
Xget_script()
X{
X    fprintf(tfp,"\r\nEnter script file: ");
X    fflush(tfp);
X    getsome(spt_buf);
X    if (spt_buf[0] == '\0') {
X	fprintf(tfp,"\r\n<<ABORTED>>\r\n");
X	fflush(tfp);
X	return 1;
X    }
X    if (access(spt_buf, 04)) {
X	fprintf(tfp," <<- NOT FOUND\r\n");
X	fflush(tfp);
X	return 1;
X    }
X    fprintf(tfp, "\r\n");
X    fflush(tfp);
X    linkflag = FALSE;
X    spt_file = spt_buf;
X    return 0;
X}
X
X/* pselect() and divert() are used by the port write process (parent).
X * They produce other sources of input for the port than the keayboard.
X *
X * Select phone number and dial it. (Hayes - type modem)
X * Phone number is stored in a file, one number per line, with the phone
X * number first, followed by some space and then any comments.
X * VERSION 2.1 - If line contains BAUD=nnnn, the baud rate will be changed
X *		 to nnn.  If lines contains BITS=7 or BITS=8, the bitmask
X *		 will be changed to the specified value.
X * VERSION 2.2 - If line contains SCRIPT=nnnnn, the designated script file
X *		 will be started.
X */
Xpselect()
X{
X    FILE *fp;
X    char buffer[WBSIZE + 1];
X    int c, i;
X
X    if ((fp = openfile(phonefile)) == NULL) {
X	fprintf(tfp,"Phonelist file '%s' not in current or home directory\r\n",
X		phonefile);
X	return;
X    }
X
X    fprintf(tfp,"d=dial x=exit SPACE=next\r\n");
X    do {
X        if(fgets(buffer, WBSIZE, fp) == NULL){
X            rewind(fp);
X            if(fgets(buffer, WBSIZE, fp) == NULL){
X                fprintf(tfp,"Empty phonelist file!\r\n");
X                return;
X            }
X        }
X        erasln();
X        for(i = 0; i < WBSIZE && (c = buffer[i]) != '\n'; i++)
X            fputc(c, tfp);
X        fputc('\r', tfp);
X	fflush(tfp);
X	while ((c=fgetc(tfp))!=' ' && c!='d' && c!='x')
X		fflush(tfp);
X	fflush(tfp);
X    } while(c == ' ');
X    erasln();
X    fflush(tfp);
X    fclose(fp);
X    if(c == 'd') {
X	fprintf(tfp,"dialing ...\r\n");
X	dialbuf(buffer);
X    } else {
X	fprintf(tfp,"exiting ...\r\n");
X    }
X}
X
Xdialbuf(buf)
Xchar *buf;
X{
X    char *nbr, *ptr, *ptr1, c;
X
X    if (ptr = strstr(buf, "BAUD=")) {
X	ptr += 5;
X	if (mbaud(ptr) == -1) 
X	    fprintf(tfp,"Invalid BAUD= value\r\n");
X	else
X	    fprintf(tfp,"<<SET TO %d BAUD>>\r\n", mbaud(NULL));
X    }
X
X    if (ptr = strstr(buf, "BITS=")) {
X	ptr += 5;
X	switch (*ptr) {
X	case '7':
X	    if (bitmask == 0xff)
X		newbmask();
X	    break;
X	case '8':
X	    if (bitmask == 0x7f)
X		newbmask();
X	    break;
X	default:
X	    fprintf(tfp,"Invalid BITS= value\r\n");
X	}
X    }
X
X    while (isspace(*buf) && *buf)
X	    buf++;
X
X    if (!(*buf))
X	    return;
X
X    for (nbr = buf; !isspace(*buf) && *buf; buf++)
X	    ;
X    
X    c = *buf;
X    *buf = '\0';
X    dial (nbr);
X
X    *buf = c;
X    if (ptr = strstr(buf, "SCRIPT=")) {
X	ptr += 7;
X	ptr1 = ptr;
X	while (!isspace(*ptr1) && *ptr1 != '\0')
X	    ptr1++;
X	*ptr1 = '\0';
X	spt_file = ptr;
X	spt_flag = 1;
X	linkflag = TRUE;
X    }
X}
X
X/* Divert file into input stream, with optional delay after each newline.
X */
Xdivert()
X{
X    FILE *fp;
X    char buffer[NMSIZE];
X    int c, i;
X
X    fprintf(tfp,"\r\nFile? ");
X    getsome(buffer);
X    if((fp = fopen(buffer, "r")) == NULL){
X        fprintf(tfp,"\r\nCan't open %s for input\r\n", buffer);
X        return;
X    }
X    fprintf(tfp,"\r\nThe default delay of %d can be ", DRIBBLE);
X    fprintf(tfp,"changed by typing the new delay after the 'y'\r\n");
X    fprintf(tfp,"Delay after every newline (y/n)? ");
X
X    getsome(buffer);
X    fprintf(tfp,"\r\n");
X
X    i = 0;
X    if(mklow(buffer[0]) == 'y' && (i = atoi(buffer + 1)) == 0)
X        i = DRIBBLE;
X    while((c = getc(fp)) != EOF)
X        if(c != '\n')
X            send_mbyte(c);
X        else {
X            send_mbyte('\r');
X            if(i)
X                sleep((unsigned) i);
X        }
X    fclose(fp);
X}
X
Xstatic char *thtext[] = {
X    "Special Terminal Mode Keys:\r\n",
X    "    ALT-c        toggle capture mode\r\n",
X    "    ALT-d        dial a phone number\r\n",
X    "    ALT-h        hangup phone\r\n",
X    "    ALT-g        execute a script file\r\n",
X    "    ALT-s        send file through modem\r\n",
X    "    ALT-x        exit terminal mode\r\n",
X    "    ALT-?        help (this message)\r\n",
X    NULL
X};
X
Xtermhelp()
X{
X    char **txtp;
X
X    for (txtp=thtext; *txtp != NULL; txtp++) {
X	fprintf(tfp,*txtp);
X    }
X    fflush(tfp);
X}
/