/* $Id$ */ /* * This file is part of ivcall. * * ivcall 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. * * ivcall 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 ivcall; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SPANDSP #include #include #endif #include "util.h" #include "lock.h" #include "g711.h" /* Those nifty DLE-sequences*/ #define DLE 0x10 #define ETX 0x03 #define DC4 0x14 /* Baudrate for the TTY. Should be greater the 64000.*/ #define BAUD_RATE B115200 /* Every single commands gets a timeout of 2 seconds.*/ #define COMMAND_TIMEOUT 2 /* The default timeout for waiting that the call gets accepted.*/ #define DEFAULT_TIMEOUT 30 #define BUFSIZE 10240 /* Initialisation of the modem*/ const char *at_commands[] = { "AT&F\n", "OK\r\n", "ATI\n", "Linux ISDN\r\nOK", "AT&B128\n", "OK\r\n", "AT+FCLASS=8\n", "OK\r\n", "AT+VIP\n", "OK\r\n", "AT+VSM=6\n", "OK\r\n", "ATS18=1\n", "OK\r\n", "ATS14=4\n", "OK\r\n", "ATS13.4=0\n", "OK\r\n", "ATS13.0=1\n", "OK\r\n", "ATS16=1\n", "OK\r\n", "ATS23=1\n", "OK\r\n", 0 }; #ifdef HAVE_SPANDSP /* Tiff file to send */ char *fax_tiff_file = NULL; /* Do fax */ int fax_enabled = 0; /* Local identifier for fax */ char *fax_ident = NULL; #endif /* Local MSN*/ char *source_msn = NULL; /* Remote MSN*/ char *destination_msn = NULL; /* Modem-device*/ char *device = NULL; /* Our name*/ char *appname = NULL; /* The default timeout*/ int timeout = DEFAULT_TIMEOUT; /* Enable debug-mode or not?*/ int debug = 0; /* We got a ctrl-c*/ volatile int interrupted = 0; /* Got an SIGARLM?*/ volatile int alarmed = 0; /* Shall we wait for a call?*/ int answer = 0; /* After how many RINGs we shall answer?*/ int rings = 1; /* Saved TTY configuration*/ struct termios saved_termios; /* Our ctrl-c handler */ void sigint(int sig) { const char s[] = "Got SIGINT\n"; loop_write(2, s, strlen(s)); interrupted = 1; } void sigalrm(int sig) { const char s[] = "Timeout\n"; loop_write(2, s, strlen(s)); alarmed = 1; } /* Waits for a certain string from the modem up to a certain time.*/ int tty_waitfor(int fd, const char *what, int t) { static char buf[256]; char *p = buf; int r = -1, l; if (debug) fprintf(stderr, "<- "); l = strlen(what); if (t == -1) t = 0; alarm(0); alarmed = 0; alarm(t); while (!interrupted && !alarmed) { ssize_t s; if ((s = read(fd, p, 1)) <= 0) break; if (debug) fprintf(stderr, "%c", *p); p++; if (p - buf >= l && !strncmp(p-l, what, l)) { r = 0; break; } } alarm(0); alarmed = 0; if (debug) fprintf(stderr, "\n"); return r; } /* Write a string to the modem*/ int tty_puts(int fd, const char *s) { int l = strlen(s); if (loop_write(fd, s, l) != l) return -1; if (tcdrain(fd) < 0) return -1; if (debug) fprintf(stderr, "-> %s", s); return 0; } /* Issue a command to the modem an wait for an answer */ int tty_command(int fd, const char *cmd, const char* resp, int t) { tcflush(fd, TCIFLUSH); tty_puts(fd, cmd); return tty_waitfor(fd, resp, t); } /* Hang up the modem */ int hard_hangup(int fd) { struct termios pts; tcflush(fd, TCIOFLUSH); if (tcgetattr(fd, &pts) < 0) return -1; cfsetospeed(&pts, B0); cfsetispeed(&pts, B0); tcsetattr(fd, TCSANOW, &pts); sleep(1); if (tcgetattr(fd, &pts) < 0) return -1; cfsetospeed(&pts, BAUD_RATE); cfsetispeed(&pts, BAUD_RATE); tcsetattr(fd, TCSANOW, &pts); sleep(1); tcflush(fd, TCIFLUSH); return 0; } /* Opens the modem and sets the baud rate*/ int tty_open(const char *device) { struct termios pts, pts2; int fd = -1, n; if ((fd = open(device, O_RDWR|O_NDELAY)) < 0) goto fail; if ((n = fcntl(fd, F_GETFL, 0)) < 0) goto fail; if (fcntl(fd, F_SETFL, n & ~O_NDELAY) < 0) goto fail; if (tcgetattr(fd, &saved_termios) < 0) goto fail; memset(&pts, 0, sizeof(pts)); pts.c_cflag = CRTSCTS | IGNPAR | HUPCL | CS8; pts.c_iflag = IGNPAR; pts.c_oflag = 0; pts.c_lflag = 0; pts.c_cc[VMIN] = 1; pts.c_cc[VTIME] = 0; cfsetospeed(&pts, BAUD_RATE); cfsetispeed(&pts, BAUD_RATE); tcflush(fd, TCIOFLUSH); if (!tcsetattr(fd, TCSANOW, &pts)) if (!tcgetattr(fd, &pts2)) if (!memcmp(&pts, &pts, sizeof(struct termios))) return fd; tcsetattr(fd, TCSANOW, &saved_termios); fail: if (fd >= 0) close(fd); return -1; } /* Closes the modem device and resets the baudrate*/ void tty_close(int fd) { tcflush(fd, TCIOFLUSH); tcsetattr(fd, TCSANOW, &saved_termios); close(fd); } /* Detects the DCD line*/ int detect_carrier(int fd) { unsigned int arg; if (ioctl(fd, TIOCMGET, &arg) < 0) return -1; return arg & TIOCM_CAR ? 1 : 0; } /* Filters out DLE-sequences from modem */ uint8_t *filter_dle(uint8_t *data, size_t size, size_t *ret_size, int *flag, int *quit) { uint8_t *ret, *s, *d; size_t l; assert(data); assert(size > 0); assert(ret_size); assert(flag); assert(quit); if (!(ret = malloc(size))) return NULL; for (l = size, s = data, d = ret; l > 0; l--, s++) { if (*flag) { switch (*s) { case DLE : *(d++) = DLE; break; case ETX : case DC4 : *quit = 1; break; default : /* We received some DTMF signals, but we ignore them. */ fprintf(stderr, "** DTMF: %c\n", *s); break; } *flag = 0; } else if (*s == DLE) *flag = 1; else *(d++) = *s; } *ret_size = d-ret; return ret; } /* Escape DLE-bytes for sending data to the modem */ uint8_t* escape_dle(uint8_t *data, size_t size, size_t *ret_size) { uint8_t *ret, *s, *d; size_t l, ms; if (!(ret = malloc(ms = (size_t) (size*1.05)))) return NULL; *ret_size = size; for (l = size, s = data, d = ret; l > 0; l--, s++) { if (*s == DLE) { *(d++) = DLE; (*ret_size)++; if (ms < *ret_size) { int n = d-ret; uint8_t *m; if (!(m = realloc(ret, ms = (size_t) (*ret_size*1.05)))) { free(ret); return NULL; } ret = m; d = ret+n; } } *(d++) = *s; } return ret; } static int cat_loop(int fd) { uint8_t *incoming = NULL, *outgoing = NULL; ssize_t incoming_idx = 0, incoming_len = 0, outgoing_idx = 0, outgoing_len = 0; int warned = 0; int fm, r = -1, flag = 0, quit = 0; uint8_t buf[BUFSIZE]; if (fd > 1) fm = fd+1; else fm = 1+1; while (!interrupted && (!quit || outgoing || incoming)) { fd_set rfds, wfds; int c, b; if ((c = detect_carrier(fd)) < 0) { if (errno != EIO) fprintf(stderr, "Failure reading DCD line: %s\n", strerror(errno)); goto finish; } else if (!c) { free(outgoing); outgoing = NULL; quit = 1; break; } b = 0; FD_ZERO(&rfds); if (!quit) { if (!outgoing) { FD_SET(0, &rfds); b = 1; } if (!incoming) { b = 1; FD_SET(fd, &rfds); } } FD_ZERO(&wfds); if (outgoing) { b=1; FD_SET(fd, &wfds); } if (incoming) { b = 1; FD_SET(1, &wfds); } if (!b) break; if ((r = select(fm, &rfds, &wfds, 0, 0)) < 0) { if (errno == EINTR) continue; fprintf(stderr, "select(): %s\n", strerror(errno)); goto finish; } if (!outgoing && FD_ISSET(0, &rfds)) { if ((outgoing_len = read(0, buf, sizeof(buf))) < 0) { fprintf(stderr, "read(): %s\n", strerror(errno)); goto finish; } if (!outgoing_len) quit = 1; else { outgoing = escape_dle(buf, outgoing_len, (size_t*) &outgoing_len); outgoing_idx = 0; } } if (outgoing && FD_ISSET(fd, &wfds)) { ssize_t l; if ((l = write(fd, outgoing+outgoing_idx, outgoing_len-outgoing_idx)) < 0) { fprintf(stderr, "write(): %s\n", strerror(errno)); goto finish; } if (!l) quit = 1; else { outgoing_idx += l; if (outgoing_idx >= outgoing_len) { free(outgoing); outgoing = NULL; outgoing_len = outgoing_idx = 0; } } } if (!incoming && FD_ISSET(fd, &rfds)) { if ((incoming_len = read(fd, buf, sizeof(buf))) < 0) { fprintf(stderr, "read(): %s\n", strerror(errno)); goto finish; } if (!incoming_len) quit = 1; else { incoming = filter_dle(buf, incoming_len, (size_t*) &incoming_len, &flag, &quit); incoming_idx = 0; } } if (incoming && FD_ISSET(1, &wfds)) { ssize_t l; if (isatty(1)) { if (!warned) { fprintf(stderr, "I won't print out binary data to a tty. Pipe stdout to somewhere else.\n"); warned = 1; } free(incoming); incoming = NULL; incoming_len = incoming_idx = 0; } else { if ((l = write(1, incoming+incoming_idx, incoming_len-incoming_idx)) < 0) { fprintf(stderr, "write(1): %s\n", strerror(errno)); goto finish; } if (!l) quit = 1; else { incoming_idx += l; if (incoming_idx >= incoming_len) { free(incoming); incoming = NULL; incoming_len = incoming_idx = 0; } } } } } r = 0; finish: free(incoming); free(outgoing); return r; } #ifdef HAVE_SPANDSP #define NSAMPLES 240 static void phase_e_handler(t30_state_t *s, void *user_data, int result) { t30_stats_t t; char local_ident[21]; char far_ident[21]; if (!result) { fprintf(stderr, "Phase E not successful.\n"); return; } fax_get_transfer_statistics(s, &t); fax_get_far_ident(s, far_ident); fax_get_local_ident(s, local_ident); fprintf(stderr, "* Remote station ID: %s\n" "* Local station ID: %s\n" "* Pages transferred: %i\n" "* Image resolution: %ix%i\n" "* Transfer rate: %i\n", far_ident, local_ident, t.pages_transferred, t.column_resolution, t.row_resolution, t.bit_rate); } static void phase_d_handler(t30_state_t *s, void *user_data, int result) { t30_stats_t t; if (!result) { fprintf(stderr, "Phase D not sucessful.\n"); return; } fax_get_transfer_statistics(s, &t); fprintf(stderr, "* Pages transferred: %i\n" "* Image size: %ix%i\n" "* Image resolution: %ix%i\n" "* Transfer rate: %i\n" "* Bad rows: %i\n" "* Longest bad row run: %i\n" "* Compression type: %i\n" "* Image size: %i\n", t.pages_transferred, t.columns, t.rows, t.column_resolution, t.row_resolution, t.bit_rate, t.bad_rows, t.longest_bad_row_run, t.encoding, t.image_size); } static int fax_loop(int fd) { t30_state_t fax; uint8_t *incoming_ulaw = NULL, *outgoing_raw = NULL; size_t outgoing_raw_index = 0, outgoing_raw_length = 0; int r = -1; int flag = 0, quit = 0; assert(fd >= 0); fax_init(&fax, !answer, NULL); if (fax_ident) fax_set_local_ident(&fax, fax_ident); if (answer) fax_set_rx_file(&fax, fax_tiff_file); else fax_set_tx_file(&fax, fax_tiff_file); fax_set_phase_e_handler(&fax, phase_e_handler, NULL); fax_set_phase_d_handler(&fax, phase_d_handler, NULL); while (!quit && !interrupted) { int q; struct timeval tv; fd_set rfds; tv.tv_sec = 0; tv.tv_usec = 10000; FD_ZERO(&rfds); FD_SET(fd, &rfds); if (select(fd+1, &rfds, NULL, NULL, &tv) < 0) { fprintf(stderr, "select(): %s\n", strerror(errno)); goto finish; } /* putchar('.'); */ /* fflush(stdout); */ if (FD_ISSET(fd, &rfds)) { uint8_t incoming_raw[NSAMPLES]; int16_t samples[NSAMPLES]; size_t nsamples, i; ssize_t rx; if ((rx = read(fd, incoming_raw, NSAMPLES)) <= 0) { fprintf(stderr, "read(): %s\n", rx < 0 ? strerror(errno) : "EOF"); goto finish; } if (!(incoming_ulaw = filter_dle(incoming_raw, (size_t) rx, &nsamples, &flag, &quit))) goto finish; assert(nsamples <= NSAMPLES); for (i = 0; i < nsamples; i++) samples[i] = st_ulaw2linear16(incoming_ulaw[i]); free(incoming_ulaw); incoming_ulaw = NULL; if (fax_rx_process(&fax, samples, nsamples) != 0) { fprintf(stderr, "Got FAX EOF\n"); break; } /* putchar('x'); */ /* fflush(stdout); */ /* fprintf(stderr, "Processing %i RX samples\n", nsamples); */ } if (ioctl(fd, TIOCOUTQ, &q) < 0) { fprintf(stderr, "TIOCOUTQ failed: %s\n", strerror(errno)); goto finish; } if (q < NSAMPLES) { if (!outgoing_raw) { int16_t samples[NSAMPLES]; size_t nsamples; if ((nsamples = fax_tx_process(&fax, samples, NSAMPLES)) > 0) { uint8_t outgoing_ulaw[NSAMPLES]; size_t i; /* putchar('o'); */ /* fflush(stdout); */ /* fprintf(stderr, "Produced %i TX samples\n", nsamples); */ for (i = 0; i < nsamples; i++) outgoing_ulaw[i] = st_14linear2ulaw(samples[i] >> 2); if (!(outgoing_raw = escape_dle(outgoing_ulaw, nsamples, &outgoing_raw_length))) goto finish; outgoing_raw_index = 0; } } if (outgoing_raw) { ssize_t tx; size_t t = outgoing_raw_length - outgoing_raw_index; assert(outgoing_raw_index < outgoing_raw_length); assert(t > 0); if ((tx = write(fd, outgoing_raw + outgoing_raw_index, t)) < 0) { fprintf(stderr, "write(): %s\n", strerror(errno)); goto finish; } outgoing_raw_index += tx; if (outgoing_raw_index >= outgoing_raw_length) { free(outgoing_raw); outgoing_raw = NULL; outgoing_raw_index = outgoing_raw_length = 0; } } } } r = 0; finish: free(incoming_ulaw); free(outgoing_raw); return r; } #endif /* The function that does the modem work*/ int go(void) { int i, r = -1, fd = -1; char fn[PATH_MAX]; char buf[64]; if ((device ? device_lock(device, appname) : device_lock_first(appname, fn, sizeof(fn))) < 0) { fprintf(stderr, "Could not get lock on device: %s\n", strerror(errno)); goto fail; } if (!device) if (!(device = strdup(fn))) { fprintf(stderr, "Out of memory!\n"); goto fail; } if ((fd = tty_open(device)) < 0) { fprintf(stderr, "Could not open device: %s\n", strerror(errno)); goto fail; } for (i = 0; at_commands[i] != 0; i+=2) if (tty_command(fd, at_commands[i], at_commands[i+1], COMMAND_TIMEOUT) < 0) { fprintf(stderr, "Initialisation failure.\n"); goto fail; } if (answer) { snprintf(buf, sizeof(buf), "AT&L%s\n", source_msn); if (tty_command(fd, buf, "OK\r\n", COMMAND_TIMEOUT) < 0) { fprintf(stderr, "Initialisation failure.\n"); goto fail; } } else { snprintf(buf, sizeof(buf), "AT&E%s\n", source_msn); if (tty_command(fd, buf, "OK\r\n", COMMAND_TIMEOUT) < 0) { fprintf(stderr, "Initialisation failure.\n"); goto fail; } } if (answer) snprintf(buf, sizeof(buf), "ATS0=%i\n", rings); else snprintf(buf, sizeof(buf), "ATD%s\n", destination_msn); if (tty_command(fd, buf, "VCON\r\n", timeout) < 0) { fprintf(stderr, "Failed to accept connection.\n"); r = -2; goto fail; } if (tty_command(fd, "AT+VTX+VRX\n", "CONNECT\r\n\r\nCONNECT\r\n", COMMAND_TIMEOUT) < 0) { fprintf(stderr, "Could not switch to full-duplex audio: %s\n", strerror(errno)); goto fail; } #ifdef HAVE_SPANDSP if ((fax_enabled ? fax_loop(fd) : cat_loop(fd)) < 0) goto fail; #else if (cat_loop(fd) < 0) goto fail; #endif buf[0] = DLE; buf[1] = DC4; buf[2] = DLE; buf[3] = ETX; loop_write(fd, buf, 4); tcdrain(fd); tty_puts(fd, "ATH\n"); r = 0; fail: if (fd >= 0) { hard_hangup(fd); tty_puts(fd, "AT&F\n"); tty_close(fd); device_unlock(); } return r; } struct option long_options[] = { {"help", 0, 0, 'h'}, {"timeout", 1, 0, 't'}, {"device", 1, 0, 'd'}, {"verbose", 0, 0, 'v'}, {"answer", 0, 0, 'a'}, {"ring", 1, 0, 'r'}, {"version", 0, 0, 'V'}, #ifdef HAVE_SPANDSP {"fax", 1, 0, 'F'}, {"local-ident", 1, 0, 'n'}, #endif {0, 0, 0, 0} }; /* Most important part of the program*/ void usage() { printf("Usage: %s [options] \n" " %s -a [options] \n" "\n" "Options:\n" " -h, --help Shows this usage information\n" " -t, --timeout=TIMEOUT Sets a timeout (%i)\n" " -d, --device=DEVICE Selects a device (if not specified, the first available /dev/ttyInn is used)\n" " -v, --verbose Enables debug mode (%s)\n" " -a, --answer Wait for incoming call instead of calling out (%s)\n" " -r, --ring=RINGS On incoming call answer after given RINGs (%i)\n" " -V, --version Show %s version number\n" #ifdef HAVE_SPANDSP " -F, --fax=FILE Send/Receive facsimile in the specified TIFF file (%s)\n" " -n, --local-ident=IDENT Identification string for local peer during facsimile transmission (%s)\n" #endif , appname, appname, timeout, debug ? "on" : "off", answer ? "on" : "off", rings, appname #ifdef HAVE_SPANDSP , fax_tiff_file, fax_ident #endif ); } /* Parses command line and executes go()*/ int main (int argc, char *argv[]) { int c, r = 1; appname = get_filename(argv[0]); while((c = getopt_long(argc, argv, "ht:d:var:VF:n:", long_options, NULL)) >= 0) { switch (c) { case 'V' : printf("%s "VERSION"\n", appname); r = 0; goto finish; case 'h' : usage(); r = 0; goto finish; case 't' : if (optarg) { if ((timeout = atoi(optarg)) < 1) { fprintf(stderr, "Timeout must be >= 1.\n"); goto finish; } } else { fprintf(stderr, "Expected argument on timeout option.\n"); goto finish; } break; case 'd' : if (optarg) { free(device); device = strdup(optarg); } else { fprintf(stderr, "Expected argument on device option.\n"); goto finish; } break; case 'v' : debug = !debug; break; case 'a' : answer = !answer; break; case 'r' : if (optarg) { if ((rings = atoi(optarg)) < 1) { fprintf(stderr, "RINGs must be >= 1.\n"); goto finish; } } else { fprintf(stderr, "Expected argument on rings option.\n"); goto finish; } break; #ifdef HAVE_SPANDSP case 'F': free(fax_tiff_file); fax_tiff_file = strdup(optarg); fax_enabled = 1; break; case 'n': free(fax_ident); fax_ident = strdup(optarg); break; #endif case '?' : goto finish; } } if (answer) { if (optind != argc-1) { fprintf(stderr, "You did not specify the listening MSN.\n"); usage(); goto finish; } source_msn = strdup(argv[optind]); } else { if (optind != argc-2) { fprintf(stderr, "You did not specify both destination and source MSNs.\n"); usage(); goto finish; } source_msn = strdup(argv[optind]); destination_msn = strdup(argv[optind+1]); } if (!timeout) timeout = answer ? -1 : DEFAULT_TIMEOUT; signal(SIGINT, &sigint); signal(SIGALRM, &sigalrm); signal(SIGPIPE, SIG_IGN); siginterrupt(SIGINT, 1); siginterrupt(SIGALRM, 1); r = go() < 0 ? 1 : 0; finish: free(source_msn); free(destination_msn); free(device); #ifdef HAVE_SPANDSP free(fax_ident); free(fax_tiff_file); #endif return r; }