#define Copyright         "Copyright 1994  Ed Casas"

#define Version		  "efax v 0.6b"

#define EFAXVER "1.05"
static char* versionString = "$VER: EFax (Amiga) " EFAXVER;

/*

  1.05 - fixed cr/lf problem in log reporting
       - fixed problem with readChar with a 0 timeout.

    Copyright (C) 1994  Ed Casas

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    You may contact the author by e-mail at: edc@ee.ubc.ca,
    or by mail at: 2629 West 3rd Ave, Vancouver, BC, Canada,
    V6K 1M4.

*/

const char *Usage =
  "Usage:\n"
  "  %s [ option ]... [ -r pat | -t num file... ]\n"
"Options:\n"
  "  -p portname load config from avm:servers.cfg\n"
  "  -c cap  set file format or receive capabilites to cap\n"
  "  -d dev  use modem on device dev\n"
  "  -u unit unit number for device\n"
  "  -g cmd  exec \"/bin/sh -c cmd\" for data calls\n"
  "  -i str  send modem command ATstr at start\n"
  "  -l id   set local indetification to id\n"
  "  -o opt  use protocol option opt:\n"
  "      1     use class 1 modem commands\n"
  "      2     use class 2 modem commands\n"
  "      a     if first [data mode] answer attempt fails retry as fax\n"
  "      e     ignore errors in modem initialization commands\n"
  "      r     reverse bit order on receive\n"  
  "      t     reverse bit order on transmit\n"  
  "      x     use XON instead of DC2 to trigger reception\n"
  "      z     add 100 ms to pause before each modem comand (cumulative)\n"
  "  -q ne   ask for retransmission if more than ne errors per page\n"
  "  -s      share (unlock) modem device while waiting for call\n"
  "  -v lvl  print messages of type in string lvl (ewinahcm)\n"
  "  -w      don't answer phone, wait for OK or CONNECT instead\n"
  "  -x fil  use uucp-style lock file fil\n"
  "  -z str  send modem command ATstr when done\n"
  "Commands:\n"
  "  -r      answer and receive fax into files pat.001, pat.002, ... \n"
  "  -t      send fax image files file... to telephone num\n"
  ;

#include <ctype.h>		/* ANSI C */
#include <errno.h>
#include <signal.h>    
#include <stdarg.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>		/* UNIX */
#include <sys/types.h>

#include <sys/stat.h>		/* required for SYSV (?) */

#include "voice.h"
#include "callback_proto.h"
#include "config_proto.h"
#include "faxfile.h"
#include "simplerexx.h"
#include "debug_proto.h"

#include "growstring.h"
struct GrowString msgLine;

extern long __stack = 10000L; /* max. stack size */

extern struct CallBack globalCallBack;
extern int globalAborted;

AREXXCONTEXT OutgoingRexx;

void setSendSerialParms(void);
void setReceiveSerialParms(void);

int verbose = 0;

struct faxout
{
	FILE	       *fp;
	int		fax_byte;
	int		fax_weight;
	int		pages;
	int		raw;	/* 1: no header, 2: no EndOfPage */
	char	       *stdiobuf;
};

struct faxout* FaxHandle = 0;

typedef struct faxin {
	int	 raw;
	FAXHDR	 faxhdr;
	FAXHDR	 pagehdr;
	int	 row;
	int	 endoffile;
	int	 eols;
	int	 rawzeros;
	int	 shdata;
	int	 shbit;
	int	 kludge;
	int	 reversebits;
	int	 stretch;
	unsigned char  *bitp;
	unsigned char	bitval;

} FAXIN;

extern FAXIN faxin;

/* prototypes */
void releaseResources(void);
void allocateResources(void);
enum ReturnStatus waitForTimeout(int seconds, int microseconds, struct CallBack* cb);
enum ReturnStatus readChar(char* c, int timeout, struct CallBack* cb);
int writeToSerial(char* s, int len);
void unReadChar(void);

extern char serialName[];
extern int unitNumber;
int dialTone = 1;
int faxHardwareHandshaking = 1;

char portNameToLoad[40];

#ifndef FILENAME_MAX
#define FILENAME_MAX 255
#endif

#ifndef u_char
#define u_char unsigned char
#endif

/* constants... */

#define FAXFILE "serial.device"  /* default fax modem device */
#define LOGF     stderr     /* session log written to this stream */

			    /* delays/timeouts, in deciseconds */
#define TO_RESET  26	    /* timeout for modem reset commands (per Hayes) */
#define T_CMD     1	    /* pause before each modem command */
#define TO_A      1200	    /* dial/answer (Phase A) - modem may T.O. first */

#define TO_DATAF  150	    /* software adaptive answer data connect */

#define T1 350		    /* T.30 T1 - waiting for DIS/DCS before Phase B */
#define T2 60		    /* T.30 T2 - waiting for frame in Phase B */
#define T3S 30		    /* T.30 response timeout (not T3) */
#define T4 30	            /* T.30 T4 - between [re]transmissions of DIS */

#define TO_DRAIN 140	    /* drain 4k modem buffer at 2400bps (tx) */
#define TO_RTCMD 10	    /* return to command mode after DLE-ETX (rx) */
#define TO_FT    31 	    /* max delay after +F[TR][MH] command */
#define TO_CHAR  51	    /* per data character (max FILL length) */
#define TO_ABRT  20	    /* max delay after sending abort sequence */

#define TO_C2B    450	    /* Class 2 DIS to CONNECT:(DCS+TCF+CFR)xretries */
#define TO_C2X    20	    /* Class 2 wait for XON: 2/5 of 5s timeout */
#define TO_C2PP   200	    /* Class 2 wait for ppr: (ppm+ppr)x3retries + 2 */
#define TO_C2R    600	    /* Class 2 receive: (TCF+FTT)x11 retrains + 5 */
#define TO_C2EOR  60	    /* Class 2 end of data rx (2 retrans x 3 s) */


#define CMDBUFSIZE 128      /* longest possible modem command or response */
#define DEFDISLEN 3	    /* length of DIS initially transmitted */
#define DEFCAP 1,3,0,2,0,0,0,0	/* default local capabilities */
#define DLE_ETX "\020\003"  /* DLE-ETX (end of data) string */
#define FNAMFT "%m%d%H%M%S" /* strftime() format for default file name */
#define HDBLKFLAG '#'	    /* prefix to force HDB (text) lock file style */
#define IDLEN 20	    /* length of T.30 identification strings */
#define LOCKPOLLDELAY 15    /* seconds between checks of lock files */
#define MAXDIS 8	    /* maximum DIS frames sent without response (T1) */
#define MAXFIFLEN 125	    /* max FIF len = MAXFRLEN - (adx+ctl+FCF) - FCS */
#define MAXFRLEN 130        /* max frame length = 3.45s x 300 bps / 8 */
#define MAXGETTY 512        /* maximum length of ``getty'' (-g) command */
#define MAXIOPT  100        /* maximum # of modem initialization commands */
#define MAXLKFILE 16	    /* maximum number of lock files */
#define MAXMSGBUF 8192	    /* maximum size of message buffer */
#define MAXNULLS 2	    /* maximum consecutive received nulls saved */
#define MAXTSTAMP 80	    /* maximum length of a time stamp */
#define MAXTRAINERR 0	    /* maximum errors allowed in training check data */
#define MAXTRAIN 2	    /* maximum training retries at lowest speed */
#define MAXRETRY 3	    /* maximum retries of unacknowledged commands */
#define MINWRITE  128       /* minimum bytes per write() to modem */
#define NCAP 8              /* number of fields in a capability string */
#define NTXRETRY  3	    /* maximum re-sends per page */
#define RCVBUFSIZE 1024	    /* read up to this many bytes at a time from fax */
#define SNDBUFSIZE 1024	    /* maximum bytes to write at a time to fax */
#define T4RTCLEN  9	    /* end of page mark for T.4 coded images */
#define T4RTC    "\000\020\001\000\020\001\000\020\001"

enum  cchar {
  NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS,  HT,  LF,
  VT,  FF,  CR,  SO,  SI,  DLE, XON, DC2, XOFF,DC4, NAK,
  SYN, ETB, CAN, EM,  SUB, ESC, FS,  GS,  RS,  US } ;

const char *prompts[] = {		/* modem responses that are prompts */
  "OOK", "-CONNECT FAX", "CCONNECT", "NNO CARRIER", "EERROR",
  "NNO DIALTONE", "BBUSY", "NNO ANSWER", "E+FCERROR", 0 } ;

enum promptcodes {			/* codes for modem prompts */
   BUSY = 'B', CONNECT = 'C', OK = 'O', RING = 'R', NO = 'N',
   ERROR = 'E' } ;

enum ttymodes				/* serial port modes */
   { COMMAND, DROPDTR, SEND, ORIGINAL } ;

			    /* signals to be caught so can hang up phone */
const int catch [] = { /*SIGHUP,*/ SIGINT, /*SIGQUIT, SIGIOT, SIGSEGV, SIGALRM,
			   SIGTERM,*/ SIGFPE, 0 } ;

typedef int cap [ NCAP ] ;		/* remote/local capabilities */

                                        /* capability fields... */
enum  captype {	               VR, BR, WD, LN, DF, EC, BF, ST } ;
const int capmax [ NCAP ] = {   1,  7,  3,  2,  3,  2,  1,  7 } ;
					/* & maximum values */

					/* characters per second for br */
const int cps [ 8 ] = { 300, 600, 900, 1200, 1500, 1800, 900, 1200 } ;

					/* next br = fallback [ br ] */
const int fallback [ 8 ] = { 0, 0, 1, 2, 7, 4, 3, 6 } ;

					/* minimum scan time in ms  */
const int delay [ 8 ] = { 0 , 5, 10, 10, 20, 20, 40, 40 } ;

/* Table to convert between T.30 DIS/DCS/DTC FIF and Class 2-like
   capability codes. Uses br=6, 7 for V.17 at 7200, 9600. */

typedef const struct t30tabstruct
{ 
  char *name ; 
  u_char byte, shift, mask ; 
  u_char captodis[8], distocap[16], captodcs[8], dcstocap[16] ; 
} t30tabst ;

#define X 0xff				/* invalid values */

