isdn4k-utils/vbox/src/voice.c

726 lines
17 KiB
C

/*
** $Id$
**
** Copyright (C) 1996, 1997 Michael 'Ghandi' Herold
*/
#include "config.h"
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <limits.h>
#include <errno.h>
#include "voice.h"
#include "init.h"
#include "script.h"
#include "modem.h"
#include "log.h"
#include "perms.h"
#include "rcvbox.h"
#include "libvbox.h"
/** Variables ************************************************************/
static int voicestatus = VOICE_ACTION_OK;
static int sequencestatus = ST_NO_INPUT;
char touchtones[TOUCHTONE_BUFFER_LEN + 1];
/** Prototypes ***********************************************************/
static int voice_set_compression(short);
static void voice_set_header(vaheader_t *);
static void voice_handle_touchtone(char);
static void voice_close_or_unlink(int, char *);
static void voice_handle_touchtone_dle(int);
/*************************************************************************/
/** voice_init_section(): Set the voice setup defaults. **/
/*************************************************************************/
void voice_init_section(void)
{
xstrncpy(setup.voice.standardmsg , setup.spool , VOICE_MAX_MESSAGE );
xstrncat(setup.voice.standardmsg , "/messages/standard.msg" , VOICE_MAX_MESSAGE );
xstrncpy(setup.voice.beepmsg , setup.spool , VOICE_MAX_MESSAGE );
xstrncat(setup.voice.beepmsg , "/messages/beep.msg" , VOICE_MAX_MESSAGE );
xstrncpy(setup.voice.timeoutmsg , setup.spool , VOICE_MAX_MESSAGE );
xstrncat(setup.voice.timeoutmsg , "/messages/timeout.msg" , VOICE_MAX_MESSAGE );
xstrncpy(setup.voice.tclscriptname , setup.spool , VOICE_MAX_SCRIPT );
xstrncat(setup.voice.tclscriptname , "/standard.tcl" , VOICE_MAX_SCRIPT );
xstrncpy(setup.voice.checknewpath , setup.spool , VOICE_MAX_CHECKNEW);
xstrncat(setup.voice.checknewpath , "/incoming" , VOICE_MAX_CHECKNEW);
xstrncpy(setup.voice.callerid , "*** Unknown ***" , VOICE_MAX_CALLERID);
xstrncpy(setup.voice.phone , "*** Unknown ***" , VOICE_MAX_PHONE );
xstrncpy(setup.voice.name , "*** Unknown ***" , VOICE_MAX_NAME );
xstrncpy(setup.voice.section , "STANDARD" , VOICE_MAX_SECTION );
setup.voice.rings = -1;
setup.voice.ringsonnew = -1;
setup.voice.doanswer = TRUE;
setup.voice.dorecord = TRUE;
setup.voice.dobeep = TRUE;
setup.voice.domessage = TRUE;
setup.voice.dotimeout = TRUE;
setup.voice.recordtime = TRUE;
}
/*************************************************************************/
/** voice_user_section(): Loads a user section. **/
/*************************************************************************/
void voice_user_section(char *id)
{
vboxrc_find_user_from_id(id);
vboxrc_find_user_section(setup.voice.section);
}
/*************************************************************************/
/** voice_set_compression(): Sets voice compression mode. **/
/*************************************************************************/
static int voice_set_compression(short c)
{
char command[64];
if ((c >= 2) && (c <= 6) && (c != 5))
{
printstring(command, "AT+VSM=%d+VLS=2", c);
log(L_DEBUG, "Setting voice compression \"%s\"...\n", compressions[c]);
return(modem_command(command, "OK|VCON"));
}
log(L_FATAL, "Unknown compression %d - can't set.\n", c);
returnerror();
}
/*************************************************************************/
/** voice_close_or_unlink(): Close and/or unlink a file. **/
/*************************************************************************/
static void voice_close_or_unlink(int fd, char *name)
{
if (fd != -1) close(fd);
if (name) unlink(name);
if (name) unlink(name);
}
/*************************************************************************
** voice_put_message(): Plays voice message. **
*************************************************************************/
int voice_put_message(char *message)
{
vaheader_t header;
long int compression;
char line_i[MODEM_BUFFER_LEN + 1];
char line_o[MODEM_BUFFER_LEN + MODEM_BUFFER_LEN + 1];
int fd;
int i;
int byte_i;
int byte_o;
int written;
int havedle;
time_t timebeg;
time_t timeend;
int bytetotal;
int secstotal;
log(L_INFO, "Playing \"%s\"...\n", message);
if ((fd = open(message, O_RDONLY)) == -1)
{
log(L_ERROR, "Can't open \"%s\".\n", message);
return(VOICE_ACTION_ERROR);
}
if (!header_get(fd, &header))
{
log(L_ERROR, "Can't read vbox audio header from message.\n");
voice_close_or_unlink(fd, NULL);
return(VOICE_ACTION_ERROR);
}
compression = ntohl(header.compression);
if (!voice_set_compression(compression))
{
log(L_ERROR, "Can't set voice audio compression or line mode.\n");
voice_close_or_unlink(fd, NULL);
return(VOICE_ACTION_ERROR);
}
if (modem_get_nocarrier_state())
{
voice_close_or_unlink(fd, NULL);
return(VOICE_ACTION_REMOTEHANGUP);
}
if (modem_command("AT+VTX", "CONNECT") == 0)
{
log(L_ERROR, "Can't start voice play mode.\n");
voice_close_or_unlink(fd, NULL);
return(VOICE_ACTION_ERROR);
}
voicestatus = VOICE_ACTION_OK;
sequencestatus = ST_NO_INPUT;
havedle = FALSE;
bytetotal = 0;
timebeg = time(NULL);
while (voicestatus == VOICE_ACTION_OK)
{
if ((byte_i = read(fd, line_i, MODEM_BUFFER_LEN)) <= 0)
{
log(L_DEBUG, "End of audio data (%s).\n", strerror(errno));
break;
}
byte_o = 0;
for (i = 0; i < byte_i; i++)
{
line_o[byte_o] = line_i[i];
if (line_o[byte_o++] == DLE) line_o[byte_o++] = DLE;
}
bytetotal += byte_o;
log(L_JUNK, "Play: <DATA %d incoming; %d outgoing>\n", byte_i, byte_o);
if (!modem_get_nocarrier_state())
{
written = 0;
errno = 0;
while (written != byte_o)
{
written += modem_raw_write(&line_o[written], (byte_o - written));
if (errno != 0) break;
}
if ((written != byte_o) || (errno != 0))
{
log(L_ERROR, "Could only write %d of %d bytes (%s).\n", written, byte_o, strerror(errno));
voicestatus = VOICE_ACTION_ERROR;
}
}
else voicestatus = VOICE_ACTION_REMOTEHANGUP;
while ((modem_check_input()) && (voicestatus == VOICE_ACTION_OK))
{
log(L_JUNK, "Have input...\n");
if (modem_raw_read(line_i, 1) == 1)
{
if (havedle)
{
switch (*line_i)
{
case ETX:
case 'b':
case 'c':
case 'e':
case 'd':
case 'q':
case 's':
log_line(L_DEBUG, "Found sequence \"<DLE>");
log_char(L_DEBUG, *line_i);
log_text(L_DEBUG, "\" (ignored)...\n");
break;
case DC4:
log(L_DEBUG, "Found sequence \"<DLE><DC4>\" (remote hangup)...\n");
voicestatus = VOICE_ACTION_REMOTEHANGUP;
break;
default:
voice_handle_touchtone_dle(*line_i);
break;
}
havedle = FALSE;
}
else
{
if (*line_i != DLE)
{
log_line(L_DEBUG, "Got unneeded character \"");
log_char(L_DEBUG, *line_i);
log_text(L_DEBUG, "\" (need a \"<DLE>\").\n");
}
else havedle = TRUE;
}
if (voicestatus == VOICE_ACTION_OK)
{
if ((index(touchtones, '#')) && (index(touchtones, '*')))
{
log(L_DEBUG, "Touchtone sequence \"%s\" found.\n", touchtones);
if (breaklist_search(touchtones))
{
log(L_INFO, "Sequence \"%s\" found in breaklist...\n", touchtones);
voicestatus = VOICE_ACTION_TOUCHTONES;
}
else
{
log(L_DEBUG, "Sequence \"%s\" not in breaklist (ignored)...\n", touchtones);
*touchtones = '\0';
}
}
}
}
else log(L_ERROR, "Can't read input from modem.\n");
}
if (ctrl_ishere(setup.spool, CTRL_NAME_SUSPEND)) {
log(L_INFO, "Control file \"%s\" exists - suspending call...\n", CTRL_NAME_SUSPEND);
if (!ctrl_remove(setup.spool, CTRL_NAME_SUSPEND)) {
log(L_WARN, "Can't remove control file \"%s\"!\n", CTRL_NAME_SUSPEND);
}
log(L_JUNK, "Sending \"<DLE><ETX>\"...\n");
printstring(line_o, "%c%c", DLE, ETX);
modem_raw_write(line_o, strlen(line_o));
if (modem_command("", "VCON")>0) {
#ifdef VBOX_SUSPEND_VALUE
printstring(line_o, "AT+S%d", VBOX_SUSPEND_VALUE);
#else
printstring(line_o, "AT+S");
#endif
if (modem_command(line_o, "OK") <= 0) {
log(L_WARN, "Can't suspend call\n");
} else {
log(L_INFO, "Call suspended\n");
voicestatus = VOICE_ACTION_REMOTEHANGUP;
}
}
}
}
timeend = time(NULL);
if (timeend >= timebeg)
{
secstotal = (timeend - timebeg);
bytetotal = get_message_ptime(compression, bytetotal);
log(L_JUNK, "Function play %d secs (kernel needs %d secs)...\n", secstotal, bytetotal);
if (secstotal < bytetotal)
{
log(L_JUNK, "Waiting %d secs to complete playing...\n", (bytetotal - secstotal));
xpause((bytetotal - secstotal) * 1000);
}
}
else log(L_WARN, "Oops - can't calculate time to wait!\n");
voice_close_or_unlink(fd, NULL);
if ((voicestatus == VOICE_ACTION_REMOTEHANGUP) || (modem_get_nocarrier_state()))
{
/*
* Remote hangup: We have got the sequence <DLE><DC4> in the
* modem stream...
*/
modem_command("", "NO CARRIER");
}
else
{
/*
* Local hangup: Send <DLE><ETX> to the modem and wait for the
* result VCON...
*/
log(L_JUNK, "Sending \"<DLE><ETX>\"...\n");
printstring(line_o, "%c%c", DLE, ETX);
modem_raw_write(line_o, strlen(line_o));
modem_command("", "VCON");
}
if (modem_get_nocarrier_state()) voicestatus = VOICE_ACTION_REMOTEHANGUP;
return(voicestatus);
}
/*************************************************************************/
/** voice_get_message(): Record a message. **/
/*************************************************************************/
/** If save is not true the recorded data are not written. **/
/*************************************************************************/
int voice_get_message(char *name, char *timestr, int save)
{
vaheader_t header;
char line_i[MODEM_BUFFER_LEN + 1];
char line_o[MODEM_BUFFER_LEN + 1];
int byte_i;
int byte_o;
int result;
int havedle;
int savetimeout;
int fd;
savetimeout = xstrtol(timestr, 90);
if (save)
log(L_INFO, "Recording \"%s\" (%d secs)...\n", name, savetimeout);
else
log(L_INFO, "Waiting %d secs for input...\n", savetimeout);
if (!voice_set_compression(setup.modem.compression))
{
log(L_ERROR, "Can't set voice audio compressen.\n");
return(VOICE_ACTION_ERROR);
}
if (save)
{
if ((fd = open(name, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IWGRP|S_IWOTH)) == -1)
{
log(L_ERROR, "Can't create \"%s\".\n", name);
return(VOICE_ACTION_ERROR);
}
truncate(name, 0);
voice_set_header(&header);
if (!header_put(fd, &header))
{
log(L_ERROR, "Can't write vbox audio header.\n");
voice_close_or_unlink(fd, name);
return(VOICE_ACTION_ERROR);
}
}
else fd = -1;
if (modem_get_nocarrier_state())
{
if (save) voice_close_or_unlink(fd, name);
return(VOICE_ACTION_LOCALHANGUP);
}
if (modem_command("AT+VRX", "CONNECT") == 0)
{
log(L_ERROR, "Can't start record mode.\n");
if (save) voice_close_or_unlink(fd, name);
return(VOICE_ACTION_ERROR);
}
sequencestatus = ST_NO_INPUT;
voicestatus = VOICE_ACTION_OK;
havedle = FALSE;
modem_set_timeout(savetimeout);
while (voicestatus == VOICE_ACTION_OK)
{
byte_o = 0;
byte_i = 0;
result = 0;
while ((byte_o < MODEM_BUFFER_LEN) && (voicestatus == VOICE_ACTION_OK))
{
if ((result = modem_raw_read(line_i, 1)) == 1)
{
byte_i++;
if (havedle)
{
switch (*line_i)
{
case DLE:
line_o[byte_o++] = DLE;
break;
case ETX:
log(L_DEBUG, "Found sequence \"<DLE><ETX>\" (remote hangup)...\n");
voicestatus = VOICE_ACTION_REMOTEHANGUP;
break;
default:
voice_handle_touchtone_dle(*line_i);
break;
}
havedle = FALSE;
}
else
{
if (*line_i != DLE)
{
line_o[byte_o++] = *line_i;
}
else havedle = TRUE;
}
}
else break;
}
if (byte_o > 0)
{
if (save)
{
log(L_JUNK, "Record: <DATA %d incoming; %d outgoing>\n", byte_i, byte_o);
write(fd, line_o, byte_o);
}
else log(L_JUNK, "Wait: <DATA %d incoming>\n", byte_i);
}
if ((result != 1) || (modem_get_timeout()))
{
if (!modem_get_timeout())
{
log(L_ERROR, "Can't read incoming data (%s).\n", strerror(errno));
voicestatus = VOICE_ACTION_ERROR;
}
else voicestatus = VOICE_ACTION_TIMEOUT;
}
if ((result == 1) && (ctrl_ishere(setup.spool, CTRL_NAME_SUSPEND))) {
log(L_INFO, "Control file \"%s\" exists - suspending call...\n", CTRL_NAME_SUSPEND);
if (!ctrl_remove(setup.spool, CTRL_NAME_SUSPEND)) {
log(L_WARN, "Can't remove control file \"%s\"!\n", CTRL_NAME_SUSPEND);
}
log(L_JUNK, "Sending \"<DLE><DC4>\"...\n");
printstring(line_o, "%c%c", DLE, DC4);
printstring(line_i, "%c%c", DLE, ETX);
modem_raw_write(line_o, strlen(line_o));
modem_wait_sequence(line_i);
if (modem_command("", "VCON")>0) {
#ifdef VBOX_SUSPEND_VALUE
printstring(line_o, "AT+S%d", VBOX_SUSPEND_VALUE);
#else
printstring(line_o, "AT+S");
#endif
if (modem_command(line_o, "OK") <= 0) {
log(L_WARN, "Can't suspend call\n");
} else {
log(L_INFO, "Call suspended\n");
voicestatus = VOICE_ACTION_REMOTEHANGUP;
}
}
}
if ((voicestatus == VOICE_ACTION_OK) || (voicestatus == VOICE_ACTION_TIMEOUT))
{
if ((index(touchtones, '#')) && (index(touchtones, '*')))
{
log(L_DEBUG, "Touchtone sequence \"%s\" found.\n", touchtones);
if (breaklist_search(touchtones))
{
log(L_INFO, "Sequence \"%s\" found in breaklist...\n", touchtones);
voicestatus = VOICE_ACTION_TOUCHTONES;
}
else
{
log(L_DEBUG, "Sequence \"%s\" not in breaklist (ignored)...\n", touchtones);
*touchtones = '\0';
}
}
}
}
modem_set_timeout(0);
if (save)
{
voice_close_or_unlink(fd, NULL);
permissions_set(name, setup.users.uid, setup.users.gid, S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH, setup.users.umask);
}
if ((voicestatus == VOICE_ACTION_REMOTEHANGUP) || (modem_get_nocarrier_state()))
{
/*
* Remote hangup: Modem should response with the sequence
* NO CARRIER.
*/
modem_command("", "NO CARRIER");
}
else
{
/*
* Local hangup: send <DLE><DC4> to the modem and read the
* response <DLE><ETX> and VCON.
*/
printstring(line_o, "%c%c", DLE, DC4);
printstring(line_i, "%c%c", DLE, ETX);
log(L_JUNK, "Sending \"<DLE><DC4>\"...\n");
modem_raw_write(line_o, strlen(line_o));
modem_get_sequence(line_i);
modem_command("", "VCON");
}
if (modem_get_nocarrier_state()) voicestatus = VOICE_ACTION_REMOTEHANGUP;
return(voicestatus);
}
/*************************************************************************/
/** voice_set_header(): Fills the vbox audio header. **/
/*************************************************************************/
static void voice_set_header(vaheader_t *header)
{
memset(header, 0, sizeof(vaheader_t));
xstrncpy(header->magic , VAH_MAGIC , VAH_MAX_MAGIC );
xstrncpy(header->name , setup.voice.name , VAH_MAX_NAME );
xstrncpy(header->callerid, setup.voice.callerid , VAH_MAX_CALLERID);
xstrncpy(header->phone , setup.voice.phone , VAH_MAX_PHONE );
xstrncpy(header->location, "*** Unknown ***" , VAH_MAX_LOCATION);
header->time = htonl(time(NULL));
header->compression = htonl(setup.modem.compression);
}
/*************************************************************************/
/** voice_handle_touchtone_dle(): Checks a byte for a touchtone DLE **/
/** sequence. **/
/*************************************************************************/
static void voice_handle_touchtone_dle(int byte)
{
switch (byte)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '*':
case '#':
case 'A':
case 'B':
case 'C':
case 'D':
voice_handle_touchtone(byte);
break;
default:
log_line(L_ERROR, "Illeagal \"<DLE>\" shielded code \"");
log_char(L_ERROR, byte);
log_text(L_ERROR, "\" (ignored)...\n");
break;
}
}
/**************************************************************************/
/** voice_handle_touchtone(): **/
/**************************************************************************/
static void voice_handle_touchtone(char Tone)
{
char Temp[2];
log(L_DEBUG, "Found touchtone \"%c\"...\n", Tone);
if (Tone == '*')
{
printstring(touchtones, "*");
return;
}
if (*touchtones != '*')
{
if ((Tone != '#') && (Tone != '*'))
{
printstring(touchtones, "*%c#", Tone);
}
else
{
if (Tone == '#') printstring(touchtones, "*#");
}
return;
}
if (index(touchtones, '#'))
{
if ((Tone != '#') && (Tone != '*'))
{
printstring(touchtones, "*%c#", Tone);
}
else
{
if (Tone == '#') printstring(touchtones, "*#");
}
return;
}
if (strlen(touchtones) < TOUCHTONE_BUFFER_LEN)
{
Temp[0] = Tone;
Temp[1] = 0;
strcat(touchtones, Temp);
}
}