/* $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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "lock.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif // 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 // 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+VSM=6\n", "OK\r\n", "ATS18=1\n", "OK\r\n", "ATS14=4\n", "OK\r\n", "ATS13.4=1\n", "OK\r\n", "ATS23=1\n", "OK\r\n", 0 }; // 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"; write(2, s, strlen(s)); interrupted = 1; } void sigalrm(int sig) { const char *s = "Timeout\n"; 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 char *filter_dle(unsigned char *data, int *size, int *flag, int *quit) { unsigned char *ret, *m, *s, *d; int l; 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 recieved some DTMF signals, but we ignore them. break; } *flag = 0; continue; } if (*s == DLE) *flag = 1; else *(d++) = *s; } *size = d-ret; if (!(m = realloc(ret, *size))) { free(ret); return NULL; } return m; } // Escape DLE-bytes for sending data to the modem char* escape_dle(unsigned char *data, int *size) { unsigned char *ret, *m, *s, *d; int l, ms; if (!(ret = malloc(ms = (int) (*size*1.05)))) return NULL; for (l = *size, s = data, d = ret; l > 0; l--, s++) { if (*s == DLE) { *(d++) = DLE; (*size)++; if (ms < *size) { int n = d-ret; fprintf(stderr, "REALLOC!\n"); if (!(m = realloc(ret, ms = (int) (*size*1.05)))) { free(ret); return NULL; } ret = m; d = ret+n; } } *(d++) = *s; } if (!(m = realloc(ret, *size))) { free(ret); return NULL; } return m; } // The function that does the modem work int go(void) { int i, flag, warned = 0, r = -1, fd = -1, quit; char *incoming = NULL, *outgoing = NULL; int incoming_idx = 0, incoming_len = 0, outgoing_idx = 0, outgoing_len = 0; int fm; char fn[PATH_MAX]; static char buf[1024]; 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; } 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; } flag = quit= 0; 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) { fprintf(stderr, "Failure reading DCD line: %s\n", strerror(errno)); goto fail; } else if (!c) { if (outgoing) { 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 fail; } if (!outgoing && FD_ISSET(0, &rfds)) { if ((outgoing_len = read(0, buf, sizeof(buf))) < 0) { fprintf(stderr, "read(): %s\n", strerror(errno)); goto fail; } if (!outgoing_len) quit = 1; else { outgoing = escape_dle(buf, &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 fail; } 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 fail; } if (!incoming_len) quit = 1; else { incoming = filter_dle(buf, &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 fail; } if (!l) quit = 1; else { incoming_idx += l; if (incoming_idx >= incoming_len) { free(incoming); incoming = NULL; incoming_len = incoming_idx = 0; } } } } } 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 (incoming) free(incoming); if (outgoing) free(outgoing); 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'}, {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", appname, appname, timeout, debug ? "on" : "off", answer ? "on" : "off", rings, appname); } // Parses command line and executes go() int main (int argc, char *argv[]) { int c, r = 1; appname = basename(argv[0]); while((c = getopt_long(argc, argv, "ht:d:var:V", 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) { if (device) free(device); else 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; 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: if (source_msn) free (source_msn); if (destination_msn) free(destination_msn); if (device) free(device); return r; }