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