t30tabst t30tab [ NCAP ] = {
  { "vr", 1, 1, 0x01, { 0, 1 } , { 0, 1 } , { 0, 1 } , { 0, 1 } },	 
  { "br", 1, 2, 0x0f, 
      { 0, 4, 12, 12, 13, 13 } ,
      { 0, X, X, X, 1, X, X, X, 3, X, X, X, 3, 5, 3, X } ,
      { 0, 4, 12, 8, 5, 1 } ,
      { 0, 5, 5, X, 1, 4, 4, X, 3, 7, X, X, 2, 6, X, X } } ,
  { "wd", 2, 6, 0x03, { 0, 2, 1 } , { 0, 2, 1, 2 } ,
      { 0, 2, 1 } , { 0, 2, 1, 2 } },
  { "ln", 2, 4, 0x03, { 0, 2, 1 } , { 0, 2, 1, X } ,
      { 0, 2, 1 } , { 0, 2, 1, X } },
  { "df", 1, 0, 0x01, { 0, 1 } , { 0, 1 } , { 0, 1 } , { 0, 1 } },
  { "ec", 3, 4, 0x03, { 0, 2, 2 } , { 0, X, 2, X } , 
      { 0, 3, 2 } , { 0, 0, 2, 1 } }, 
  { "bf", 5, 5, 0x01, { 0, 1 } , { 0, 1 } , { 0, 1 } , { 0, 1 } },
  { "st", 2, 1, 0x07, 
      { 7, 4, 3, 2, 6, 0, 5, 1 } , { 5, 7, 3, 2, 1, 6, 4, 0 } ,
      { 7, 4, X, 2, X, 0, X, 1 } , { 5, 7, 3, 1, X, X, X, 0 } } 
} ;

					/* values of capability fields */
const char *capvaluestr [ NCAP ] [8] = {
  { " 98lpi", "196lpi" } , 
  { " 2400bps", " 4800bps", " 7200bps", " 9600bps", "  12kbps", "14.4kbps",
    "7200bpsV.17", "9600bpsV.17" } ,
  { "8.5\"/215mm", " 10\"/255mm", " 12\"/303mm" } ,
  { "11\"/A4", "14\"/B4", " any  " } ,
  { "1D" , "2D" }, { "   -   ", "ECM-256", "ECM-64 " }, { " - ", "BFT" },
  { "0ms", "5ms", "10/5ms", "10ms", "20/10ms", "20ms", "40/20ms", "40ms" }
} ;

/* T.30 control frames */

enum frametype {	    
 DIS=0x01, CSI,	NSF=0x04,
 CFR=0x21, FTT,
 MCF=0x31, RTN, RTP, PIN, PIP,
 DCS=0x41, TSI,	NSS=0x44,
 CRP=0x58, DCN=0x5f,
 EOM=0x71, MPS, EOP=0x074, PRI_EOM=0x79, PRI_MPS, PRI_EOP=0x7c,
 DTC=0x80, CIG, NSC=0x84
 } ;

/* Class 1 commands to [receive=0/transmit=1] [data=0/training=1] for
   [baud rate=BR]. */

const char *c1cmd [ 2 ]  [ 2 ] [ 8 ] = { 
{ { "+FRM=24", "+FRM=48", "+FRM=72", "+FRM=96", "+FRM=122", "+FRM=146" ,
    "+FRM=74", "+FRM=98" } ,
  { "+FRM=24", "+FRM=48", "+FRM=72", "+FRM=96", "+FRM=121", "+FRM=145" ,
    "+FRM=73", "+FRM=97" } } ,
{ { "+FTM=24", "+FTM=48", "+FTM=72", "+FTM=96", "+FTM=122", "+FTM=146" ,
    "+FTM=74", "+FTM=98", } ,
  { "+FTM=24", "+FTM=48", "+FTM=72", "+FTM=96", "+FTM=121", "+FTM=145" ,
    "+FTM=73", "+FTM=97" } }
} ;


/* World's fastest T.4 decoder.  Implements a tree search.  Each bit of
   each byte is tested using the T4TST(byte,bitmask) macro. This macro
   invokes the macro T4CODE(len) when it detects a valid T.4 code or on the
   first EOL after an invalid code.  The macro's parameter len will be the
   run length, -1 on a normal EOL or -2 for the EOL following an error.  1D
   coding only.

   As per T.4, run lengths >63 (make-up codes) should be added to the
   subsequent (terminating) code.  The first run of a line is white and
   colors alternate.

   Each node of the tree contains pointers to the next node for a '0' or a
   '1' bit.  NULL pointers indicate terminal nodes in which case, the run
   length (or -1 or -2) is given by the zlen or olen member and the search
   continues with the node given by the znext or onext pointer.

   The decoding tree, stored in packed format (LSB to MSB plus guard bit),
   is built by calling mktree() which returns a pointer to the initial
   value of 't4p' to be used by the T4TST macro.

*/

typedef struct dtree { 
  struct dtree *zero, *one, *onext, *znext ; 
  short int zlen, olen ;
} dtree ;

#define T4TST(c,m) t4p = ( c & m ) ? \
( t4p->one  ? t4p->one  : ( T4CODE ( t4p->olen ), t4p->onext ) ) : \
( t4p->zero ? t4p->zero : ( T4CODE ( t4p->zlen ), t4p->znext ) ) ;

#define T4CTST(c) T4TST(c,0x80) ;  T4TST(c,0x40) ; \
                  T4TST(c,0x20) ;  T4TST(c,0x10) ; \
                  T4TST(c,0x08) ;  T4TST(c,0x04) ; \
                  T4TST(c,0x02) ;  T4TST(c,0x01) ;

#define T4NODES 228

const short int pkdt4tree [] = {

7,1,29,53,243,263,9,1,11,1,13,1,15,1,17,1,19,1,21,1,23,1,25,1,  
27,1,27,2,-2,31,49,43,33,75,35,37,4,10,121,39,41,173,4,63,4,0,  
79,45,95,47,93,4,1,71,51,167,4,2,55,61,57,59,4,3,67,69,4,4,63,65,  
4,5,83,4,6,4,7,2,128,4,8,4,9,87,73,109,4,11,89,77,99,4,12,117,  
103,81,107,4,13,85,2,64,4,14,4,15,4,16,4,17,119,91,203,4,18,4,19,127,  
97,131,4,20,129,137,101,143,4,21,123,105,145,4,22,4,23,147,111,155,  
113,115,4,24,149,151,4,25,153,4,26,4,27,163,4,28,165,209,125,  
4,29,4,30,4,31,4,32,4,33,4,34,133,135,4,35,4,36,4,37,4,38,139,141,  
4,39,4,40,4,41,4,42,4,43,4,44,4,45,4,46,4,47,4,48,4,49,4,50,4,51,4,52,  
4,53,4,54,157,2,192,159,161,4,55,4,56,4,57,4,58,4,59,4,60,4,61,4,62,  
175,169,181,171,197,2,256,2,320,2,384,2,1664,177,179,185,2,448,2,512,  
183,191,2,576,189,187,2,640,2,704,2,768,2,832,2,896,193,195,2,960,2,1024,  
2,1088,2,1152,199,201,2,1216,2,1280,2,1344,2,1408,205,207,2,1472,2,1536,  
2,1600,2,1728,235,211,213,217,215,223,2,1792,221,219,229,2,1856,2,1920,  
2,1984,2,2048,225,227,2,2112,2,2176,2,2240,2,2304,231,233,2,2368,2,2432,  
2,2496,2,2560,237,1,239,1,241,1,241,2,-1,245,261,247,265,249,267,  
273,251,271,253,255,2,12,281,257,303,259,307,2,0,2,1,2,4,2,3,2,2,  
2,6,2,5,269,2,7,2,9,2,8,2,10,2,11,291,275,277,279,2,13,283,287,2,14,  
2,15,299,313,285,383,2,16,289,309,2,17,357,423,293,295,321,297,317,  
2,18,363,327,301,331,2,19,305,341,2,20,339,2,21,349,333,311,347,2,22,  
315,351,2,23,361,379,319,389,2,24,323,371,325,367,2,25,397,395,329,  
2,26,2,27,2,28,2,29,335,337,2,30,2,31,2,32,2,33,2,34,2,35,343,345,  
2,36,2,37,2,38,2,39,2,40,2,41,2,42,2,43,353,355,2,44,2,45,2,46,2,47,  
359,393,2,48,2,49,2,50,2,51,365,377,2,52,403,399,369,401,2,53,  
373,4,64,375,409,2,54,407,405,2,55,381,387,2,56,415,385,391,2,57,2,58,  
417,2,59,2,60,419,2,61,4,256,2,62,2,63,4,128,4,192,421,4,320,  
4,384,4,448,4,512,4,576,4,640,4,704,4,768,4,832,4,896,4,960,411,413,  
4,1024,4,1088,4,1152,4,1216,4,1280,4,1344,4,1408,4,1472,4,1536,4,1600,  
4,1664,4,1728,449,425,427,431,429,437,4,1792,435,433,443,4,1856,4,1920,  
4,1984,4,2048,439,441,4,2112,4,2176,4,2240,4,2304,445,447,4,2368,4,2432,  
4,2496,4,2560,451,1,453,1,455,1,455,2,-1,
-1 } ;


/* Program options. Not changed except in main(). */

cap local = { DEFCAP } ;	  /* -c  local capabilities */
char *argv0 = "?" ;		  /*     program name (argv[0]) */
char *fnamepat= FNAMFT ;	  /* -r  pattern for received file names */
char **fnames = 0 ;		  /* -t  files to send */
char *getty = "startbbs %d" ;		  /* -g  command for data calls */
char *iopt[ MAXIOPT ] ;		  /* -i  modem init commands */
char *zopt[ MAXIOPT ] ;		  /* -z  modem reset commands */
char *lkfiles [ MAXLKFILE+1 ] ;	  /* -x  lock files  */
char localid  [ IDLEN + 1 ] ;	  /* -l  local ID (telephone #) */
char *verb = "ewin" ;		  /* -v  verbosity level */
int c1 = 0 ;			  /* -o1 use class 1 protocol */
int softadans = 0 ;		  /* -oa use software adaptive answer */
int igniniterr=0 ;		  /* -oe ignore initialization errors */
int niopt=0 ;			  /*     number of init commands */
int nzopt=0 ;			  /*     number of reset commands */
int cmdpause = T_CMD ;		  /* -oz delay before each init command */
int maxpgerr = 10 ;		  /* -q  maximum errors per page */
int nfiles=0 ;			  /*     number of files to send */
int share = 0 ;			  /* -s  share fax device */
int waitforcall = 0 ;		  /* -w  wait for incoming call */
u_char startchar = DC2 ;	  /* -ox character to start reception */



/* Bit ordering: serial devices transmit LS bit first.  T.4/T.30 says MS
   bit is sent first. `Normal' order therefore reverses bit order. */

u_char                           /* bit reversal lookup tables*/
   reversebits [ 256 ] , normalbits [ 256 ] , 
   *rxbitorder = normalbits , *txbitorder = normalbits ;

dtree *t4tree ;				/* T.4 decoding tree */

/* fax stream variables (private). */

u_char faxobuf [ SNDBUFSIZE ] ,		/* fax output stream. */
  *faxop=faxobuf , *faxoq=faxobuf+SNDBUFSIZE ;

int faxdev=-1 ;				/* fax device file descriptor. */

/* session state variables. */

