1#include <ctype.h>		/* ANSI C */
2#include <signal.h>
3#include <stdio.h>
4#include <string.h>
5
6#include "efaxio.h"		/* EFAX */
7#include "efaxmsg.h"
8#include "efaxos.h"
9
10#define MAXRESPB 1024	    /* maximum bytes of modem responses saved */
11
12char *prompts[] = {		/* modem responses that are prompts */
13  "OOK", "-CONNECT FAX", "CCONNECT", "NNO CARRIER", "EERROR",
14  "NNO DIALTONE", "BBUSY", "NNO ANSWER", "M+FCERROR", "VVCON",
15  "DDATA", "++FRH:", 0 } ;
16
17int lockpolldelay = 8000 ;	/* milliseconds between checks of lock files */
18
19			    /* signals to be caught so can hang up phone */
20int catch [] = { CATCHSIGS, 0 } ;
21
22
23/* Modem features */
24
25int c1=0, c20=0 ;		/* use class 1/class 2.0 */
26int c2=0 ;			/* force class 2 */
27int cmdpause = T_CMD ;		/* delay before each init command */
28int vfc = 0 ;			/* virtual flow control */
29char startchar = DC2 ;		/* character to start reception */
30
31				/* response detector lookup tables */
32uchar rd_nexts [ 256 ] = { 0 }, rd_allowed [ 256 ] = { 0 } ;
33
34/* Initialize two lookup tables used by a state machine to detect
35   modem responses embedded in data.  The first shows which
36   characters are allowed in each state.  The second shows which
37   characters in each state increment the state.  Each table is
38   indexed by character.  The detector sequences through 6 states
39   corresponding to sequences of the form: <CR><LF>AX...<CR><LF>
40   where A is an upper-case letter and X is an u.c. letter or
41   space.  The state values are 01, 02, 04, 08, 10 and 20 (hex)
42   and are used to mask in a bit from the tables.  When the state
43   reaches 0x20 a modem response has been detected.  With random
44   data there is a small O(10^-10) chance of spurious
45   detection. */
46
47void rd_init ( void )
48{
49  int c ;
50
51  rd_nexts[CR] = rd_allowed[CR] = 0x01 | 0x08 ;
52  rd_nexts[LF] = rd_allowed[LF] = 0x02 | 0x10 ;
53  for ( c='A' ; c<'Z' ; c++ ) {
54      rd_allowed[c] = 0x04 | 0x08 ;
55      rd_nexts[c] = 0x04 ;
56    }
57  rd_allowed[' '] = 0x08 ;
58}
59
60
61/* Get a modem response into buffer s, storing up to n bytes.
62   The response includes characters from the most recent control
63   character until the first LF following some text.  Returns s
64   or null if times-out in t deciseconds or on i/o error. Trace
65   messages are buffered to reduce possible timing problems. */
66
67char *tgets( TFILE *f, char *s, int len, int t )
68{
69  int i, n, c ;
70
71  for ( i=n=0 ; 1 ; i++ ) {
72    if ( ( c = tgetc ( f, t ) ) == EOF ) break ;
73    if ( i == 0 ) msg ( "M-+ .%03d [", time_ms ( ) ) ;
74    msg ( "M-+ %s", cname ( c ) ) ;
75    if ( n > 0 && c == LF ) break ;
76    if ( ! iscntrl ( c ) && n < len ) s[n++] = c ;
77  }
78
79  if ( n >= len ) msg ( "W- modem response overflow" ) ;
80  s[ n < len ? n : len-1 ] = '\0' ;
81  if ( i > 0 ) {
82    if ( c == EOF ) msg ( "M- <...%.1f s>]", (float)t/10 ) ;
83    else msg ( "M- ]" ) ;
84  }
85
86  return c == EOF ? 0 : s ;
87}
88
89
90/* Send bytes to the modem, doing bit-reversal and escaping DLEs.
91   Returns 0 or 2 on errors. */
92
93int sendbuf ( TFILE *f, uchar *p, int n, int dcecps )
94{
95  int err=0, c, over ;
96  uchar *order = f->obitorder ;
97  uchar buf [ MINWRITE+1 ] ;
98  int i ;
99
100  for ( i=0 ; ! err && n > 0 ; n-- ) {
101    c  = order [ *p++ ] ;
102    if ( c == DLE ) buf[i++] = DLE ;
103    buf[i++] = c ;
104    if ( i >= MINWRITE || n == 1 ) {
105
106      /* ``virtual'' flow control */
107
108      if ( vfc && dcecps > 0 ) {
109	over = f->bytes - ( proc_ms ( ) - f->mstart ) * dcecps / 1000
110	  - MAXDCEBUF ;
111	if ( over > 0 ) msleep ( over * 1000 / dcecps ) ;
112      }
113
114      if ( tput ( f, (char*)buf, i ) < 0 )
115	err = msg ( "ES2fax device write error:" ) ;
116
117      i = 0 ;
118    }
119  }
120
121  return err ;
122}
123
124
125/* Scan responses since giving previous command (by cmd()) for a
126   match to string 's' at start of response.  If a matching
127   response is found it finds the start of the data field which
128   is defined as the next non-space character in the current or
129   any subsequent responses. If ip is not null, reads one integer
130   (decimal format) into ip. [problem: Class 2.0 status responses
131   are in hex.] Returns pointer to start of data field of
132   response string or NULL if not found. */
133
134char responses [ MAXRESPB ], *lresponse = responses ;
135
136char *sresponse ( char *s, int *ip )
137{
138  char *p, *r = 0 ;
139  int lens, lenr ;
140
141  lens = strlen ( s ) ;
142  for ( p=responses ; p<lresponse ; p += strlen(p) + 1 ) {
143
144    if ( ! strncmp ( p, s, lens ) ) {
145      r = p + lens ;
146
147      lenr = strlen ( r ) ;
148      if ( strspn ( r, " \r\n" ) == lenr && r+lenr < lresponse ) r += lenr ;
149
150      if ( ip && sscanf ( r, "%d", ip ) > 0 )
151	msg ( "R read value %d from \"%s\"", *ip, r ) ;
152    }
153
154  }
155
156  return r ;
157}
158
159
160/* Search for the string s in responses since last command.
161   Skips lines beginning with "AT" (command echo) and removes
162   trailing spaces.  Returns pointer to the string or NULL if not
163   found. */
164
165char *strinresp ( char *s )
166{
167  char *p, *r = 0 ;
168
169  for ( p=responses ; p<lresponse && !r ; p += strlen(p) + 1 )
170    if ( strncmp ( p, "AT", 2 ) )
171      r = strstr ( p, s ) ;
172
173  return r ;
174}
175
176
177/* Appends the value of the first response that includes the
178   string s to buffer buf of length len.  Skips characters before
179   and including "=" in the response and doesn't copy trailing
180   spaces.  */
181
182int getresp ( char *s, char *buf, int len )
183{
184  int err=0, n ;
185  char *p, *q ;
186
187  if ( ( p = strinresp ( s ) ) ) {
188    if ( ( q = strchr ( p, '=' ) ) ) p = q+1 ;
189    for ( q = p+strlen(p)-1 ; q>=p && isspace(*q) ; q-- ) ;
190    n = q - p + 1 ;
191    if ( n + strlen(buf) < len )
192      strncat ( buf, p, n ) ;
193    else
194      strncat ( buf, p, len - strlen(buf) - 1 ) ;
195  } else {
196    err = 1 ;
197  }
198  return err ;
199}
200
201
202/* Search for a match to the string s in a null-terminated array of
203   possible prefix strings pointed to by p.  The first character of each
204   prefix string is skipped.  Returns pointer to the table entry or NULL if
205   not found. */
206
207char *strtabmatch ( char **p, char *s )
208{
209  while ( *p && strncmp ( *p+1, s, strlen ( *p+1 ) ) ) p++ ;
210  return ( ! *p || **p == '-' ) ? NULL : *p ;
211}
212
213
214/* Send command to modem and check responses.  All modem commands
215   go through this function. Collects pending (unexpected)
216   responses and then pauses for inter-command delay (cmdpause)
217   if t is negative.  Writes command s to modem if s is not null.
218   Reads responses and terminates when a response is one of the
219   prompts in prompts[] or if times out in t deciseconds.
220   Repeats command if detects a RING response (probable
221   collision). Returns the first character of the matching prefix
222   string (e.g. 'O' for OK, 'C' for CONNECT, etc.)  or EOF if no
223   such response was received within timeout t. */
224
225int cmd ( TFILE *f, char *s, int t )
226{
227  char buf [ CMDBUFSIZE ], *p = "" ;
228  int resplen=0, pause=0 ;
229#if defined(__APPLE__)
230  int ringcount = 0;
231#endif
232
233  if ( t < 0 ) {
234    pause = cmdpause ;
235    t = -t ;
236  }
237
238  lresponse = responses ;
239
240  retry:
241
242  if ( s ) {
243
244    while ( tgets ( f, buf, CMDBUFSIZE, pause ) )
245      msg ( "W- unexpected response \"%s\"", buf ) ;
246
247    msg ( "C- command  \"%s\"", s ) ;
248
249    if ( strlen(s) >= CMDBUFSIZE-4 ) {
250      msg ( "E modem command \"%s\" too long", s ) ;
251    } else {
252      snprintf ( buf, sizeof(buf), "AT%s\r", s ) ;
253      tput ( f, buf, strlen(buf) ) ;
254    }
255  }
256
257  if ( t ) {
258
259    msg ( "C- waiting %.1f s", ((float) t)/10 ) ;
260
261    while ( ( p = tgets ( f, buf, CMDBUFSIZE, t ) ) ) {
262
263      msg ( "C- response \"%s\"", buf ) ;
264
265      if ( ( resplen += strlen ( buf ) + 1 ) <= MAXRESPB ) {
266	strcpy ( lresponse, buf ) ;
267	lresponse += strlen ( buf ) + 1 ;
268      }
269
270      if ( ( p = strtabmatch ( (char**) prompts, buf ) ) ) {
271	break ;
272      }
273
274      if ( ! strcmp ( buf, "RING" ) ) {
275#if defined(__APPLE__)
276	CFNumberRef value;
277
278	ringcount++;
279	value = CFNumberCreate(kCFAllocatorDefault,
280			kCFNumberIntType, &ringcount);
281
282	notify(CFSTR("ring"), value);
283
284	CFRelease(value);
285#endif
286        msleep ( 100 ) ;
287        goto retry ;
288      }
289    }
290  }
291
292  return p ? *p : EOF ;
293}
294
295
296/* Send command to modem and wait for reply after testing (and
297   possibly setting) current error status via err
298   pointer. Returns 0 if err is already set, command response, or
299   EOF on timeout. */
300
301int ckcmd ( TFILE *f, int *err, char *s, int t, int r )
302{
303  int c=0 ;
304  if ( ( ! err || ! *err ) && ( c = cmd ( f, s, t ) ) != r && r ) {
305    msg ( err ? "E %s %s %s" : "W %s %s %s",
306	 c == EOF ? "timed out" : "wrong response",
307	 s ? "after command: " :  "after waiting",
308	 s ? s : "" ) ;
309    if ( err ) *err = 3 ;
310  }
311  return c ;
312}
313
314
315/* Resynchronize modem from an unknown state.  If no immediate
316   response, try pulsing DTR low (needs &D{2,3,4}), and then
317   cancelling data or fax data modes.  In each case, discards any
318   responses for about 2 seconds and then tries command ATQ0V1 to
319   enable text responses.  Returns 0 if OK or 4 if no response.
320   */
321
322int modemsync ( TFILE *f )
323{
324  int err=0, method=0 ;
325
326  for ( method=0 ; ! err ; method++ ) {
327    switch ( method ) {
328    case 0 :
329      break ;
330    case 1 :
331      /* In the 0.9 code base the next line was incorrectly placed after the break.
332       * Putting it in the correct place prevents transmission between efax versions.
333       * We'll comment out the VOICECOMMAND until we better understand the problem.
334       */
335      //ttymode ( f, VOICECOMMAND ) ;
336      break ;
337    case 2 :
338      msg ("Isync: dropping DTR") ;
339      ttymode ( f, COMMAND ) ; msleep ( 200 ) ;
340      ttymode ( f, DROPDTR ) ; msleep ( 200 ) ;
341      ttymode ( f, COMMAND ) ;
342      break ;
343    case 3 :
344      msg ("Isync: sending escapes") ;
345      ttymode ( f, VOICECOMMAND ) ;
346      tput ( f, CAN_STR, 1 ) ;
347      tput ( f, DLE_ETX, 2 ) ;
348      msleep ( 100 ) ;
349      ttymode ( f, COMMAND ) ;
350      tput ( f, CAN_STR, 1 ) ;
351      tput ( f, DLE_ETX, 2 ) ;
352      msleep ( 1500 ) ;
353      tput ( f, "+++", 3 ) ;
354      break ;
355    case 4 :
356      err = msg ("E4sync: modem not responding") ;
357      continue ;
358    }
359    while ( method && cmd ( f, 0, 20 ) != EOF ) ;
360    if ( cmd ( f, "Q0V1", -20 ) == OK ) break ;
361  }
362  return err ;
363}
364
365
366/* Set up modem by sending initialization/reset commands.
367   Accepts either OK or CONNECT responses. Optionally changes
368   baud rate if a command begins with a number. Returns 0 if OK,
369   3 on errors. */
370
371int setup ( TFILE *f, char **cmds, int ignerr )
372{
373  int err=0 ;
374  char c ;
375
376  for ( ; ! err && *cmds ; cmds++ ) {
377#if 0
378    if ( *cmds && isdigit( **cmds ) ) {
379
380    }
381#endif
382    if ( ( c = cmd ( f, *cmds, -TO_RESET ) ) != OK && c !=  VCONNECT &&
383	! ignerr ) {
384      err = msg ( "E3modem command (%s) failed", *cmds ? *cmds : "none" ) ;
385    }
386  }
387
388  return err ;
389}
390
391
392/* Terminate session.  Makes sure modem is responding, sends
393   modem reset commands or hang-up command if none, removes lock
394   files. Returns 0 if OK, 3 on error.*/
395
396int end_session ( TFILE *f, char **zcmd,  char **lkfile, int sync )
397{
398  int err = 0 ;
399
400  if ( f && sync )
401    err = modemsync ( f ) ;
402  if ( f && zcmd && ! err && sync )
403    err = setup ( f, zcmd, 0 ) ;
404  if ( f )
405    ttymode ( f, ORIGINAL ) ;
406  if ( lkfile )
407    unlockall ( f, lkfile ) ;
408  return err ;
409}
410
411
412/* Initialize session.  Try locking and opening fax device until opened or
413   get error. Then set tty modes, register signal handler, set up
414   modem. Returns 0 if OK, 2 on errors, 3 if initialization failed, 4 if no
415   modem response. */
416
417int begin_session ( TFILE *f, char *fname, int reverse, int hwfc,
418		    char **lkfile, ttymodes mode, void (*onsig) (int), int wait )
419{
420  int i, err=0, busy=0, minbusy=0 ;
421
422  do {
423    err = lockall ( f, lkfile, busy >= minbusy ) ;
424    if ( ! err ) err = ttyopen ( f, fname, reverse, hwfc ) ;
425    if ( err == 1 ) {
426      if ( busy++ >= minbusy ) {
427	msg ( "W %s locked or busy. waiting.", fname ) ;
428	minbusy = minbusy ? minbusy*2 : 1 ;
429      }
430
431      waiting = 1;
432      if (tdata ( f, lockpolldelay / 100 ) == TDATA_SLEEP)
433	err = TDATA_SLEEP;		/* machine is about to sleep... */
434    }
435  } while ( err == 1 ) ;
436
437  if ( ! err ) msg ( "Iopened %s", fname ) ;
438
439  if ( ! err ) err = ttymode ( f, mode ) ;
440
441  if ( ! err ) {
442    rd_init ( ) ;
443    f->rd_state = RD_BEGIN ;
444  }
445
446  for ( i=0 ; ! err && catch [ i ] ; i++ )
447    if ( signal ( catch [ i ], onsig ) == SIG_ERR )
448      err = msg ( "ES2can't set signal %d handler:", catch [ i ] ) ;
449
450  if ( !err ) err = modemsync ( f ) ;
451
452  return err ;
453}
454