/*----------------------------------------------------------------------*/ /* vmcp Voice Modem Control Program. */ /* A little program to send commands to the modem */ /* with special features for voice modems. */ /* */ /* Version 0.6 Sep 8 1997 */ /* */ /* Author: Niccolo Rigacci */ /* */ /* Copyright (C) 1996 Niccolo Rigacci */ /* */ /* 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. */ /*----------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include /*----------------------------------------------------------------------*/ /* Macro definitions /*----------------------------------------------------------------------*/ /* DLE Escape char for voice mode. */ /* BREAK_KEY Break key for -k switch. */ /* MAX_LEN_FNAME Max len for filename strings. */ /* TEMP_DIR Where to put the lock file. */ /* DEVS_DIR Where modem devices are. */ /* DFLT_DEV Default modem device. */ /* DEV_NULL Black-hole file. */ /* HDB_LCK_FORMAT Format string for printing PID in HDB lockfile. */ /* HDB_LCK_LEN Len of HDB lockfile. */ #define TRUE 1 #define FALSE 0 #define DLE 16 #define BREAK_KEY 27 #define MAX_LEN_FNAME 255 #define TEMP_DIR "/tmp/" #define DEVS_DIR "/dev/" #define DFLT_DEV "ttyS0" #define DEV_NULL "/dev/null" #define HDB_LCK_FORMAT "%10d\n" #define HDB_LCK_LEN 11 #define EXIT_OK 0 #define EXIT_TIMEOUT 100 #define EXIT_KEYPRESS 101 #define ERR_MEMORY 150 #define ERR_SYNTAX 151 #define ERR_DEVICE 152 #define ERR_OPEN 153 #define ERR_WRITE 154 #define ERR_CLOSE 155 #define ERR_LOCK 156 #define ERR_IOCTL 157 #define ERR_LCKFILE 158 #define ERR_BAUDRATE 159 #define ERR_SIGNAL 200 /*----------------------------------------------------------------------*/ /* Function prototypes /*----------------------------------------------------------------------*/ int out_chr(int c); int out_esc(int c); void no_response(int i); void end_on_signal(int i); void fatal(int exit_code); char *strdupcvt(char *str); void safeprint(char *msg, char *str); void port_reset(int fd); /*----------------------------------------------------------------------*/ /* Global variables /*----------------------------------------------------------------------*/ struct termios ts; /* Saved stdin line settings */ int debug = FALSE; /* Be verbose */ int timeout = 5; /* Seconds to wait */ int break_on_key = FALSE; /* Return on keypress */ int escaping_dle = FALSE; /* Handle escaping of DLE */ int make_lock_file = FALSE; /* Must create lock file */ int made_lock_file = FALSE; /* Lock file created */ int changed_std_input = FALSE; /* Std input settings changed */ int quit_on_eof = FALSE; /* Return on end of input file */ int skip_out_string = FALSE; /* Do not output the -W string */ FILE *inp_fp; /* Input file pointer */ FILE *out_fp; /* Output file pointer */ FILE *esc_fp; /* Escape file pointer */ unsigned char *lckfname = NULL; /* Name of the lock file */ unsigned char *command = NULL; /* Command to send */ unsigned char *out_string = NULL; /* String to wait */ unsigned char *esc_string = NULL; /* Escape chars to wait */ int out_len; /* Len of out_string */ int out_pos = 0; /* Index to scan out_string */ int ok_exit_code; /* Exit code if OK (0 - 99) */ long baud_rate; /* Baud rate of serial line */ /*----------------------------------------------------------------------*/ /* Main program /*----------------------------------------------------------------------*/ void main(int argc, char **argv) { int i, c, fd, opt; int eof_inp = TRUE; int finito = FALSE; int reset_port = FALSE; int previous_dle = FALSE; int sent_one_dle = FALSE; unsigned char pid_buf[HDB_LCK_LEN + 1]; unsigned char filename[MAX_LEN_FNAME + 1]; unsigned char *inp_file, *out_file, *esc_file; unsigned char *device; unsigned char buf; struct termios new_ts; fd_set rfds; pid_t pid; /* Set default values for some strings. */ inp_file = out_file = esc_file = DEV_NULL; device = DFLT_DEV; /* Process command line arguments */ while ((opt = getopt(argc, argv, "c:d:eghi:kl:o:qs:t:w:W:x:z:")) != -1) { switch (opt) { case 'e': escaping_dle = TRUE; break; case 'g': debug = TRUE; break; case 'k': break_on_key = TRUE; break; case 'q': quit_on_eof = TRUE; break; case 'c': if ((command = strdupcvt(optarg)) == NULL) fatal(ERR_MEMORY); break; case 'd': if ((device = strdup(optarg)) == NULL) fatal(ERR_MEMORY); break; case 'h': fprintf(stderr, "Voice Modem Control Program\n"); fprintf(stderr, "Usage: %s [OPTION]...\n", argv[0]); fprintf(stderr, "\t-c command\n\t-d device\n\t-e\n\t-g\n\t-h\n"); fprintf(stderr, "\t-i in_file\n\t-k\n\t-l lockfile\n"); fprintf(stderr, "\t-o out_file\n\t-q\n\t-s esc_file\n"); fprintf(stderr, "\t-t sec\n\t-w waitstring\n\t-W skipstring\n"); fprintf(stderr, "\t-x esc_string\n\t-z baudrate\n"); exit(0); break; case 'i': if ((inp_file = strdup(optarg)) == NULL) fatal(ERR_MEMORY); eof_inp = FALSE; break; case 'l': if ((lckfname = strdup(optarg)) == NULL) fatal(ERR_MEMORY); make_lock_file = TRUE; break; case 'o': if ((out_file = strdup(optarg)) == NULL) fatal(ERR_MEMORY); break; case 's': if ((esc_file = strdup(optarg)) == NULL) fatal(ERR_MEMORY); break; case 't': timeout = atoi(optarg); break; case 'W': skip_out_string = TRUE; case 'w': if ((out_string = strdupcvt(optarg)) == NULL) fatal(ERR_MEMORY); out_len = strlen(out_string); break; case 'x': if ((esc_string = strdup(optarg)) == NULL) fatal(ERR_MEMORY); break; case 'z': baud_rate = atol(optarg); reset_port = TRUE; break; default: fprintf(stderr, "Try `vmcp -h' for help.\n"); fatal(ERR_SYNTAX); break; } } /* Set standard input line settings in non-canonical mode: */ /* no line buffering, no echo, no wait. */ if (break_on_key) { /* Get standard input (file descriptor 0) line settings. */ if (ioctl(0, TCGETS, &ts) != 0) fatal(ERR_IOCTL); new_ts = ts; new_ts.c_lflag &= ~ICANON; new_ts.c_lflag &= ~ECHO; new_ts.c_cc[VTIME] = 0; new_ts.c_cc[VMIN] = 0; if (ioctl(0, TCSETS, &new_ts) != 0) fatal(ERR_IOCTL); changed_std_input = TRUE; } /* Set the timeout alarm. */ if (timeout != 0) { signal(SIGALRM, no_response); alarm(timeout); } /* Set some signal handling functions. */ signal(SIGHUP, end_on_signal); signal(SIGINT, end_on_signal); signal(SIGQUIT, end_on_signal); signal(SIGTERM, end_on_signal); /* Open communication file device. */ sprintf(filename, "%s%s", DEVS_DIR, device); if ((fd = open(filename, O_RDWR | O_NONBLOCK)) == -1) fatal(ERR_DEVICE); if (flock(fd, LOCK_EX | LOCK_NB) != 0) fatal(ERR_LOCK); if (reset_port) { if (debug) fprintf(stderr, "Resetting the serial line\n"); port_reset(fd); } /* Create lock file if required. Use HDB lockfile format: */ /* ten byte ASCII decimal number, with a trailing newline. */ if (make_lock_file) { pid = getpid(); sprintf(pid_buf, HDB_LCK_FORMAT, pid); sprintf(filename, "%sTMP..%d", TEMP_DIR, pid); /* Create a temp lock file and write PID to it. */ if ((i = creat(filename, 0644)) == -1) fatal(ERR_LCKFILE); if (write(i, pid_buf, HDB_LCK_LEN) != HDB_LCK_LEN) fatal(ERR_LCKFILE); if (close(i) != 0) fatal(ERR_LCKFILE); /* Change name to the lock file. */ if (link(filename, lckfname) == 0) made_lock_file = TRUE; unlink (filename); if (!made_lock_file) fatal(ERR_LCKFILE); } /* Write command to communication file device. */ /* NOTE: no check is made for EAGAIN error, we */ /* suppose command string to be small enough */ /* to fit entirely in the output buffer. */ if (command != NULL) { /* Just paranoia: US-Robotic requires a pause */ /* of 1 ms before "AT" commands, we do 2 ms. */ if (strncmp(command, "AT", 2) == 0 || strncmp(command, "at", 2) == 0) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 2000; select(0, NULL, NULL, NULL, &tv); } if (debug) safeprint("Sending", command); i = strlen(command); if (write(fd, command, i) != i) fatal(ERR_WRITE); } else if (debug && (out_string != NULL)) if (skip_out_string) safeprint("Skipping", out_string); else safeprint("Waiting for", out_string); /* Open input, output and escape files. */ if ((inp_fp = fopen(inp_file, "rb")) == NULL) fatal(ERR_OPEN); if ((out_fp = fopen(out_file, "wb")) == NULL) fatal(ERR_OPEN); if ((esc_fp = fopen(esc_file, "wb")) == NULL) fatal(ERR_OPEN); /* Main loop to send input file to communication device's input */ /* and to capture output to output file. If escaping of DLE */ /* char is enabled, escaped chars are sent to escape file. */ while (!finito) { /* If "-i file" has more characters to send, do one. */ if (!eof_inp) { /* Write a char from "-i file" to device, escape DLE if required. */ if ((c = fgetc(inp_fp)) == EOF) { eof_inp = TRUE; finito = quit_on_eof; } else { buf = (unsigned char)c; if (write(fd, &buf, 1) == 1) { /* No error, check for DLE escaping. */ if (buf == DLE && escaping_dle && !sent_one_dle) { sent_one_dle = TRUE; if (ungetc(c, inp_fp) == EOF) fatal(ERR_WRITE); } else sent_one_dle = FALSE; } else { /* Error writing, check for error type. */ if (errno == EAGAIN) { /* May be buffer full, retry later. */ if (ungetc(c, inp_fp) == EOF) fatal(ERR_WRITE); } else fatal(ERR_WRITE); } } } else { /* No more char to send, can wait chars from kbd or device. */ FD_ZERO(&rfds); if (break_on_key) FD_SET(0, &rfds); FD_SET(fd, &rfds); /* Sleep until chars ready for reading. */ select(fd + 1, &rfds, NULL, NULL, NULL); } /* Read a character from device, escape DLE if required. */ if (read(fd, &buf, 1) == 1) { if (escaping_dle) if (previous_dle) { previous_dle = FALSE; if (buf == DLE) finito = out_chr((int)buf); else finito = out_esc((int)buf); } else if (buf == DLE) previous_dle = TRUE; else finito = out_chr((int)buf); else finito = out_chr((int)buf); } /* Check BREAK_KEY pressed. */ if (break_on_key) if ((getchar() == BREAK_KEY)) { ok_exit_code = EXIT_KEYPRESS; finito = TRUE; } } /* Close input, output, escape files and modem device. */ i = 0; if (fclose(inp_fp) == EOF) i += 1; if (fclose(out_fp) == EOF) i += 2; if (fclose(esc_fp) == EOF) i += 4; if (flock(fd, LOCK_UN) == -1) i += 8; if (close(fd) == -1) i += 16; if (i) { if (debug) fprintf(stderr, "Close error: %d\n", i); fatal(ERR_CLOSE); } else fatal(ok_exit_code); } /*----------------------------------------------------------------------*/ /* Write c to output file, return TRUE if received out_string. */ /*----------------------------------------------------------------------*/ int out_chr(int c) { if (!skip_out_string) fputc(c, out_fp); if (out_string == NULL) return FALSE; else { if ((unsigned char)c == out_string[out_pos]) out_pos++; else out_pos = 0; if (out_pos == out_len) if (skip_out_string) { skip_out_string = FALSE; free(out_string); out_string = NULL; return FALSE; } else { ok_exit_code = EXIT_OK; return TRUE; } else return FALSE; } } /*----------------------------------------------------------------------*/ /* Write c to escape file, return TRUE if c is in esc_string. */ /*----------------------------------------------------------------------*/ int out_esc(int c) { int i; fputc(c, esc_fp); if (esc_string == NULL) return FALSE; else { for (i = 0; esc_string[i] != '\0' && esc_string[i] != (unsigned char)c; i++); if (esc_string[i] == '\0') return FALSE; else { ok_exit_code = ++i; return TRUE; } } } /*----------------------------------------------------------------------*/ /* Timeout: exit program. */ /*----------------------------------------------------------------------*/ void no_response(int i) { fatal(EXIT_TIMEOUT); } /*----------------------------------------------------------------------*/ /* Signal: exit program. */ /*----------------------------------------------------------------------*/ void end_on_signal(int i) { fatal(ERR_SIGNAL + i); } /*----------------------------------------------------------------------*/ /* Fatal errors handling. */ /*----------------------------------------------------------------------*/ void fatal(int exit_code) { /* Restore standard input line settings earlier saved. */ if (break_on_key) if (changed_std_input) if (ioctl(0, TCSETS, &ts) != 0) if (debug) fprintf(stderr, "ioctl(2) error %d\n", errno); /* Remove lock file if required. */ if (made_lock_file) if (unlink(lckfname) != 0) if (debug) fprintf(stderr, "Can't remove %s", lckfname); if (debug) fprintf(stderr, "Exit code %d\n", exit_code); exit(exit_code); } /*----------------------------------------------------------------------*/ /* Make a duplicate of the string, do some backslash parsing, add CR. */ /*----------------------------------------------------------------------*/ char *strdupcvt(char *str) { unsigned char *newstr, *pt1, *pt2; int i, addcr = TRUE; /* Allocate max space for new string. */ if ((newstr = (unsigned char*)malloc(strlen(str) + 3)) != NULL) { strcpy(newstr, str); for (pt1 = pt2 = newstr; *pt1; pt1++, pt2++) { if (*pt1 != '\\') *pt2 = *pt1; else { pt1++; switch (*pt1) { case 'c': pt2--; addcr = FALSE; break; case 'n': *pt2 = '\n'; break; case 'r': *pt2 = '\r'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': *pt2 = 0; for (i = 0; i < 3 && *pt1 && *pt1 >= '0' && *pt1 <= '7'; i++, pt1++) *pt2 = (*pt2 << 3) + (*pt1 & 7); pt1--; break; default: pt1--; *pt2 = *pt1; break; } } } if (addcr) *(pt2++) = '\r'; *pt2 = '\0'; } return newstr; } /*----------------------------------------------------------------------*/ /* Print msg and str. Unprintable chars in str are printed in hex. */ /*----------------------------------------------------------------------*/ void safeprint(char *msg, char *str) { int i; fprintf(stderr, "%s ", msg); for (i = 0; str[i]; i++) { if (str[i] >= ' ' && str[i] <= '~') fprintf(stderr, "%c", str[i]); else fprintf(stderr, " 0x%02x", str[i]); } fprintf(stderr, "\n"); } /*----------------------------------------------------------------------*/ /* Set the serial line to raw, etc. */ /*----------------------------------------------------------------------*/ void port_reset(int fd) { int line; struct termios ts; /* Flush input and output queues. */ if (ioctl(fd, TCFLSH, 2) != 0) fatal(ERR_IOCTL); /* Turn off DTR control line. */ line = TIOCM_DTR; if (ioctl(fd, TIOCMBIC, &line) != 0) fatal(ERR_IOCTL); /* Pauses for 3 seconds. */ /* No modem should resist over 3 seconds with */ /* DTR off: they return at least in command mode! */ sleep(3); /* Turn on DTR control line. */ line = TIOCM_DTR; if (ioctl(fd, TIOCMBIS, &line) != 0) fatal(ERR_IOCTL); /* Fetch the current terminal parameters. */ if (ioctl(fd, TCGETS, &ts) != 0) fatal(ERR_IOCTL); /* Sets hardware control flags: */ /* 8 data bits */ /* Enable receiver */ /* Ignore CD (local connection) */ /* Use RTS/CTS flow control */ ts.c_cflag = CS8 | CREAD | CLOCAL | CRTSCTS; ts.c_iflag = 0; ts.c_oflag = NL0 | CR0 | TAB0 | BS0 | VT0 | FF0; ts.c_lflag = 0; switch ((int)(baud_rate / 100)) { case 96: ts.c_cflag |= B9600; break; case 192: ts.c_cflag |= B19200; break; case 384: ts.c_cflag |= B38400; break; case 576: ts.c_cflag |= B57600; break; case 1152: ts.c_cflag |= B115200; break; case 2304: ts.c_cflag |= B230400; break; default: fatal(ERR_BAUDRATE); break; } /*-----------------------------------------------------------*/ /* All these capabilities are turned off; see termios(2) */ /* and stty(1L) man pages: */ /* */ /* c_cflag CSTOPB PARENB HUPCL */ /* */ /* c_iflag IGNBRK BRKINT IGNPAR PARMRK INPCK */ /* ISTRIP INLCR IGNCR ICRNL IXON */ /* IXOFF IUCLC IXANY IMAXBEL */ /* */ /* ts.c_oflag OPOST OLCUC OCRNL ONLCR ONOCR */ /* ONLRET OFILL OFDEL */ /* */ /* ts.c_lflag ISIG ICANON IEXTEN ECHO ECHOE */ /* ECHOK ECHONL NOFLSH XCASE TOSTOP */ /* ECHOPRT ECHOCTL ECHOKE */ /*-----------------------------------------------------------*/ ts.c_cc[VINTR] = '\0'; ts.c_cc[VQUIT] = '\0'; ts.c_cc[VERASE] = '\0'; ts.c_cc[VKILL] = '\0'; ts.c_cc[VEOF] = '\0'; ts.c_cc[VTIME] = '\0'; ts.c_cc[VMIN] = 1; ts.c_cc[VSWTC] = '\0'; ts.c_cc[VSTART] = '\0'; ts.c_cc[VSTOP] = '\0'; ts.c_cc[VSUSP] = '\0'; ts.c_cc[VEOL] = '\0'; ts.c_cc[VREPRINT] = '\0'; ts.c_cc[VDISCARD] = '\0'; ts.c_cc[VWERASE] = '\0'; ts.c_cc[VLNEXT] = '\0'; ts.c_cc[VEOL2] = '\0'; /* Sets the new terminal parameters. */ if (ioctl(fd, TCSETS, &ts) != 0) fatal(ERR_IOCTL); return; }