FILE *file=0 ;				/* current image file  */
cap remote ;                            /* remote capabilities  */ 
cap session ;		                /* session capabilities */
char remoteid [ IDLEN + 10 ] ;		/* ID (tel no) of remote station */
int crate = 19200 ;		        /* CONNECT rate */
int datamode=0 ;			/* true if answered in data mode */
int decimate = 0 ;			/* drop 1/2 lines to get 98 lpi */
int good=1 ;				/* page received OK */
int hsc=-1 ;				/* hangup status code (Class 2) */
int minlen = 0 ;			/* pad lines to this many bytes */


/* Functions... */

/* Print time stamp. */

void tstamp( void )
{
  static time_t last = 0 , now ;
  char *fmt = 0, tbuf [ MAXTSTAMP ] ;

  now = time ( 0 ) ;

  if ( now - last >   0 ) fmt = " (%M:%S)" ;
  if ( now - last > 600 ) fmt = " %c" ;

  if ( fmt ) {
    strftime ( tbuf , MAXTSTAMP , fmt , localtime( &now ) ) ;
    appendGrowString(&msgLine, tbuf);
    last = now ; 
  }
}


/* For systems without strerror(3) */

char *strerror( int i )       
{
  extern int sys_nerr;
  extern char *sys_errlist[];
  return ( i >= 0 && i < sys_nerr ) ? sys_errlist[i] : "Unknown Error" ;
}


/* Return string corresponding to character c. */

char *cname ( u_char c ) 
{
#define CNAMEFMT "<0x%02x>"
#define CNAMELEN 6+1
  static char *cname [ 256 ] = {		/* character names */
  "<NUL>","<SOH>","<STX>","<ETX>", "<EOT>","<ENQ>","<ACK>","<BEL>",
  "<BS>", "<HT>", "<LF>", "<VT>",  "<FF>", "<CR>", "<SO>", "<SI>", 
  "<DLE>","<XON>","<DC2>","<XOFF>","<DC4>","<NAK>","<SYN>","<ETB>",
  "<CAN>","<EM>", "<SUB>","<ESC>", "<FS>", "<GS>", "<RS>", "<US>" } ;
  static char names[ (127-32)*2 + 129*(CNAMELEN) ], *p=names ;
  static int i=0 ;
    
  if ( ! i ) 
    for ( i=32 ; i<256 ; i++ ) {
      cname [ i ] = p ;
      sprintf ( p, i<127 ? "%c" : CNAMEFMT , i ) ;
      p += strlen ( p ) + 1 ;
    }

  return cname [ c ] ;
} 

/* Return name of frame of type 'fr'. */

const char *frname ( int fr )
{
static struct framenamestruct {  int code ;  const char *name ; } 
framenames [] = {
 {DIS,"DIS"},{CSI,"CSI"},{NSF,"NSF"},{CFR,"CFR"},{FTT,"FTT"},{MCF,"MCF"},
 {RTN,"RTN"},{RTP,"RTP"},{PIN,"PIN"},{PIP,"PIP"},{DCS,"DCS"},{TSI,"TSI"},
 {NSS,"NSS"},{CRP,"CRP"},{DCN,"DCN"},{EOM,"EOM"},{MPS,"MPS"},{EOP,"EOP"},
 {PRI_EOM,"PRI-EOM"},{PRI_MPS,"PRI-MPS"},{PRI_EOP,"PRI-EOP"},
 {DTC,"DTC"},{CIG,"CIG"},{NSC,"NSC"}, {0,0} }, *p ;

  for ( p=framenames ; p->code && p->code != fr ; p++ ) ;
  return p->code ? p->name : "UNKNOWN" ;
}


/* Print a message with a variable number of printf()-type arguments if the
   first character appears in the verb[ose] string.  Other leading
   characters and digits do additional actions: + allows the message to be
   continued on the same line, '-' buffers the message instead of printing
   it, E, and W expand into strings, S prints the error message for the
   most recent system error, 0-4 set the return value, a space is skipped
   and ends prefix.  Returns 0 if no prefix digit. */

int msg ( const char *fmt, ... ) 
{
  char debug[128];
  int err=0, dolf=1, flush=1 ;
  char *ps="", *pe="", *pw="" ;
  const char *p ;
  static int atcol1=1 ;

  va_list ap ;

  if (atcol1) newGrowString(&msgLine);

  va_start ( ap, fmt ) ;

  for ( p=fmt ; *p ; p++ ) {
    switch ( *p ) {
    case ' ': p++ ; goto print ;
    case 'A': break ;				   /* program args */
    case 'C': break ;				   /* commands/responses */
    case 'M': break ;				   /* modem dialogue */
    case 'N': break ;				   /* negotiation */
    case 'E': pw = "Error: " ; break ;
    case 'H': break ;				   /* HDLC frame data */
    case 'I': break ;				   /* information */
    case '+': dolf = 0 ; break ;
    case '-': flush = 0 ; break ;
    case 'R': break ;				   /* reception errors */
    case 'S': ps = strerror ( errno ) ; break ;
    case 'W': pw = "Warning: " ; break ;
    case '0': case '1': case '2': case '3': case '4': case '5': 
      err = *p - '0' ; break ;
    default: goto print ;
    }
  }

  print:  
  if ( strchr ( verb , tolower ( *fmt ) ) ) {
    if ( atcol1 ) {
      sprintf ( debug , "%s: %s%s" , argv0 , pe , pw ) ;
      appendGrowString(&msgLine, debug);
    }
    vsprintf( debug, p , ap ) ;
    appendGrowString(&msgLine, debug);
    appendGrowString(&msgLine, ps);

    if ( ( atcol1 = dolf ) ) {
      tstamp ( ) ; 
      showError('e', msgLine.s);
      deleteGrowString(&msgLine);
    }
  }

  va_end ( ap ) ;
  return err ;
}


/* Unpack the decoding tree. If the LS bit is 1, the entry is a
   pointer to the next node in a code word, else it is a terminal
   node and the value is a pointer to where to continue scanning
   for the next code word and the following value is the run
   length (or -1 or -2). */

dtree *unpktree ( void )
{
  static dtree nodes [ T4NODES ], *p ;
  const short int *l ;
  for ( p=nodes, l=pkdt4tree ; *l > 0 ; p++ ) {
    if ( p - nodes >= T4NODES ) {
      /* fprintf ( stderr, "error in T.4 decoding tree\n" ) ; */ break ;
    }
    if ( *l & 1 ) { p->zero = nodes + *l++/2 ; }
    else { p->znext = nodes + *l++/2 ; p->zlen = *l++ ; } ;
    if ( *l & 1 ) { p->one  = nodes + *l++/2 ; } 
    else { p->onext = nodes + *l++/2 ; p->olen = *l++ ; } ;
  }
  return nodes + 1 ;		/* search starts at node 1 (white runs) */
}


/* millisecond delays (for systems without usleep). */

void msleep ( int t )
{
  waitForTimeout(t / 1000, (t % 1000) * 1000, &globalCallBack);
}

/* faxgetc returns the next character
   from the fax device or EOF after idle time t. */

/* t is in tenths of a second */
/* returns EOF on timeout */
int faxgetc(int t) {
  enum ReturnStatus status;
  char c;

  if (t < 0) {
    t = 6000; /* 60 seconds */
    while ((status = readChar(&c, t, &globalCallBack)) == TimedOut);
    return c;
  } else {
    status = readChar(&c, t, &globalCallBack);
    if (status == TimedOut) return(EOF);
    return c;
  }
}

/* A function like faxgetc that also removes DLE escapes, detects DLE-ETX
   terminators and fixes bit order. Does not ignore invalid escape
   sequences. Returns the character read, EOF on error/timeout, or -2 on
   DLE-ETX.  */

int faxgetdatac ( int t )
{
  int c ;
  return ( c = faxgetc(t) ) == DLE && ( c = faxgetc(t) ) == ETX ? -2 : 
    normalbits [ c & 0xff ] ;
}


/* Get a modem response into buffer s, storing up to n bytes.  The response
   begins with most recent non-control character and ends with CR/LF.
   Returns s or null if times-out in t deciseconds or on i/o error. Trace
   messages are buffered to reduce possible timing problems. */

char *faxgets( char *s , int n , int t )
{
  int c=0, sta=0 ;
  char *p = s ;

  while ( sta < 4 ) {
    if ( ( c = faxgetc( t ) ) == EOF ) break ;
    c &= 0x7f ;
    if ( ! sta ) msg ( "M-+ [" ) ; 
    msg ( "M-+ %s" , cname ( c ) ) ;
    switch ( sta ) {
    case 0 :
    case 1 : sta =               ( iscntrl ( c ) ? 1 : (p=s, 2) ) ; break ;
    case 2 : sta = c == CR ? 3 : ( iscntrl ( c ) ? 1 :       2  ) ; break ;
    case 3 : sta = c == LF ? 4 : ( iscntrl ( c ) ? 1 : (p=s, 2) ) ; break ;
    default: msg ( "Ecan't happen (faxgets)" ) ;
    }
    if ( sta == 2 && p < s+n-1 ) *p++ = c ;
  }

  *p = 0 ;
  if ( p >= s+n-1 ) msg ( "W- modem response overflow" ) ;
  if ( sta ) msg ( "M- %s]" , c == EOF ? "<timeout>" : "" ) ;

  return c == EOF ? 0 : s ;
}


/* Write the fax output buffer to fax output device. To allow zero-fill
   underflow padding, should be called after writing the EOL zero
   byte. Warns if called with full buffer (presumably called from
   faxputc()). Returns 0 or EOF on error. */

int faxflush ( void )
{ 
  int n=0 ;
  u_char *p = faxobuf ;

  if ( faxop >= faxoq ) msg ("Wfax output buffer overflow") ;

  n = writeToSerial(p, faxop - p);

  return n >= 0 ? (faxop = faxobuf , 0) : (msg ("ESfax device write:") , EOF) ;
} 

/* faxputc() is a macro like putc().  It returns the character written or
   EOF on error. */

#define faxputc(c) ( faxop >= faxoq && faxflush() == EOF ? EOF : \
		    (u_char) ( *faxop++ = (c) ) )

/* faxobytes() returns number of bytes in fax output buffer */

#define faxobytes() ( faxop - faxobuf ) 


/* A function like faxputc but also escapes DLEs and sets proper bit
   order. */

int faxputdatac ( u_char c )
{
  int x ;
  x  = normalbits [ c ] ;
  if ( x == DLE ) x = faxputc ( x ) ;
  return x < 0 ? x : faxputc ( x ) ;
}


/* Send character or string to modem immediately (for commands).  Returns
   like putc() and puts(). */

int faxputcnow ( char c ) 
{ 
  return faxputc ( c ) < 0 ? EOF : ( faxflush() ? EOF : c ) ; 
}

int faxputs ( const char *s )
{
  int n=0 ;
  while ( s && *s && ( n = faxputcnow ( *s++ ) ) != EOF ) ;
  return n ;
}

/* Convert capability string to cap[ability]. Returns 0 or 2 on errors. */

