563 lines
14 KiB
C
563 lines
14 KiB
C
/*
|
|
* ISDN handling functions
|
|
*
|
|
* This file is part of ANT (Ant is Not a Telephone)
|
|
*
|
|
* Copyright 2002, 2003 Roland Stigge
|
|
*
|
|
* ANT 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.
|
|
*
|
|
* ANT 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 ANT; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
/* GNU headers */
|
|
#include <stdio.h>
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_TERMIOS_H
|
|
#include <termios.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#endif
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
|
|
/* own header files */
|
|
#include "globals.h"
|
|
#include "isdn.h"
|
|
|
|
static char* calls_filenames[] =
|
|
{ "/var/lib/isdn/calls", "/var/log/isdn/calls", "/var/log/isdn.log" };
|
|
char* isdn_calls_filename_from_config = NULL;
|
|
|
|
/*
|
|
* locks (via lock file) and opens a (free) ttyI device
|
|
*
|
|
* output:
|
|
* isdn_device_name, isdn_lockfile_name
|
|
*
|
|
* returns isdn file descriptor on success, -1 otherwise
|
|
*/
|
|
int open_isdn_device(char **isdn_device_name, char **isdn_lockfile_name) {
|
|
int i = 0; /* try from 0 */
|
|
int found = 0; /* free file name found */
|
|
char buf[64];
|
|
int fd;
|
|
int n;
|
|
int pid;
|
|
|
|
while (i < 64 && !found) {
|
|
snprintf(buf, sizeof(buf), "%s/LCK..ttyI%d", LOCK_PATH, i);
|
|
*isdn_lockfile_name = strdup(buf);
|
|
if ((fd = open(*isdn_lockfile_name, O_RDONLY)) >= 0) { /* exists */
|
|
/* stale? -> remove (for re-use) */
|
|
n = read(fd, buf, sizeof(buf) - 1);
|
|
close(fd);
|
|
if (n > 0) {
|
|
if (n == 4) {
|
|
if (sizeof(int) == 4)
|
|
pid = *(int*) buf;
|
|
else
|
|
pid = 0;
|
|
} else {
|
|
buf[n] = 0;
|
|
sscanf(buf, "%d", &pid);
|
|
}
|
|
if (pid > 0) {
|
|
if (kill((pid_t) pid, 0) < 0 && errno == ESRCH) { /* stale */
|
|
if (!unlink(*isdn_lockfile_name))
|
|
found = 1; /* file removed */
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (errno == ENOENT)
|
|
found = 1; /* file doesn't exist */
|
|
}
|
|
if (!found) i++;
|
|
}
|
|
|
|
if (found && i < 64) { /* got a valid lock file name */
|
|
/* lock device */
|
|
if ((fd = open(*isdn_lockfile_name, O_WRONLY | O_CREAT, 0666)) < 0) {
|
|
return -1;
|
|
}
|
|
snprintf(buf, sizeof(buf), "%10ld\n", (long)getpid());
|
|
write(fd, buf, strlen(buf));
|
|
close(fd);
|
|
|
|
/* finally name and open the device itself */
|
|
snprintf(buf, sizeof(buf), "/dev/ttyI%d", i);
|
|
*isdn_device_name = strdup(buf);
|
|
|
|
/* tty device would possibly block on open -> O_NONBLOCK */
|
|
return open(*isdn_device_name, O_RDWR | O_NONBLOCK);
|
|
} else
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* tries to read the given string from the specified tty (e.g. a ttyI)
|
|
* (waits for the string if not read immediately)
|
|
*
|
|
* input:
|
|
* fd tty file descriptor
|
|
* s (0-terminated) string to compare with
|
|
* timeout give up after this number of seconds
|
|
* 0: return immediately if no data available
|
|
* -1: no timeout
|
|
* got if got != NULL, store reference to result there
|
|
*
|
|
* output:
|
|
* got pointer to string containing buffer actually read if called
|
|
* with got != NULL
|
|
* -> will be NULL on error
|
|
* NOTE: caller is responsible to free the referenced buffer
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*
|
|
* NOTE: currently uses a fixed size buffer, implying a maximum number
|
|
* of bytes read
|
|
*/
|
|
int tty_read(int fd, char *s, int timeout, char **got) {
|
|
int failed = 0; /* 0 or 1*/
|
|
int buf_last = 0; /* index of end of string ('\0') */
|
|
char buf[256] = "";
|
|
struct timeval tv, *tvp;
|
|
fd_set fds;
|
|
|
|
if (timeout >= 0) {
|
|
tv.tv_sec = timeout;
|
|
tv.tv_usec = 0;
|
|
tvp = &tv;
|
|
} else
|
|
tvp = NULL;
|
|
|
|
while (!strstr(buf, s) && !failed) {
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
|
|
if (buf_last == sizeof(buf) - 1) /* buffer full */
|
|
failed = 1;
|
|
else {
|
|
if (select(FD_SETSIZE, &fds, 0, 0, tvp) == 1) { /* input ready */
|
|
read(fd, &buf[buf_last], 1); /* read 1 more byte */
|
|
buf[++buf_last] = 0;
|
|
} else { /* timeout or signal */
|
|
failed = 1;
|
|
}
|
|
}
|
|
}
|
|
/* printf("%s\n", buf); */
|
|
if (got != NULL) {
|
|
*got = strdup(buf);
|
|
}
|
|
return -failed;
|
|
}
|
|
|
|
/*
|
|
* writes specified 0-terminated string to the specified tty
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int tty_write(int fd, char *s) {
|
|
if (write(fd, s, strlen(s)) != (int)strlen(s)) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* clears input and output queue of specified tty
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int tty_clear(int fd) {
|
|
return tcflush(fd, TCIOFLUSH);
|
|
}
|
|
|
|
/*
|
|
* ISDN initialization
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int init_isdn_device(int isdn_fd, struct termios *backup) {
|
|
int failed = 0;
|
|
int flags;
|
|
char *(init_commands[]) = {
|
|
"AT&F\n", "OK\r\n", /* restore factory settings */
|
|
"ATE0\n", "OK\r\n", /* echo off */
|
|
/*"AT&B128\n", "OK\r\n",*/ /* set outgoing packet size */
|
|
"ATI\n", "Linux ISDN\r\nOK\r\n", /* check for real isdn device */
|
|
"AT+FCLASS=8\n", "OK\r\n", /* enable audio mode */
|
|
"AT+VSM=6\n", "OK\r\n", /* set uLaw format */
|
|
"ATS18=1\n", "OK\r\n", /* set audio mode (dial out) */
|
|
"ATS14=4\n", "OK\r\n", /* layer-2 protocol = transparent */
|
|
"ATS13.4=1\n", "OK\r\n", /* CALLER NUMBER after first RING */
|
|
"ATS13.6=1\n", "OK\r\n", /* enable RUNG messages */
|
|
"ATS23=1\n", "OK\r\n" /* Calling Party Number (CPN) extended RING */
|
|
};
|
|
struct termios settings;
|
|
unsigned int i;
|
|
|
|
/* assume:
|
|
* ttyI speed is 64000 by default
|
|
*/
|
|
|
|
/* switch O_NONBLOCK off (turned on while opening) */
|
|
flags = fcntl(isdn_fd, F_GETFL, 0);
|
|
if (flags != -1) {
|
|
if (fcntl(isdn_fd, F_SETFL, flags & ~O_NONBLOCK) == -1) {
|
|
perror("G_GETFL");
|
|
return -1;
|
|
}
|
|
} else {
|
|
perror("F_GETFL");
|
|
return -1;
|
|
}
|
|
|
|
if (tcgetattr(isdn_fd, &settings))
|
|
return -1;
|
|
|
|
memcpy(backup, &settings, sizeof(struct termios));
|
|
|
|
settings.c_lflag &= ~(ICANON | ECHO | ECHONL | ECHOCTL | ISIG);
|
|
settings.c_iflag &= ~(IXON | IXOFF | IXANY | IGNCR | ICRNL | INLCR );
|
|
settings.c_iflag |= IGNPAR;
|
|
settings.c_cflag |= HUPCL;
|
|
settings.c_oflag &= ~ONLCR;
|
|
|
|
settings.c_cc[VMIN] = 1;
|
|
settings.c_cc[VTIME] = 0;
|
|
|
|
if (tcsetattr(isdn_fd, TCSANOW, &settings))
|
|
return -1;
|
|
|
|
i = 0;
|
|
|
|
while (!failed && i < sizeof(init_commands) / sizeof(char*)) {
|
|
tty_clear(isdn_fd);
|
|
if (tty_write(isdn_fd, init_commands[i++]))
|
|
failed = 1;
|
|
else
|
|
if (tty_read(isdn_fd, init_commands[i++], ISDN_COMMAND_TIMEOUT, NULL))
|
|
failed = 1;
|
|
}
|
|
|
|
return -failed;
|
|
}
|
|
|
|
/*
|
|
* ISDN de-initialization, restores termios settings
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int deinit_isdn_device(int isdn_fd, struct termios *backup) {
|
|
return tcsetattr(isdn_fd, TCSANOW, backup);
|
|
}
|
|
|
|
/*
|
|
* sets an MSN for the specified ttyI (originating MSN)
|
|
* fd is assumed to be in command mode
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int isdn_setMSN(int isdn_fd, char *msn) {
|
|
char buf[256];
|
|
|
|
if (snprintf(buf, sizeof(buf), "AT&E%s\n", msn) >= (int)sizeof(buf)) {
|
|
fprintf(stderr, "Error: Specified MSN too long.\n");
|
|
return -1;
|
|
}
|
|
tty_clear(isdn_fd);
|
|
if (tty_write(isdn_fd, buf))
|
|
return -1;
|
|
else
|
|
if (tty_read(isdn_fd, "OK\r\n", ISDN_COMMAND_TIMEOUT, NULL))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sets MSNs for the specified ttyI (MSNs to listen on)
|
|
* fd is assumed to be in command mode
|
|
* msns: semicolon-separated list of msns
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int isdn_setMSNs(int isdn_fd, char *msns) {
|
|
char buf[256];
|
|
|
|
if (snprintf(buf, sizeof(buf), "AT&L%s\n", msns) >= (int)sizeof(buf)) {
|
|
fprintf(stderr, "Error: Specified MSNs string too long.\n");
|
|
return -1;
|
|
}
|
|
tty_clear(isdn_fd);
|
|
if (tty_write(isdn_fd, buf))
|
|
return -1;
|
|
else
|
|
if (tty_read(isdn_fd, "OK\r\n", ISDN_COMMAND_TIMEOUT, NULL))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* stops audio mode (enter command mode)
|
|
*
|
|
* input: self_hangup: we want to hang up ourselves
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int isdn_stop_audio(int isdn_fd, int self_hangup) {
|
|
int result = 0;
|
|
unsigned char abort_sending[] = {DLE, DC4, 0};
|
|
unsigned char end_of_audio[] = {DLE, ETX, 0};
|
|
|
|
char *got;
|
|
|
|
tty_clear(isdn_fd);
|
|
|
|
if (self_hangup) { /* we want to hang up ourselves */
|
|
if (tty_write(isdn_fd, end_of_audio)) { /* ETX - end of audio */
|
|
fprintf(stderr, "Error sending ETX (End of audio).\n");
|
|
return -1;
|
|
}
|
|
if (tty_write(isdn_fd, abort_sending)) {/* abort sending (request) (DC4) */
|
|
fprintf(stderr, "Error sending DC4 (abort sending).\n");
|
|
return -1;
|
|
}
|
|
if (tty_read(isdn_fd, end_of_audio, ISDN_COMMAND_TIMEOUT, NULL)) {
|
|
fprintf(stderr, "Error waiting for ETX (End of audio).\n");
|
|
return -1;
|
|
}
|
|
if (tty_read(isdn_fd, "\r\n", ISDN_COMMAND_TIMEOUT, NULL)) {
|
|
fprintf(stderr, "Error getting line break.\n");
|
|
return -1;
|
|
}
|
|
if (tty_read(isdn_fd, "\r\n", ISDN_COMMAND_TIMEOUT, &got)) {
|
|
fprintf(stderr, "Error getting new line.\n");
|
|
}
|
|
if (!strstr(got, "VCON")) {
|
|
fprintf(stderr, "Error getting status.\n");
|
|
result = -1;
|
|
}
|
|
free(got);
|
|
} else { /* remote side hangup */
|
|
if (tty_write(isdn_fd, end_of_audio)) { /* ETX - end of audio */
|
|
fprintf(stderr, "Error sending ETX (End of audio).\n");
|
|
return -1;
|
|
}
|
|
/* there doesn't seem to come anything after remote hangup ... (?) */
|
|
}
|
|
tty_clear(isdn_fd);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* hang up isdn device
|
|
*
|
|
* NOTE: assumes command mode
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int isdn_hangup(int isdn_fd) {
|
|
tty_clear(isdn_fd);
|
|
if (tty_write(isdn_fd, "ATH\n"))
|
|
return -1;
|
|
if (tty_read(isdn_fd, "OK\r\n", ISDN_COMMAND_TIMEOUT, NULL))
|
|
return -1;
|
|
tty_clear(isdn_fd);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Set isdn device to block mode, assuming in audio mode
|
|
*
|
|
* in blockmode, the minimum number of bytes possible to read from specified
|
|
* ttyI will be set to DEFAULT_ISDNBUF_SIZE and non blocking reads and writes
|
|
* will be enabled
|
|
*
|
|
* input:
|
|
* isdn_fd file descriptor
|
|
* flag 0 == off, 1 == on
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int isdn_blockmode(int isdn_fd, int flag) {
|
|
struct termios settings;
|
|
int min, time;
|
|
int flags;
|
|
|
|
flags = fcntl(isdn_fd, F_GETFL, 0);
|
|
if (flags == -1) {
|
|
perror("F_GETFL");
|
|
return -1;
|
|
}
|
|
|
|
if (flag) {
|
|
min = DEFAULT_ISDNBUF_SIZE;
|
|
time = 0; /* 10 == 1sec */
|
|
flags |= O_NONBLOCK; /* select consumes much cpu time with this */
|
|
} else {
|
|
min = 1;
|
|
time = 0;
|
|
flags &= ~O_NONBLOCK;
|
|
}
|
|
|
|
if (fcntl(isdn_fd, F_SETFL, flags) == -1) {
|
|
perror("G_GETFL");
|
|
return -1;
|
|
}
|
|
|
|
if (tcgetattr(isdn_fd, &settings)) {
|
|
perror("isdn_blockmode, tcgetattr");
|
|
return -1;
|
|
}
|
|
|
|
settings.c_cc[VMIN] = min;
|
|
settings.c_cc[VTIME] = time;
|
|
|
|
if (tcsetattr(isdn_fd, TCSANOW, &settings)) {
|
|
perror("isdn_blockmode, tcsetattr");
|
|
return -1;
|
|
}
|
|
|
|
/* verify */
|
|
if (tcgetattr(isdn_fd, &settings))
|
|
return -1;
|
|
if (settings.c_cc[VMIN] != min) {
|
|
fprintf(stderr,"Error setting block size. New block size: %d.\n",
|
|
settings.c_cc[VMIN]);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* closes ttyI device and removes lock file
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int close_isdn_device(int isdn_fd, char *isdn_device_name,
|
|
char *isdn_lockfile_name) {
|
|
if (close(isdn_fd)) {
|
|
return -1;
|
|
}
|
|
if (unlink(isdn_lockfile_name)) {
|
|
fprintf(stderr, "Removing isdn device lock file: unlink error.\n");
|
|
return -1;
|
|
}
|
|
free(isdn_lockfile_name);
|
|
free(isdn_device_name);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* dials specified number (voice call)
|
|
* (proper ttyI init is assumed)
|
|
*
|
|
* returns:
|
|
* 0 on success (VCON)
|
|
* 1 on busy
|
|
* -1 otherwise (error / timeout)
|
|
*/
|
|
int isdn_dial(int isdn_fd, char *number, int timeout) {
|
|
char *s;
|
|
int result;
|
|
char *got;
|
|
|
|
if ((s = (char*) malloc(strlen(number) + 5))) {
|
|
|
|
snprintf(s, strlen(number) + 5, "ATD%s\n", number);
|
|
tty_clear(isdn_fd);
|
|
result = tty_write(isdn_fd, s);
|
|
free(s);
|
|
if (result) {
|
|
fprintf(stderr, "Error dialing.\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (tty_read(isdn_fd, "\r\n", timeout, NULL)) /* error / timeout */
|
|
return -1;
|
|
|
|
if (tty_read(isdn_fd, "\r\n", ISDN_COMMAND_TIMEOUT, &got))
|
|
return -1;
|
|
|
|
if (strstr(got, "BUSY\r\n")) {
|
|
free(got);
|
|
return 1;
|
|
}
|
|
|
|
if (strstr(got, "VCON\r\n")) {
|
|
free(got);
|
|
return 0;
|
|
}
|
|
|
|
free(got);
|
|
return -1; /* failed somehow */
|
|
}
|
|
|
|
/*
|
|
* sets ttyI to full duplex mode
|
|
* (should be called directly after VCON)
|
|
*
|
|
* returns 0 on success, -1 otherwise
|
|
*/
|
|
int isdn_set_full_duplex(int isdn_fd) {
|
|
tty_clear(isdn_fd);
|
|
if (tty_write(isdn_fd, "AT+VRX+VTX\n"))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* returns the name of the calls file (originally just /var/lib/isdn/calls)
|
|
* on error, returns NULL
|
|
*/
|
|
char* isdn_get_calls_filename(void) {
|
|
unsigned int i;
|
|
int fd;
|
|
|
|
if (isdn_calls_filename_from_config &&
|
|
(fd = open(isdn_calls_filename_from_config, O_RDONLY)) != -1)
|
|
{
|
|
close(fd);
|
|
if (debug)
|
|
fprintf(stderr,
|
|
"Using calls file listed in I4L config.\n");
|
|
return isdn_calls_filename_from_config;
|
|
}
|
|
for (i = 0; i < sizeof(calls_filenames) / sizeof(char*); i++) {
|
|
if ((fd = open(calls_filenames[i], O_RDONLY)) != -1) {
|
|
close(fd);
|
|
return calls_filenames[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|