int str2cap ( char *s, cap c )
{
  int err=0, i=0, n ;
  char *p ;

  for ( n=0, p=s ; ! err && *p && i<NCAP ; p++ )
    if ( isdigit ( *p ) ) c [ n++ ] = *p - '0' ;
    else if ( isspace ( *p ) || *p == ',' ) ;
    else err = msg ("E2invalid character (%c) in (%s)", *p , s ) ;

  if ( n < NCAP ) msg ( "W0missing value(s) in capability \"%s\"", s ) ;

  for ( i=0 ; i<n ; i++ ) 
    if ( c [ i ] > capmax [ i ] || c [ i ] < 0 ) 
      err = msg ( "E2%s = %d is %s", s , t30tab[i].name , c [ i ], 
		c[i]<0 ? "negative" : "too big" ) ;
  
  return err ;
}

/* Print cap[ability] 'c' using text values and prefix 's'. */

void printcap ( char *s , cap c )
{
  int i ;
  msg ( "N-+ %s" , s ) ;
  for ( i=0 ; i<NCAP ; i++ ) 
    msg ( "N-+  %s" , c[i] > capmax [ i ] || c[i] < 0 ?  "ERROR" :
	 capvaluestr [ i ] [ c[i] ] ) ;
  msg ( "N-" , s ) ;
}


/* Convert a cap[ability] 'c' to a DIS/DCS/DTC FIF 'fif' of 'len' bytes.
   Converts into DIS format if 'isdis' is true, else into DCS/DTC
   format. Returns 0 or 3 on internal error (bad conversion tables). */

int mkdis ( cap c , u_char *fif , int len , int isdis ) 
{
  int err=0, i, j, k ;
  t30tabst *p ;

  if ( len < 3 || len > 5 ) 
    len = msg ( "W3bad DCS/DIS length (%d) set to 3" , len ) ;

  fif[0] = 0 ;
  fif[1] = isdis ? 0xc0 : 0x40 ;
  for ( i=2 ; i<len-1 ; i++ ) fif[i] = 0x01 ;       /* add extension bits */
  fif[i] = 0 ;

  for ( i=0 ; p=t30tab+i, i<NCAP ; i++ ) {

    j = c [ i ] ;
    if ( j > capmax [ i ] || j < 0 ) {
      j = 0 ;
      err = msg ( "E3mkdis: bad %s = %d set to 0", p->name, j ) ;
    }
    k = ( isdis ? p->captodis : p->captodcs ) [ j ] ;
    if ( k == X ) {
      k = 0 ;
      err = msg ( "E3mkdis: can't happen (invalid %s)", p->name ) ;
    }
    if ( p->byte < len ) fif [ p->byte ] |=  k << p->shift ;
  }
  return err ;
}

/* Return length of DIS/DTC FIF (counts extension bits). */

int dislen ( u_char *fif )
{
  int n ;
  for ( n=3 ; fif [ n-1 ] & 0x01 ; n++ ) ;
  return n ;
}

/* Convert received DIS/DCS/DTC FIF to cap. Returns 0 or 3 on error (bad
   DIS/DCS field). */

int mkcap ( u_char *fif, cap c , int dis ) 
{
  int err=0, i, j, k, len ;
  t30tabst *p ;

  len = dislen ( fif ) ;

  for ( i=0 ; i<NCAP ; i++ ) {
    p=t30tab+i ;
    if ( p->byte >= len ) {
      c [ i ] = 0 ;
    } else {
      j = fif [ p->byte ] >> p->shift & p->mask ;
      k = ( dis ? p->distocap : p->dcstocap ) [ j ] ;
      if ( k == X ) {
	c [ i ] = 0 ;
	err = msg("E3mkcap: bad %s field (%d) set to 0", p->name, j) ;
      } else { 
	c [ i ] = k ;
      }
    }
  }
  return err ;
}


/* Compute compatible local/remote capabilities. As side effect, sets
   minimum line length and decimation. This routine is used by the sending
   station only (or both in Class 2). Returns 0 if OK or 3 if no compatible
   settings possible. */

int mincap ( cap local, cap remote, cap session )
{
  int err=0, i ;
  int msttab[2][8] = { { 0,1,3,3,5,5,7,7 } , { 0,1,1,3,3,5,5,7 } } ;

  for ( i=0 ; i<NCAP && i!=ST ; i++ )
    session[i] = remote[i] < local[i] ? remote[i] : local[i] ;

  session[ST] = msttab [ session[VR] ] [ remote[ST] ] ;

  minlen = (cps[session[BR]] * delay[session[ST]] + 500) / 1000 ;

  decimate = local[VR] == 1 && session[VR] == 0 ;

  printcap ( "local  ", local ) ;
  printcap ( "remote ", remote ) ;
  printcap ( "session", session ) ;
  msg ( "N- padding to %d bytes/line.%s", minlen, 
       decimate ? " reducing 198->96 lpi." : "" ) ;

  if ( local[WD] != session[WD] || local[LN] != session[LN] || 
      local[DF] != session[DF] ) 
    err = msg ("E3can't convert image to remote capabilities" ) ;

  return err ;
}


/* Search for a match to the string s in a null-terminated array of
   possible prefix strings pointed to by p.  The first character of each
   prefix string is skipped.  Returns pointer to the table entry or NULL if
   not found.  */

char *strtabmatch ( char **p, char *s )
{
  while ( *p && strncmp ( *p+1 , s , strlen ( *p+1 ) ) ) p++ ;
  return ( ! *p || **p == '-' ) ? NULL : *p ;
}


/* Send command to modem and check responses.  Collects pending
   (unexpected) responses and then pauses for inter-command delay
   (cmdpause) if t is negative.  Writes command s to modem if s is not
   null.  Reads responses and terminates when a response is one of the
   prompts in responses[] or if times out in t deciseconds.  Notes and
   processes important class 2 responses (FPTS: Page Transfer Status, FHNG:
   HaNGup status, and FDCS: Current Session capabilities).  Repeats command
   if detects a RING response (probable collision). Returns the first
   character of the matching prefix string (e.g. 'O' for OK, 'C' for
   CONNECT, etc.)  or EOF if no such response was received within timeout
   t. */

int cmd ( const char *s , int t )
{
  char buf [ CMDBUFSIZE ] , *p = "" ;
  int ppr ;

//  if (s)
//    msg("Icmd(\"%s\", %d)", s, t);
//  else
//    msg("Icmd(0, %d)", t);

//  msg("IhasChar=%d, numChars=%d", hasChar(), numCharsInBuffer());
  

 retry:

  while ( s && faxgets ( buf , CMDBUFSIZE , t<0 ? cmdpause : 0 ) )
    msg ( "W- unexpected response \"%s\"", buf ) ;

  msg ( s ? "C- command  \"%s\"" : "C- waiting" , s ) ;

  if ( s ) { faxputs ( "AT" ) ; faxputs ( s ) ; faxputcnow ( CR ) ; }

  while ( t && ( p = faxgets( buf , CMDBUFSIZE , t<0 ? -t : t ) ) ) {

    msg ( "C- response \"%s\"" , p ) ;

    if ( ! strncmp ( buf, "DATA" , 4 ) ||
	! strncmp ( buf, "CONNECT DATA" , 12 ) )
      datamode = 1 ;
    if ( ! strncmp ( buf, "+FDCS:", 6 ) && ! str2cap ( buf+6 , session ) ) 
      mincap ( local, session, session ) ; /* set decimation & minlen */

    if (strncmp(buf, "+FCSI:", 6) == 0 || strncmp(buf, "+FTSI:", 6) == 0) {
      strcpy(remoteid, buf + 6);
    }

    if ( sscanf ( buf , "+FPTS: %d", &ppr ) > 0 ) good = ppr & 1 ;
    sscanf ( buf, "+FHNG: %d", &hsc ) ;
    sscanf ( buf, "CONNECT %d", &crate ) ;

    if ( ( p = strtabmatch ( (char**) prompts , buf ) ) ) break ;

    if ( ! strncmp ( buf, "RING", 4 ) ) { msleep(100) ; goto retry ; }
  }

  return p ? *p : EOF ;
}

/* Send command to modem and wait for either of two possible replies after
   testing (and possibly setting) current error status via err pointer. */

void ckcmd ( int *err, const char *s, int t, int r1, int r2 )
{
  int c ;
  if ( ! *err )
    if ( ( c = cmd ( s, t ) ) != r1 && c != r2 ) 
      if ( c == EOF ) 
	*err = msg ("E3modem command (%s) timed out", s ? s : "none" ) ;
      else
	*err = msg ("E3wrong response to command (%s)", s ? s : "none" ) ;
}

/* Resynchronize modem from an unknown state.  If no immediate response,
   try pulsing DTR low (needs &D{2,3,4}), and cancelling data or fax data
   modes.  In each case, discard any responses for about 2 seconds and then
   try command s.  Returns 0 if OK or 4 if no response.  */

int faxsync( char *s )
{
  int err=0, method=0 ;

  while ( ! err ) {
    switch ( method++ ) {
    case 0 : 
      break ;
    case 1 : 
      msg ("Isync: dropping DTR -- Unsupported on Amiga") ;
      break ;
    case 2 : 
      msg ("Isync: sending escape") ;
      faxputs ( DLE_ETX ) ; 
      msleep ( 1500 ) ;
      faxputs ( "+++" ) ; 
      break ;
    case 3 :
      err = msg ("E4sync: modem not responding") ;
      continue ;
    }
    flushBuffers();
    if ( cmd ( s , -TO_RESET ) == OK ) break ;
  }
  return err ;
} 


/* Terminate session.  Makes sure modem is responding, sends modem reset
   commands, resets fax device to original state, removes lock files. */

int end_session ( void )
{
  int i, err ;
  err = faxsync ( "Q0V1" ) ;

  for ( i=0 ; ! err && i<nzopt ; i++ )
    if ( cmd( zopt[i] , -TO_RESET ) != OK  && ! igniniterr )
      err = msg ("E3modem reset command (%s) failed", zopt[i]) ;

  return err ;
} 
    

/* signal handler: hang up and exit */

void onsig ( int sig ) 
{ 
  msg ( "Eterminating on signal %d", sig ) ; 
  end_session ( ) ;
  msg ("Idone, returning 5") ;
  exit(5) ; 
} 

int getResolution(char* fname) {
  FILE* fp = fopen(fname, "rb");
  if (fp) {
    faxin_open_fp(fp, 0, 0);
    faxin_begin_page(fp);
    fclose(fp);
    return faxin.faxhdr.info.hires;
  }
  return 0;
}


/* Initialize session.  Try locking and opening fax device until opened or
   get error. Then set tty modes, register signal handler, setup modem. */

int begin_session ( char *faxfile )
{
  char txcmd[50];
  int i , err=0;

  if ( ! err ) msg ( "Iopened %s on fd %d", faxfile, faxdev ) ;

  for ( i=0 ; ! err && catch [ i ] ; i++ ) 
    if ( signal ( catch [ i ] , onsig ) == SIG_ERR ) 
      err = msg ( "ES2can't set signal %d handler:", catch [ i ] ) ;
  
  if ( !err ) err = faxsync("Q0V1") ;

  for ( i=0 ; ! err && i<niopt ; i++ )
    if ( cmd( iopt[i] , -TO_RESET ) != OK && ! igniniterr )
      err = msg ("E3modem initialization command (%s) failed", iopt[i]) ;

  if (nfiles) {
    /* we are transmitting */
    local[VR] = getResolution(fnames[0]);
  }

  if (!c1) {
    sprintf(txcmd, "+FDCC=%d,%d,%d,%d,%d,%d,%d,%d", local[VR], local[BR], local[WD], local[LN], local[DF], local[EC], local[BF], local[ST]);

    if (cmd(txcmd, -TO_RESET) != OK && ! igniniterr)
      err = msg("E3fax capability setup (%s) failed", txcmd);
  }

  return err ;
}


/* Position `file' to start of page p (1...nfiles).  Assumes global
   variables 'fnames' and 'nfiles,' have been initialized.  This function
   looks ahead to see if there are more pages and if they are of a
   different format.  Eventually will handle multi-page files with embedded
   format information (e.g. TIFF-F).  Sets ppm to MPS if more pages to be
   sent with same format, EOM if more pages with different format, EOP if
   no more pages. Returns 0 if OK, 2 on errors.  Currently sets ppm to MPS
   or EOP only.  */

int rdpage ( int p , int *ppm )
{
  int err=0 ;

  if ( file && fclose ( file ) ) msg ("ES2file close:") ; 

  if ( p <= nfiles && p > 0 ) {
    if ( ( file = fopen ( fnames[p-1] , "rb" ) ) ) {
      msg ( "Iopened \"%s\" (p %d/%d)", fnames[p-1] , p, nfiles ) ;
      *ppm = p >= nfiles ? EOP : MPS ;
      faxin_open_fp(file, 0, 0);
      faxin_begin_page(file);
    } else {
      err = msg ("ES2 file open\n%s:", fnames[p-1]) ;
    }
  } else {
    err = msg ("E2 bad page number (%d)", p ) ;
  }

  return err ;
}

/* Same as rdpage() but creates new file name and opens `file' for
   writing. If page is -1 removes the most recently opened file. Assumes
   'fnampat' contains the strftime(3) pattern to be used for generating
   file names. Returns 0 if OK, 2 on errors. */

int wrpage ( int p )
{
  int err=0 ;
  static char fname [ FILENAME_MAX + 1 ] , base [ FILENAME_MAX + 1 ] ;
  time_t t ;

  t = time(0)  ;
  if ( ! *base ) strftime( base, FILENAME_MAX, fnamepat, localtime ( &t ) ) ;

//  if ( file && fclose ( file ) ) msg ("ES2file close:") ; 
  if ( file ) {
    faxout_end_page(FaxHandle);
    faxout_close(FaxHandle);
    msg("ES2file close:");
  }

  if ( p == -1 ) {
    if ( remove ( fname ) )
      err = msg ( "ES2deleting file %s", fname ) ; 
    else
      msg ( "Iremoved %s", fname ) ; 
  } else {
    if ( *fname ) msg ( "Ireceived -> %s", fname ) ;
    sprintf ( fname, "%.*s.%03d", FILENAME_MAX - 5, base, p ) ;
    FaxHandle = faxout_open(fname, 0);
    if (FaxHandle) {
      file = FaxHandle->fp;
      err = 0;
    } else err = msg ("ES2opening file\n%s:", fname) ;

    if ( ! err ) msg ( "Iopened file %s", fname ) ;
  }
  return err ;
}


/* Send data for one page. Enable serial port flow control.  Read
   characters from file and pad lines if necessary.  Bit-reverse characters
   and escape DLE before storing in buffer.  Flush buffer if enough
   characters stored.  Append RTC if needed.  Send DLE-ETX and return
   serial port to command mode when done. Returns 0 if OK, non-0 on
   errors. */

int send_data( void )
{
  int done=0, err=0;
  int lastc=0, c ;
  int eol=0, len=0, llen=0, eolcnt=0, rtc=0, skip=0 ;
  int bytes=0, pad=0, linec=0, lines=0 ;
  int i, noise=0 ;
  time_t start, dt ;
  dtree *t4p = t4tree ;

  done = err = 0; 
  start = time(0) ;

  while ( ! done ) {

    if ( ( c = getc ( file ) ) != EOF ) {

#define T4CODE(l) ( l < 0 ? (eol=l,llen=len,len=0) : (len+=l) ) 
      T4CTST(c) ;
#undef T4CODE

      if ( eol ) {
	if ( ! llen ) {
	  if ( ++eolcnt >= 6 ) done = rtc = 1 ; 
	} else {
	  eolcnt=0 ;
	}
	if ( eol == -2 ) 
	  msg ("W- image error (line %d)", lines ) ;
	if ( ! skip ) {
	  lines++ ; 
	  while ( ( linec < minlen || lastc ) ) { 
	    faxputc ( lastc = 0 ) ; 
	    linec++ ; 
	    pad++ ; 
	  }
	  linec = 0 ;
	}
	if ( decimate ) {
	  if ( skip ) {
	    skip = 0 ;
	  } else {
	    faxputc ( lastc = 0 ) ; /* force enough zeroes for next EOL */
	    pad++ ; 
	    skip = 1 ;
	  }
	}
	if ( faxobytes() > MINWRITE ) { 
	  if ( faxflush() ) done=err=2 ;  
	}
	eol=0 ;
      }
      if ( ! skip || ! llen ) {
	faxputc ( lastc = txbitorder [ c ] ) ; 
	linec ++ ;
	bytes++ ;
	if ( lastc == DLE ) { faxputc ( lastc = DLE ) ; linec++ ; pad++ ; }
      }	
    } else {
      done = 1 ;
      if ( ferror ( file ) ) err = msg ("ES2file read:") ;
    }
  }

  if ( ! err && ! rtc ) {
    for ( i=0 ; i<T4RTCLEN ; i++ ) faxputdatac ( (T4RTC)[i] ) ;
    msg ( "I- added RTC (%d bytes)" , T4RTCLEN ) ;
    pad += T4RTCLEN ;
  } 

  for ( ; ( c = faxgetc ( 0 ) ) != EOF ; noise++ ) 
    msg ( "W-+%s" , cname ( c ) ) ; 
  if ( noise ) msg ("W- : %d byte(s) received while sending", noise ) ;
	
  faxputs ( DLE_ETX ) ;		/* and faxflush() */

  ckcmd ( &err, 0, TO_DRAIN, OK, 0 ) ;

  dt = time(0) - start ;
  msg ("Isent %d lines  %d+%d bytes  %d s  %d bps" , 
       lines, bytes, pad, (int) dt, ((bytes+pad)*8)/dt ) ;

  return err ;
}


/* Receive data. Get characters from the modem and check for
   errors/EOF.  Remove long runs of zeroes (T.4 FILL). If in DLE
   escape check for DLE, end of data or protocol violation.
   Otherwise, test for DLE & write the character out.  Check that
   the output file is still OK.  If not, send one CANcel
   character and wait for protocol to complete.  */

int receive_data ( void )
{
  int done=0, err=0, nulls=0, c ;
  int bytes=0, lines=0, nerr=0 ;
  int eol=0, eolcnt=0, len=0, llen=0, lastlen=0, rtc=0 ;
  dtree *t4p = t4tree ;

  faxout_begin_page(FaxHandle, session[VR] /* VR */, 1 /* dirty */);

  while ( ! done ) {

    if ( ( c = faxgetc( TO_CHAR ) ) == DLE ) {
      if ( ( c = faxgetc( TO_CHAR ) ) == ETX ) done = 1 ;
      else if ( c != DLE ) {
	msg ( "W- \"%s\" received after DLE", cname ( c ) ) ;
	continue ;
      }
    }	
    if ( c == EOF ) {
      done = err = msg ("E3fax device read timeout") ;
      continue ;
    } 
    if ( c == 0 ) {
      if ( ++nulls > MAXNULLS ) {
	continue ;
      }
    } else {
      nulls = 0 ;
    }
    if ( ! done && ! rtc ) {
      c = rxbitorder[c];
      putc ( c, file ) ;
      bytes++ ;
    }
#define T4CODE(l) ( l<0 ? (eol=l, llen=len, len=0) : (len+=l) ) 
      T4CTST(c) ;
#undef T4CODE
    if ( eol ) {
      lines++ ;
      if ( ( ( llen != lastlen ) && llen && lastlen && lines > 3 ) ) {
	nerr++ ;
	msg ("R-+ (%d:%d)", lines, llen ) ;
      }
      lastlen = llen ;
      if ( ! llen ) {
	if ( ++eolcnt >= 6 ) rtc = 1 ; 
      } else {
	eolcnt=0 ;
      }
      eol=0 ; 
    }

    if ( ! err && ferror ( file ) ) {
      err = msg ("ES2fax file write:") ;
      faxputcnow ( CAN ) ;
      msg ("Wreceive CANcelled") ;
    } 

  } /* while */
  
  if ( nerr ) msg ("R-  : reception errors" ) ;
  nerr /= 2 ;
  if ( nerr ) msg ("W- %d reception errors", nerr ) ;

  ckcmd ( &err, 0, TO_C2EOR, ( c1 ? NO : OK ) , 0 ) ;
  
  if ( ! err && ! rtc ) {
    fwrite ( T4RTC , sizeof ( u_char ) , T4RTCLEN , file ) ;
    msg ( "I- added RTC (%d bytes)" , T4RTCLEN ) ;
  }

  msg ( "I- received %d lines, %d bytes, %d errors", lines, bytes, nerr ) ;

  good = nerr < maxpgerr ;
  return err ;
}


/* Send training check sequence of n zeroes.  Returns 0 or 2 on error. */

int puttrain ( const char *s , int n )
{
  int i, err=0 ;

  ckcmd ( &err, s , TO_FT , CONNECT, 0 ) ;

  if ( ! err ) {

    for ( i=0 ; ! err && i < n ; i++ ) {
      faxputc ( 0 ) ;
      if ( ! ( i & 63 ) ) faxflush ( ) ;
    }
    faxputc ( 1 ) ;  /* disable zero-fill padding */
    faxputs ( DLE_ETX ) ;

    msg ( "I- sent TCF (%d bytes)", n ) ;

    ckcmd ( &err, 0, TO_DRAIN, OK, 0 ) ;
  }

  return err ;
}

/* Checks n bytes of received training check sequence. Skips first n/16
   bytes.  Returns 0 if OK or ignoring training errors, 1 if too many
   errors/not enough data, or 3 on other errors. */

int gettrain ( const char *s , int n ) 
{ 
  int err=0, c, i, errcnt=0, skip=n/16 ;

  ckcmd ( &err, s , TO_FT , CONNECT, 0 ) ;
  
  for ( i=0 ; ! err && ( c = faxgetdatac ( TO_CHAR ) ) >= 0 ; i++ )
    if ( c != 0 && i >= skip && i < n+skip ) errcnt++ ;

  ckcmd ( &err, 0, TO_RTCMD, NO, 0 ) ;
  
  if ( !err )
    msg ( "I- received TCF (%s: %d error(s) in %d/%d bytes)", 
	 ( err = i<n || errcnt>MAXTRAINERR ) ? "failed" : "passed" , 
	 errcnt, n, i ) ;

  return err ;
}

/* Log sent/received HDLC frame.  Display of these messages is delayed to
   avoid possible timing problems. */

void logfr ( const char *s , const char *nm , u_char *p , int n )
{
  int i=0 ;
  msg ( "I- %s %s", s, nm ) ;
  msg ( n > 10 ? "H- %s %d bytes:" : "H-+ %s %d bytes:" , s, n ) ;
  for ( i=0 ; i<n ; i++ ) {
    msg ( "H-+  %02x" , p[i] & 0xff ) ;
    if ( ( i&0xf ) == 0xf && i != n-1 ) msg ( "H-" ) ;
  }
  msg ( "H-") ;
}

/* Send HDLC control frame of type type.  Extra bits are OR'ed to the frame
   type (FCF) if this frame follows a previous one (no +FTH required) or if
   more frames will follow.  Sets up flag, address, and fax control field
   (FCF) bytes in `buf'.  Sends these plus `len` additional bytes.
   Terminates with DLE-ETX and checks response.  Returns 0 if OK, 2 or 3 on
   error. */

#define MORE_FR  0x100 
#define SUB_FR 0x200 

int putframe ( int type, u_char *buf , int len )
{
  int err=0, i ;

  buf [ 0 ] = 0xff ;
  buf [ 1 ] = type & MORE_FR ? 0xc0 : 0xc8 ;
  buf [ 2 ] = type & 0xff ;

  if ( ! ( type & SUB_FR ) )
    ckcmd ( &err, "+FTH=3" , TO_FT, CONNECT, 0 ) ;

  if ( ! err ) {
//    msg("Isending %s", frname(type & 0x7f));
    for ( i=0 ; ! err && i< len+3 ; i++ )  faxputdatac ( buf [ i ] ) ;
    faxputs ( DLE_ETX ) ;

    ckcmd ( &err, 0, TO_DRAIN, type & MORE_FR ? CONNECT : OK , 0 ) ;
    if ( ! err ) 
      logfr ( "sent", frname(type & 0x7f), buf, len+3 ) ;
  }

  return err ;
}


/* Read HDLC frame and store it in buffer buf of size n.  Skips
   issuing +FRH command on pass==0.  Returns length of frame if
   OK, EOF on timeout, -3 if any errors as per T.30 5.4.2 (too
   long, FCS error) */

int getframe ( int pass , u_char *buf , int n , int t )
{
  int err=0, c, i=0 ;

//  msg ("Igetframe should time out after %d", t);

  if ( pass && ( c = cmd ( "+FRH=3" , t ) ) != CONNECT ) 
    err = ( c == EOF ) ? -EOF : msg ( "E3get frame command failed") ;
  
  if ( err == -EOF ) { faxputc ( CAN ) ; cmd ( "" , TO_ABRT ) ; }

  if ( ! err ) {
    for ( i=0 ; ( c = faxgetdatac ( pass ? TO_CHAR : t ) ) >= 0  ; i++ )
      if ( i < n ) buf[ i ] = c ;
    if ( c == EOF ) 
      err = i ? msg ( "E3timed out reading frame data") : EOF ;
    if ( i >= n ) err = msg ( "E3frame too long (%d bytes)", i ) ;
  } 


  // msg("Iin getframe before last ckcmd");
  ckcmd ( &err, 0, TO_RTCMD, OK, CONNECT ) ;
  // msg("Iin getframe after last ckcmd");

  return err ? -err : ( i < n ? i : n ) ;
}

/* Reverse bit and byte order of ID strings as per T.30 5.3.6.2.4-6 */

void revcpy ( u_char *from , u_char *to )
{
  int i, j ;
  for ( i=0, j=IDLEN-1 ; i<IDLEN ; i++, j-- ) 
    to [ i ] = normalbits [ from [ j ] & 0xff ] ;
}

/* Handle procedure interrupt requests (just print message for now).
   Returns 0. */

int pr_int( void )
{
  return msg ("W0procedure interrupt request ignored" ) ;
}


/* Class 1 send.  Each received frame elicits an appropriate reply.
   Optional or unrecognized frames are ignored.  Terminates by sending DCN
   after receiving MCF after EOP or on error.  Timeouts, bad frames or CRP
   repeat last command up to MAXRETRY times.  On training check failures
   the speed (remote capability br) is reduced. The lowest speed is retried
   MAXTRAIN times.  Page transmission failures are retried NTXRETRY
   times. */


enum replies
{ NONE=0x100, DCS1, DCS2, TXDATA, PPM, DONE, BADFR, TIMEOUT, SENDDIS } ;

int c1send ( int started ) 
{ 
  int err=0, done=0, pass=started, page=1, frame=NONE, reply=DONE ;
  int frlen, rxdislen=0, disbit=0 ;
  int cmdtry=0, pagetry=0, traintry=0 ;
  int ppm=EOP ;
  u_char buf [ MAXFRLEN ] , *fif=buf+3 ;

  for ( pass=started ; ! done ; pass++ ) {

    if ( err ) {
      frame = NONE ;
    } else {
      frlen = getframe ( pass, buf, MAXFRLEN, started ? T3S : T1 ) ;
      if ( frlen < 3 ) {
	frame = CRP ;
      } else {
	frame = buf [ 2 ] & 0x7f ;
	logfr ( "received" , frname(frame), buf , frlen ) ;
	cmdtry = 0 ;
      }
    }

    switch ( frame ) {
      
    case CRP:
      if ( !started || cmdtry++ >= MAXRETRY ) 
	err = msg ( "E3 no response from remote" ) ;
      break ;

    case NSF:
      reply = NONE ;
      break ;

    case CSI:
      strcpy(remoteid, "\"");
      revcpy ( fif , (u_char*) &remoteid[1] ) ;
      strcat(remoteid, "\"");
      msg ( "I- remote ID=\"%*.*s\"" , IDLEN, IDLEN, remoteid ) ;
      reply = NONE ;
      break ;

    case DIS:
      started = 1 ;
      disbit = 0x80 ;
      rxdislen = dislen ( fif ) ;
      mkcap ( fif , remote , 1 ) ;
      reply = DCS1 ;
      break ;

    case CFR:
      reply = TXDATA ;
      break ;

    case FTT:
      if ( (remote[BR] = fallback[session[BR]]) > 0 || traintry++ < MAXTRAIN ) 
	reply = DCS2 ; 
      else
	err = msg ( "E1failed to train") ; 
      break ;

    case PIP: 
      pr_int ( ) ; 
    case RTP:
    case MCF:
      page++ ;
      pagetry=0 ;
      if ( ppm == MPS && frame == MCF ) reply = TXDATA ;
      else if ( ppm == EOP ) done = 1 ;
      else reply = DCS2 ; 
      break ;

    case PIN: 
      pr_int ( ) ;
    case RTN:
      if ( pagetry++ < NTXRETRY ) reply = DCS2 ;
      else err = msg( "E1too many page send retries" ) ;
      break ;

    case DCN:
      err = msg ( "E3remote disconnected") ;
      break ;

    case NONE:
      break ;

    default:
      break ;

    } /* switch ( frame ) */

    switch ( err || done ? DONE : reply ) {

    case  DCS1:
      revcpy ( (u_char*) localid , fif ) ;
      err = putframe ( TSI + MORE_FR + disbit , buf, IDLEN ) ;      
							   /* fall through */
    case DCS2:
      mincap ( local, remote, session ) ;
      mkdis ( session , fif , rxdislen , 0 ) ;
      if ( !err ) err = 
	putframe ( DCS + (reply==DCS1 ? SUB_FR : 0) + disbit, buf, rxdislen ) ;
      ckcmd ( &err, "+FTS=8", TO_FT, OK, 0 ) ;
      if ( !err ) err = 
	puttrain ( c1cmd [1][1][session[BR]] , (int)(1.65 * cps [ session[BR] ]) ) ;
      reply = DCS2 ;
      break ;

    case TXDATA:
      if ( !err ) err = rdpage ( page, &ppm ) ;
      ckcmd ( &err,  c1cmd [1][0][session[BR]] , TO_FT, CONNECT, 0 ) ;
      if ( !err ) err = send_data ( ) ;
      ckcmd ( &err, "+FTS=8", TO_FT, OK, 0 ) ;      
							   /* fall through */
    case  PPM:
      if ( !err ) err =	putframe ( ppm + disbit, buf, 0 ) ;
      reply = PPM ;
      break ;

    case DONE:
      putframe ( DCN + disbit, buf, 0 ) ; 
      done = 1 ;
      break ;

    case NONE:
      break ;

    default:
      err = msg ( "E3can't happen(reply)" ) ;
      break ;

    } /* switch ( reply ) */

  } /* for ( ! done ) */

  return err ;
}


/* Class 1 receive.  Sends DIS until gets a DCS or times out.  Sends ppr
  after ppm and receives data after DCS or MPS. Note: TCF (training check
  data) is received 75 +/- 20 ms after DCS so there should be no lengthy
  processing between DCS and gettrain(). */

int c1receive ( int started ) 
{ 
  int err=0, done=0, page=1, pass, frame, frlen ;
  u_char buf[MAXFRLEN], *fif=buf+3 ;

  err = wrpage ( page ) ;

  for ( pass=started ; ! err && ! done ; pass++ ) {

    if ( pass > 0 ) {
      frlen = getframe(pass, buf, MAXFRLEN, started ? T2 : T4 ) ;
//      msg("Igetframe done, started was %d", started);
      if ( frlen == EOF ) {
	frame = started ? TIMEOUT : ( pass < MAXDIS ? SENDDIS : TIMEOUT ) ;
      } else if ( frlen < 3 ) {
	frame = started ? NONE : ( pass < MAXDIS ? SENDDIS : NONE ) ;
      } else {
	frame = buf [ 2 ] & 0x7f ;
	logfr ( "received" , frname(frame), buf , frlen ) ;
      }
    } else {
      frame=SENDDIS ;
    }

    switch ( frame ) {
      
    case SENDDIS:
      revcpy ( (u_char*) localid, fif ) ;
      if ( !err ) err = 
	putframe ( CSI + MORE_FR + ( pass ? 0 : SUB_FR ), buf, IDLEN ) ;
      mkdis ( local , fif , DEFDISLEN , 1 ) ;
      if ( !err ) err = 
	putframe ( DIS + SUB_FR , buf, DEFDISLEN ) ;
      break ;
      
    case TIMEOUT:
      done = err = msg ( "E3timed out waiting for sender" ) ;
      break; 

    case BADFR :
      msg ( "W bad frame" ) ;
      break ;

    case TSI: ;
      strcpy(remoteid, "\"");
      revcpy ( fif , (u_char*) &remoteid[1] ) ;
      strcat(remoteid, "\"");
      msg ( "I- remote ID=\"%*.*s\"" , IDLEN, IDLEN, remoteid ) ;
      break ;
      
    case DCS: ;
      started = 1 ;
      mkcap ( fif , session  , 0 ) ;
//      printcap ( "session" , session ) ;
      if ( gettrain ( c1cmd [0][1][session[BR]], cps[session[BR]] ) == 0 ) {
	if ( !err ) err = putframe ( CFR, buf, 0 ) ;
	if ( !err ) goto getdata ;
      } else {
	if ( !err ) err = putframe ( FTT, buf, 0 ) ;
      }
      break ;

    case PRI_EOM:
    case PRI_MPS:
    case PRI_EOP:
      pr_int() ;

    case EOM:
    case MPS:
    case EOP:
      err = wrpage ( ++page ) ;	
      if ( ! err )
	if ( good ) err = putframe ( MCF, buf, 0 ) ;
	else        err = putframe ( RTN, buf, 0 ) ;
      if ( ! err && good && ( frame == MPS || frame == PRI_MPS ) ) 
	goto getdata ; 
      if ( ! err && good && (frame == EOM || frame == PRI_EOM ) ) {
	pass=started=0 ;
	ckcmd ( &err, "+FTS=8", TO_FT, OK, 0 ) ;
      }
      break ;

    getdata:
      ckcmd ( &err, c1cmd [0][0][session[BR]] , TO_FT, CONNECT, 0 ) ;
      if ( !err ) err = receive_data (  ) ;
      break ;
      
    case DCN:
      done = 1 ;
      break;

    } /* switch */
    
  } /* do */

  wrpage ( -1 ) ;		/* remove last file */
  return err ;
}

/* Class 2 fax transmission.  Retries each page up to NTXRETRY times.
   Transmission begins after DC2 or XON is received.  Sends the data and
   sends appropriate post-page message.  Checks for 'good' status set in
   cmd() by +FPTS: responses. */

int c2send ( void )
{
  int c, page, err=0, done=0, try, ppm=0, noise=0 ;
  char txcmd[50];

  for ( page=1 ; !err && ! done ; page++ ) {

    for ( try=good=0 ; !err && !good && try<NTXRETRY ; try++ ) {

      err = rdpage ( page , &ppm ) ;

/*
      local[VR] = faxin.faxhdr.info.hires;
      sprintf(txcmd, "+FDT=%d,%d,%d,%d", session[DF], local[VR], session[WD], session[LN]);
*/
      sprintf(txcmd, "+FDT");
      ckcmd ( &err, txcmd , TO_C2B , CONNECT, 0 ) ;
  
      if ( ! err ) 
	while ( !err && ( c = faxgetc ( TO_C2X ) ) != XON && c != DC2 )
	  if ( c == EOF ) {
	    msg ( "Wno XON/DC2 received after CONNECT") ;
	    break;
	  } else {
	    msg ( "W+%s", cname ( c ) ) ;
	    noise++ ;
	  }

      if ( noise ) {
	msg ( "Wreceived (%d) characters while waiting to send", noise ) ;
	noise = 0 ;
      }

      if ( ! err ) err = send_data ( ) ;

      good = 1 ; /* assume good if no +FPTS: */
      ckcmd ( &err, ppm == EOP ? "+FET=2" : "+FET=0" , TO_C2PP, OK, 0 ) ;

      if ( hsc > 0 ) err = msg ( "E2abnormal termination (code %d)", hsc ) ;
    }
    if ( ppm == EOP ) done = 1 ;
    if ( try >= NTXRETRY ) {
      err = msg ( "E2too many page send retries" ) ;
      cmd ( "+FK", T3S ) ;
    }
  } 
  return err ; 
}

/* Class 2 fax reception.  Send fax data receive command.  If
   response is OK, then no more pages are coming.  If it's
   CONNECT receive the data for one page.  Returns 0 or >= 2 for
   errors.  */

int c2receive ( void )
{
  int err=0, page=1, done=0, c ;

  err = wrpage ( page ) ;

  while ( ! err && ! done && hsc < 0 ) {
    if ( ( c = cmd ( "+FDR" , TO_C2R ) ) == CONNECT ) {
      faxputcnow ( startchar ) ;
      err = receive_data ( ) ;
      ckcmd ( &err, good ? "+FPTS=1" : "+FPTS=2", T3S , OK, 0 ) ;
      if ( ! err ) err = wrpage ( ++page ) ;
    } else {
      if ( c == OK ) { wrpage ( -1 ) ; done = 1 ; } 
      else err = msg ( "E3receive (+FDR) command failed") ;
    }
  } 
  if ( hsc > 0 ) err = msg ( "E2abnormal call termination (code %d)", hsc ) ;

  return err ;
}

/* Dial a number and send a fax. */

int send ( char *tel , char *faxfile )
{
  char c, s [ 128 ] ;
  int err=0 ;

  msg ( "Idialing %.127s", tel ) ;
  if (dialTone)
    sprintf ( s , "DT%.127s" , tel ) ;
  else
    sprintf ( s , "D%.127s" , tel ) ;

  err = begin_session ( faxfile ) ;

  if ( ! err ) {
    if ( ( ( c = cmd ( s , TO_A ) ) == ( c1 ? CONNECT : OK ) ) && hsc < 0 ) 
      msg ( "Iconnected" ) ; 
    else if ( c ==  BUSY ) 
      err = msg ( "W1number is busy" ) ; 
    else if ( c == EOF ) {
      err = msg ( "E2aborted manually" ) ;
    } else 
      err = msg ( "E2can't establish session" ) ;
  }

  if ( ! err ) 
    err = c1 ? c1send ( 0 ) : c2send ( ) ;

  if ( err != 1 && err < 4 ) end_session( ) ;

  return err ;
}


/* Receive a fax.  Open modem device and initialize it.  Remove locks if
   sharing device with outgoing calls.  If waiting for call, wait for modem
   activity, else answer phone.  Figure out what mode we answered in and
   handle call appropriately.  Re-lock if necessary. Then exec *getty or
   run class 1 or class 2 reception routines. Modems prompt as follows
   after answering: Class 0: CONNECT nnn for data; Class 1: FAX + CONNECT
   for fax, DATA + CONNECT nnn for data, just CONNECT for fax if +FAE=0;
   Class 2: CONNECT (data) or OK (fax). ("+FCON" and "CONNECT FAX" are 
   status messages, not prompts). */

int receive ( char *faxfile )
{
  enum connectmode { NONE, DATAMODE, FAXMODE } ; 
  enum connectmode mode=NONE ;
  int c=0, err ;

  if ( ( err = begin_session ( faxfile ) ) == 0 ) {

    if ( ! err && waitforcall ) {
      char ch;

      msg ( "Iwaiting for activity on %s", faxfile ) ;
      while (readChar(&ch, 6000, &globalCallBack) == TimedOut);
      unReadChar();
      msg ( "Iactivity detected at ") ;
    }
  
    if ( ! err && share ) {
      msleep ( 200 ) ;		/* let other programs lock port  */
    }

    if ( ! err && softadans && *getty ) {
      if ( cmd ( ( waitforcall ? 0 : "A" ) , TO_DATAF ) == CONNECT ) 
	mode = DATAMODE ;
      else {
	int i ; /* abort data answer mode & set fax mode to try again */
	for ( i=0 ; i<3 ; i++ ) 
	  if ( cmd ( c1 ? "+FCLASS=1" : "+FCLASS=2" , -TO_RESET ) == OK )
	    break ; 
      }
    }
    if ( ! err && mode == NONE ) {
      c = cmd ( ! waitforcall || ( softadans && *getty ) ? "A" : 0 , TO_A ) ;
      if (c == EOF) {
	err = msg ( "E2aborted manually" ) ;
      } else {
	if ( c1 )
	  mode = ( c == CONNECT ) ? ( datamode ? DATAMODE : FAXMODE ) : NONE ;
	else
	  mode = ( c == CONNECT ) ? DATAMODE : ( c == OK ? FAXMODE : NONE ) ;
      }
    }
  
    if ( err || hsc >= 0 ) mode = NONE ;

    if ( ! err )
      switch ( mode ) {
      case DATAMODE : {
        void* lastSent = 0;
        char buf [ MAXGETTY ] ;
	msg ( "Idata call answered") ;
	sprintf ( buf , getty , crate );
	msg ( "ISending ARexx \"%s\"" , buf ) ;
	lastSent = SendARexxMsg(OutgoingRexx, buf, FALSE);
	while (1) {
	  Wait(ARexxSignal(OutgoingRexx));
	  if (lastSent == GetReplyARexxMsg(OutgoingRexx)) break;
	}
	
	return 0;
	break ; }
      case FAXMODE :
	msg ( "Ifax call answered") ;
	break ;
      case NONE:
	err = msg ( "E3unable to answer call") ;
	break ;
      }

    setReceiveSerialParms();

    if ( ! err ) err = c1 ? c1receive ( 0 ) : c2receive ( ) ;

    setSendSerialParms();

    if ( err != 1 && err < 4 ) end_session( ) ;   /* if not locked */
  }
  
  return err  ;
}


/* Simple (one option per argument) version of getopt(3). */

int optind = 1 ;
char *optarg ;

int nextopt( int argc, char **argv, char *args )
{
  char *a, *p ;

  if ( optind >= argc || *(a = argv[optind]) != '-' ) return -1 ;
  optind++ ;

  if ( ! *(a+1) || ! ( p = strchr ( args , *(a+1) ) ) )
    return msg ( "Eunknown option (%s)" , a ) , '?' ; 

  if ( *(p+1) != ':' ) optarg = 0 ;
  else
    if ( *(a+2) ) optarg = a+2 ;
    else
      if ( optind >= argc ) return msg ( "Eno argument for (%s)", a ) , '?' ;
      else optarg = argv [ optind++ ] ;
  return *(a+1) ;
}

char modemSetup[6][50];
char modemReset[5][50];
char idInit[50];
char debugVerbosity[15];
char capInit[CMDBUFSIZE];

void processConfig(char* var, char* value) {
  static int foundPortName = 0;
  static int inPortConfig = 0;
  int d;
  
  if (stricmp(var, "PORT") == 0) {
    if (stricmp(value, portNameToLoad) == 0 && foundPortName == 0) {
      foundPortName = 1;
      inPortConfig = 1;
    }
  }

  if (inPortConfig == 1) {
    if (stricmp(var, "SERIALNAME") == 0) {
      strcpy(serialName, value);
    } else if (stricmp(var, "UNITNUMBER") == 0) {
      sscanf(value, "%d", &unitNumber);
    } else if (stricmp(var, "FAXCLASS") == 0) {
      if (stricmp(value, "CLASS 1") == 0) {
	strcpy(modemSetup[0], "+FCLASS=1");
	c1 = 1;
      } else if (stricmp(value, "CLASS 2") == 0) {
	strcpy(modemSetup[0], "+FCLASS=2");
	c1 = 0;
      }
      iopt[niopt++] = modemSetup[0];
    } else if (stricmp(var, "FAXID") == 0) {
      sprintf ( localid, "%*.*s", IDLEN, IDLEN, value ) ;
      sprintf ( idInit , "+FLID=\"%.*s\"" , CMDBUFSIZE-9, localid ) ;
      if ( !c1 ) { iopt[niopt++] = idInit; }
    } else if (stricmp(var, "REVERSETXBITORDER") == 0) {
      sscanf(value, "%d", &d);
      if (d) txbitorder = reversebits;
    } else if (stricmp(var, "REVERSERXBITORDER") == 0) {
      sscanf(value, "%d", &d);
      if (d) {
	rxbitorder = reversebits;
	msg("Ireversed rx bit order");
      }
    } else if (stricmp(var, "STARTRXWITHXON") == 0) {
      sscanf(value, "%d", &d);
      if (d)
	startchar = XON;
      else
	startchar = DC2;
    } else if (stricmp(var, "FAXHARDWAREHANDSHAKING") == 0) {
      sscanf(value, "%d", &faxHardwareHandshaking);
    } else if (stricmp(var, "COMMANDPAUSE") == 0) {
      sscanf(value, "%d", &d);
      for ( ; d > 0; d--) cmdpause += T_CMD;
    } else if (stricmp(var, "MAXPAGEERRORS") == 0) {
      sscanf(value, "%d", &maxpgerr);
    } else if (stricmp(var, "DIALTONE") == 0) {
      sscanf(value, "%d", &dialTone);
    } else if (stricmp(var, "DEBUGVERBOSITY") == 0) {
      strcpy(debugVerbosity, value);
      verb = debugVerbosity;
    } else if (stricmp(var, "FAXCAPABILITY") == 0) {
      str2cap(value, local);
/*
      sprintf ( capInit , "+FDCC=%.*s" , CMDBUFSIZE-7, value ) ;
      if ( !c1 ) {
	iopt[niopt++] = capInit;
      }
*/
    } else if (stricmp(var, "FAXSETUP1") == 0) {
      strcpy(modemSetup[1], value);
      if (strcmp(modemSetup[1], "") != 0)
	iopt[niopt++] = modemSetup[1];
    } else if (stricmp(var, "FAXSETUP2") == 0) {
      strcpy(modemSetup[2], value);
      if (strcmp(modemSetup[2], "") != 0)
	iopt[niopt++] = modemSetup[2];
    } else if (stricmp(var, "FAXSETUP3") == 0) {
      strcpy(modemSetup[3], value);
      if (strcmp(modemSetup[3], "") != 0)
	iopt[niopt++] = modemSetup[3];
    } else if (stricmp(var, "FAXSETUP4") == 0) {
      strcpy(modemSetup[4], value);
      if (strcmp(modemSetup[4], "") != 0)
	iopt[niopt++] = modemSetup[4];
    } else if (stricmp(var, "FAXSETUP5") == 0) {
      strcpy(modemSetup[5], value);
      if (strcmp(modemSetup[5], "") != 0)
	iopt[niopt++] = modemSetup[5];
    } else if (stricmp(var, "FAXRESET1") == 0) {
      strcpy(modemReset[0], value);
      if (strcmp(modemReset[0], "") != 0)
	zopt[nzopt++] = modemReset[0];
    } else if (stricmp(var, "FAXRESET2") == 0) {
      strcpy(modemReset[1], value);
      if (strcmp(modemReset[1], "") != 0)
	zopt[nzopt++] = modemReset[1];
    } else if (stricmp(var, "FAXRESET3") == 0) {
      strcpy(modemReset[2], value);
      if (strcmp(modemReset[2], "") != 0)
	zopt[nzopt++] = modemReset[2];
    } else if (stricmp(var, "FAXRESET4") == 0) {
      strcpy(modemReset[3], value);
      if (strcmp(modemReset[3], "") != 0)
	zopt[nzopt++] = modemReset[3];
    } else if (stricmp(var, "FAXRESET5") == 0) {
      if (strcmp(modemReset[4], "") != 0)
	strcpy(modemReset[4], value);
      zopt[nzopt++] = modemReset[4];
    } else if (stricmp(var, "ENDRECORD") == 0) {
      inPortConfig = 0;
    } else if (stricmp(var, "STARTRECORD") == 0) {
      inPortConfig = 0;
    } else {
    }
  }
}



/* Fax send/receive program for Class 1 or 2 fax modems. Initialize
   character name, bit reversal and T.4 decoding tables and process
   arguments.  Returns 0 on success, 1 if number busy or device locked, 2
   for errors, 3 for protocol errors, 4 if no modem response, 5 on fatal
   signal. */

int main( int argc, char **argv)
{
  int i, err=0 , c, nlocks=0 ;
  char *faxfile = FAXFILE ;
  char capinit [ CMDBUFSIZE ] , idinit [ CMDBUFSIZE ] ;
//  char msgbuf [ MAXMSGBUF ] ;
  char tmp[200];
  void* lastSent = 0;

  argv0 = argv[0] ;

//  setvbuf ( LOGF , msgbuf , _IOFBF , MAXMSGBUF ) ;

  msg ( "I " Version "  starts ") ;
  msg ( "I " Copyright "  (compiled "__DATE__ " " __TIME__ ")" ) ;

  argv0 = strrchr ( argv0 , '/' )  ;
  if ( ! argv0 ) argv0 = argv[0] ; else argv0++ ;

  cname ( 0 ) ;
  for ( i=0 ; i<256 ; i++ ) normalbits [ reversebits [ i ] = i ] = 
    ( i& 1 ? 128:0 ) | ( i& 2 ? 64:0 ) | ( i& 4 ? 32:0 ) | ( i&  8 ? 16:0 ) |
    ( i&16 ?   8:0 ) | ( i&32 ?  4:0 ) | ( i&64 ?  2:0 ) | ( i&128 ?  1:0 ) ;
  t4tree = unpktree( ) ;

  while (!err && (c=nextopt(argc,argv,"p:c:d:g:i:l:o:q:r:st:v:wx:z:T") ) != -1) {
    switch (c) {
    case 'c': 
      err = str2cap ( optarg , local ) ;
      sprintf ( capinit , "+FDCC=%.*s" , CMDBUFSIZE-7, optarg ) ;
      if ( !err && !c1 ) { optarg = capinit ; goto addopt ; }
      break ;
    case 'l': 
      if ( strlen ( optarg ) > IDLEN ) 
	msg("Wlocal ID truncated to 20 characters" ) ;
      sprintf ( localid, "%*.*s", IDLEN, IDLEN, optarg ) ;
      sprintf ( idinit , "+FLID=\"%.*s\"" , CMDBUFSIZE-9, localid ) ;
      if ( !c1 ) { optarg = idinit ; goto addopt ; }
      break ;
    case 'i': 
    addopt:
      if ( niopt < MAXIOPT ) iopt [ niopt++ ] = optarg ;
      else err = msg ( "E2too many modem init commands"); 
      break ;
    case 'z': 
      if ( nzopt < MAXIOPT ) zopt [ nzopt++ ] = optarg ;
      else err = msg ( "E2too many modem reset commands"); 
      break ;
    case 'd': faxfile = optarg ; break ;
    case 'g': getty = optarg ; break ;
    case 'p': strcpy(portNameToLoad, optarg);
      loadConfig("avm:servers.cfg", processConfig);
      break;
    case 'o': 
      for ( ; *optarg ; optarg++ ) 
	switch ( *optarg ) {
	case '1' : c1 = 1 ; break ;
	case '2' : c1 = 0 ; break ;
	case 'a' : softadans = 1 ;  break ;
	case 'e' : igniniterr = igniniterr ? 0 : 1 ;  break ;
	case 'r' : rxbitorder = reversebits ; break ;
	case 't' : txbitorder = reversebits ; break ;
	case 'x' : startchar = XON ; break ;
	case 'z' : cmdpause += T_CMD ; break ;
	 default : msg ( "Wunrecognized protocol option (%c)", *optarg ) ; 
	}
      break ;
    case 'q':
      if ( sscanf ( optarg , "%d", &maxpgerr ) != 1 )
	err=msg ("E2can't read quality (-q) argument (%s)", optarg ) ;
      break;
    case 'r': 
      fnamepat = optarg ;

      atexit(releaseResources);
      allocateResources();
  
      err = receive ( faxfile ) ; 
      sprintf(tmp, "call setclip(upper('%s.faxid'), %s)", portNameToLoad, remoteid);
      //fprintf(stderr, tmp);

      lastSent = SendARexxMsg(OutgoingRexx, tmp, TRUE);
      while (1) {
	Wait(ARexxSignal(OutgoingRexx));
	if (lastSent == GetReplyARexxMsg(OutgoingRexx)) break;
      }

      break;
    case 's': share = 1 ; break;
    case 't': 
      fnames = argv + optind ;
      nfiles = argc - optind ;

      atexit(releaseResources);
      allocateResources();

      if (stricmp(optarg, "MAN!") == 0)
	err = send ( "", faxfile);
      else
	err = send ( optarg , faxfile ) ; 

      sprintf(tmp, "call setclip(upper('%s.faxid'), %s)", portNameToLoad, remoteid);
      //fprintf(stderr, tmp);

      lastSent = SendARexxMsg(OutgoingRexx, tmp, TRUE);
      while (1) {
	Wait(ARexxSignal(OutgoingRexx));
	if (lastSent == GetReplyARexxMsg(OutgoingRexx)) break;
      }
      break;
    case 'v': 
      verb = optarg ;
      if ( strchr ( verb , 'a' ) )
	  for ( i=0 ; i<argc ; i++ ) msg ( "Iargv[%d]=%s", i, argv[i]) ; 
      break ;
    case 'w': waitforcall = 1 ; break ;
    case 'x': 
      if ( nlocks < MAXLKFILE ) lkfiles [ nlocks++ ] = optarg ; 
      else err = msg ( "E2too many lock files" ) ; 
      break ;
    case 'T':
      if ( ( err = begin_session ( faxfile ) ) == 0) end_session( ) ;
      break;
    default : if (stderr) fprintf ( stderr, Usage, argv0 ) ; err = 2 ; break ;
    }
  }

  msg ( "Idone, returning %d", err ) ;
  exit(err);
}
