isdn4k-utils/vbox3/vboxgetty/voice.c

911 lines
22 KiB
C
Raw Blame History

/*
** $Id$
**
** Copyright 1996-1998 Michael 'Ghandi' Herold <michael@abadonna.mayn.de>
*/
#ifdef HAVE_CONFIG_H
# include "../config.h"
#endif
#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 <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "voice.h"
#include "log.h"
#include "modem.h"
#include "stringutils.h"
#include "vboxrc.h"
#include "vboxgetty.h"
#include "tclscript.h"
#include "control.h"
#include "audio.h"
#include "breaklist.h"
/** Variables ************************************************************/
static struct vboxuser *voicevboxuser;
static struct vboxcall *voicevboxcall;
static int voicedesc = -1;
static int voicevbox = 0;
static int voicestat = VBOXVOICE_STAT_OK;
static int audiodesc = -1;
static unsigned char voicename_ulaw[PATH_MAX + 1];
static unsigned char voicename_vbox[PATH_MAX + 1];
static unsigned char voicename_call[PATH_MAX + 1];
unsigned char voice_touchtone_sequence[VBOXVOICE_SEQUENCE + 1];
/** Prototypes ***********************************************************/
static void voice_stop_vtxrtx(void);
static void voice_create_vboxcall(void);
static void voice_remove_vboxcall(void);
static void voice_mkdir(unsigned char *);
static int voice_check_touchtone(int);
/*************************************************************************
** voice_init(): Beantwortet den Anruf und startet das Tcl-Skript. **
*************************************************************************
** => vboxuser Zeiger auf die vboxuser-Struktur **
** => vboxcall Zeiger auf die vboxcall-Struktur **
*************************************************************************/
int voice_init(struct vboxuser *vboxuser, struct vboxcall *vboxcall)
{
unsigned char tempconnect[12];
unsigned char msgsavetime[32];
time_t currenttime;
char *stop;
int rc;
struct vbox_tcl_variable vars[] =
{
{ "vbxv_savetime" , msgsavetime },
{ "vbxv_callerid" , vboxuser->incomingid },
{ "vbxv_callername" , vboxcall->name },
{ "vbxv_localphone" , vboxuser->localphone },
{ "vbxv_username" , vboxuser->name },
{ "vbxv_userhome" , vboxuser->home },
{ "vbxv_usedscript" , vboxcall->script },
{ "vbxv_saveulaw" , voicename_ulaw },
{ "vbxv_savevbox" , voicename_vbox },
{ NULL , NULL }
};
printstring(msgsavetime, "%d", vboxcall->savetime);
/* Die beiden <20>bergebenen Strukturen global machen, damit alle */
/* Voice Funktionen sie benutzen k<>nnen. */
voicevboxuser = vboxuser;
voicevboxcall = vboxcall;
/* Die Namen der beiden Dateien (*.ulaw und *.vbox) erzeugen. */
/* Eine Datei enth<74>lt die Audiodaten, die andere die Inform- */
/* ationen <20>ber den Benutzer. */
currenttime = time(NULL);
printstring(voicename_ulaw, "%s/new/%11.11lu-%8.8lu.ulaw", vboxuser->home, (unsigned long)currenttime, (unsigned long)getpid());
printstring(voicename_vbox, "%s/new/%11.11lu-%8.8lu.vbox", vboxuser->home, (unsigned long)currenttime, (unsigned long)getpid());
/* Den Namen der Call Datei erzeugen, in dem sp<73>ter Daten zum */
/* aktuellen Anruf gesichert werden. */
printstring(voicename_call, "%s/vboxcall-%s", vboxuser->home, savettydname);
/* Userspool Verzeichnisstruktur erzeugen. Wenn eines der */
/* Verzeichnisse bereits existiert, bleibt es unver<65>ndert. */
voice_mkdir(NULL );
voice_mkdir("new");
voice_mkdir("msg");
voice_mkdir("tcl");
/* Variablen f<>r Tcl erzeugen, Kompression setzen, Full Duplex */
/* Audio Modus starten und das Skript aufrufen. */
log_line(LOG_A, "Answering call...\n");
voicevbox = 0;
voicedesc = -1;
audiodesc = -1;
*voice_touchtone_sequence = '\0';
if (scr_init_variables(vars) == 0)
{
if (modem_command(&vboxmodem, "ATA", "VCON") > 0)
{
log_line(LOG_D, "Setting voice compression to \"ulaw\"...\n");
if (modem_command(&vboxmodem, "AT+VSM=6+VLS=2", "OK") > 0)
{
log_line(LOG_D, "Starting full duplex audio mode...\n");
if (modem_command(&vboxmodem, "AT+VTX+VRX", "CONNECT") > 0)
{
voice_hear(0);
voice_save(0);
/* Bevor das Skript gestartet wird, werden die */
/* ersten 11 Byte vom Modem eingelesen. Damit */
/* wird ein Fehler in i4l umgangen, der die */
/* Connectmessage bei Full Duplex 2x in den Mo- */
/* dembuffer schreibt. */
modem_set_timeout(2);
vboxmodem_raw_read(&vboxmodem, tempconnect, 11);
modem_set_timeout(0);
voice_create_vboxcall();
{
int r, pid, wstat;
if ((pid =fork()) == -1) {
log_line(LOG_E, "fork failed.\n");
exit(1);
}
if (pid == 0) {
if (seteuid(0) == -1) {
log_line(LOG_E, "seteuid failed.\n");
exit(1);
}
if (setgid(vboxuser->gid) == -1) {
log_line(LOG_E, "setgid failed.\n");
exit(1);
}
if (setuid(vboxuser->uid) == -1) {
log_line(LOG_E, "setuid failed.\n");
exit(1);
}
rc = scr_execute(vboxcall->script, vboxuser);
exit(rc);
}
do
r =waitpid(pid, &wstat, 0);
while ((r == -1) && (errno == EINTR));
if (WIFEXITED(wstat) && (WEXITSTATUS(wstat) == 0))
rc =0;
else
rc =-1;
}
voice_remove_vboxcall();
voice_hear(0);
voice_save(0);
voice_stop_vtxrtx();
if ((stop = ctrl_exists(vboxuser->home, "suspend", savettydname)))
{
ctrl_remove(vboxuser->home, "suspend", savettydname);
if (!vboxmodem.nocarrier)
{
log_line(LOG_A, "Suspending call to number %s...\n", stop);
if (modem_command(&vboxmodem, "AT+S1", "OK") > 0) {
log_line(LOG_D, "Call suspended to number %s.\n", stop);
} else {
log_line(LOG_E, "Can't suspend call to number %s.\n", stop);
}
}
}
return(rc);
}
}
}
}
log_line(LOG_E, "Unable to answer call!\n");
return(-1);
}
/*************************************************************************
** voice_wait(): Liest eine angegebene Zeit lang Audiodaten vom Modem. **
*************************************************************************
** => timeout Timeout in Sekunden **
*************************************************************************
** <= 0 wenn der Timeout eingetreten ist. **
** 1 wenn eine g<>ltige Touchtonesequenz gefunden wurde. **
** 2 wenn der Anruf suspended werden soll. **
** -1 bei einem Fehler oder Remote hangup **
*************************************************************************/
int voice_wait(int timeout)
{
unsigned char modem_line_i[1];
unsigned char modem_line_o[VBOXVOICE_BUFSIZE + 1];
int total_byte_i;
int total_byte_o;
int modem_byte_i;
int modem_byte_o;
int last_was_dle;
int result;
char *stop;
total_byte_i = 0;
total_byte_o = 0;
last_was_dle = 0;
voicestat = VBOXVOICE_STAT_OK;
log(LOG_D, "Reading voice datas (%ds timeout)...\n", timeout);
modem_set_timeout(timeout);
while (voicestat == VBOXVOICE_STAT_OK)
{
modem_byte_i = 0;
modem_byte_o = 0;
result = 0;
while ((modem_byte_o < (VBOXVOICE_BUFSIZE - 2)) && (voicestat == VBOXVOICE_STAT_OK))
{
if ((result = vboxmodem_raw_read(&vboxmodem, modem_line_i, 1)) == 1)
{
modem_byte_i++;
if (last_was_dle)
{
switch (*modem_line_i)
{
case DLE:
modem_line_o[modem_byte_o++] = DLE;
break;
case ETX:
voicestat |= VBOXVOICE_STAT_HANGUP;
break;
default:
if (voice_check_touchtone(*modem_line_i) == 0) voicestat |= VBOXVOICE_STAT_TOUCHTONE;
break;
}
last_was_dle = 0;
}
else
{
if (*modem_line_i == DLE) last_was_dle = 1;
modem_line_o[modem_byte_o++] = *modem_line_i;
}
}
else break;
}
total_byte_o += modem_byte_o;
total_byte_i += modem_byte_i;
if (modem_byte_o > 0)
{
if (voicedesc != -1) write(voicedesc, modem_line_o, modem_byte_o);
if (audiodesc != -1) write(audiodesc, modem_line_o, modem_byte_o);
/* Einmal in der Sekunde die Logmessage ausgeben und die */
/* Controls checken. */
if ((total_byte_o >= VBOXVOICE_SAMPLERATE) || (voicestat != VBOXVOICE_STAT_OK))
{
log_line(LOG_D, "Wait: incoming %04d; outgoing %04d...\n", total_byte_i, total_byte_o);
total_byte_i = 0;
total_byte_o = 0;
if ((stop = ctrl_exists(voicevboxuser->home, "suspend", savettydname)))
{
log(LOG_D, "Control \"vboxctrl-suspend-%s\" detected: %s.\n", savettydname, stop);
voicestat |= VBOXVOICE_STAT_SUSPEND;
}
if ((stop = ctrl_exists(voicevboxuser->home, "audio", savettydname)))
{
log(LOG_D, "Control \"vboxctrl-audio-%s\" detected: %s.\n", savettydname, stop);
if (strcasecmp(stop, "stop") == 0) voice_hear(0);
if (strcasecmp(stop, "start") == 0) voice_hear(1);
}
}
}
if ((result != 1) || (modem_get_timeout()))
{
if (!modem_get_timeout())
{
log_line(LOG_W, "Can't read voice data (%s).\n", strerror(errno));
voicestat |= VBOXVOICE_STAT_TIMEOUT;
}
else voicestat |= VBOXVOICE_STAT_TIMEOUT;
}
}
modem_set_timeout(0);
result = 0;
if (voicestat & VBOXVOICE_STAT_TOUCHTONE)
{
log_line(LOG_D, "Full touchtone sequence found!\n");
result = 1;
}
if (voicestat & VBOXVOICE_STAT_SUSPEND) result = 2;
if ((voicestat & VBOXVOICE_STAT_HANGUP) || (vboxmodem.nocarrier))
{
log_line(LOG_D, "*** Remote hangup ***\n");
voice_save(0);
voice_hear(0);
modem_command(&vboxmodem, "", "NO CARRIER");
result = -1;
}
return(result);
}
/*************************************************************************
** voice_play(): Spielt eine Nachricht ab und liest dabei Voicedaten **
** vom Modem. **
*************************************************************************
** name Name der Nachricht die gespielt werden soll. **
*************************************************************************
** <= 0 wenn der Timeout eingetreten ist. **
** 1 wenn eine g<>ltige Touchtonesequenz gefunden wurde. **
** 2 wenn der Anruf suspended werden soll. **
** -1 bei einem Fehler oder Remote hangup **
*************************************************************************/
int voice_play(unsigned char *name)
{
unsigned char modem_line_i[1];
unsigned char modem_line_o[VBOXVOICE_BUFSIZE + 1];
int total_byte_i;
int total_byte_o;
int modem_byte_i;
int modem_byte_o;
int last_was_dle;
int result;
int desc;
char *stop;
int i;
if ((!name) || (!*name))
{
log(LOG_W, "No message to play selected (ignored).\n");
return(0);
}
if ((stop = rindex(name, '/'))) name = ++stop;
log(LOG_D, "Playing \"%s\"...\n", name);
printstring(temppathname, "%s/msg/%s", voicevboxuser->home, name);
errno = 0;
desc = open(temppathname, O_RDONLY);
if (desc == -1)
{
printstring(temppathname, "%s/msg/%s", PKGDATADIR, name);
errno = 0;
desc = open(temppathname, O_RDONLY);
}
if (desc == -1)
{
log(LOG_W, "Can't open \"%s\" (%s).\n", name, strerror(errno));
return(0);
}
last_was_dle = 0;
total_byte_i = 0;
total_byte_o = 0;
voicestat = VBOXVOICE_STAT_OK;
while (voicestat == VBOXVOICE_STAT_OK)
{
modem_byte_i = 0;
modem_byte_o = 0;
result = 0;
modem_set_timeout(5);
while ((modem_byte_o < (VBOXVOICE_BUFSIZE - 2)) && (voicestat == VBOXVOICE_STAT_OK))
{
if ((i = read(desc, modem_line_i, 1)) != 1)
{
if (i != 0) log(LOG_W, "Can't read \"%s\" [%d] (%s).\n", name, i, strerror(errno));
voicestat |= VBOXVOICE_STAT_DONE;
}
else vboxmodem_raw_write(&vboxmodem, modem_line_i, 1);
if ((result = vboxmodem_raw_read(&vboxmodem, modem_line_i, 1)) == 1)
{
modem_byte_i++;
if (last_was_dle)
{
switch (*modem_line_i)
{
case DLE:
modem_line_o[modem_byte_o++] = DLE;
break;
case ETX:
voicestat |= VBOXVOICE_STAT_HANGUP;
break;
default:
if (voice_check_touchtone(*modem_line_i) == 0) voicestat |= VBOXVOICE_STAT_TOUCHTONE;
break;
}
last_was_dle = 0;
}
else
{
if (*modem_line_i == DLE) last_was_dle = 1;
modem_line_o[modem_byte_o++] = *modem_line_i;
}
}
else break;
}
modem_set_timeout(0);
total_byte_o += modem_byte_o;
total_byte_i += modem_byte_i;
if (modem_byte_o > 0)
{
if (voicedesc != -1) write(voicedesc, modem_line_o, modem_byte_o);
if (audiodesc != -1) write(audiodesc, modem_line_o, modem_byte_o);
/* Einmal in der Sekunde die Logmessage ausgeben und die */
/* Controls checken. */
if ((total_byte_o >= VBOXVOICE_SAMPLERATE) || (voicestat != VBOXVOICE_STAT_OK))
{
log_line(LOG_D, "Play: incoming %04d; outgoing %04d...\n", total_byte_i, total_byte_o);
total_byte_i = 0;
total_byte_o = 0;
if ((stop = ctrl_exists(voicevboxuser->home, "suspend", savettydname)))
{
log(LOG_D, "Control \"vboxctrl-suspend-%s\" detected: %s.\n", savettydname, stop);
voicestat |= VBOXVOICE_STAT_SUSPEND;
}
if ((stop = ctrl_exists(voicevboxuser->home, "audio", savettydname)))
{
log(LOG_D, "Control \"vboxctrl-audio-%s\" detected: %s.\n", savettydname, stop);
if (strcasecmp(stop, "stop") == 0) voice_hear(0);
if (strcasecmp(stop, "start") == 0) voice_hear(1);
}
}
}
if ((result != 1) || (modem_get_timeout()))
{
if (!modem_get_timeout())
{
log_line(LOG_W, "Can't read voice data (%s).\n", strerror(errno));
voicestat |= VBOXVOICE_STAT_TIMEOUT;
}
else voicestat |= VBOXVOICE_STAT_TIMEOUT;
}
}
modem_set_timeout(0);
if (desc != -1) close(desc);
result = 0;
if (voicestat & VBOXVOICE_STAT_TOUCHTONE)
{
log_line(LOG_D, "Full touchtone sequence found!\n");
result = 1;
}
if (voicestat & VBOXVOICE_STAT_SUSPEND) result = 2;
if ((voicestat & VBOXVOICE_STAT_HANGUP) || (vboxmodem.nocarrier))
{
log_line(LOG_D, "*** Remote hangup ***\n");
voice_save(0);
voice_hear(0);
modem_command(&vboxmodem, "", "NO CARRIER");
result = -1;
}
return(result);
}
/*************************************************************************
** voice_save(): Schaltet das mitspeichern der eingehenden Voicedaten **
** ein oder aus. **
*************************************************************************
** => save > 0 um das mitspeichern einzuschalten; alle anderen **
** Werte schaltes es aus. **
*************************************************************************/
int voice_save(int save)
{
FILE *vbox;
if (save > 0)
{
if (voicedesc == -1)
{
log(LOG_D, "Starting voice recording...\n");
log(LOG_D, "Opening \"%s\"...\n", voicename_ulaw);
errno = 0;
if ((voicedesc = open(voicename_ulaw, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) == -1)
{
log(LOG_E, "Can't open/append \"%s\" (%s).\n", voicename_ulaw, strerror(errno));
return(-1);
}
}
if (!voicevbox)
{
/* Wenn die *.vbox Datei noch nicht existiert wird sie */
/* jetzt erzeugt. Die Datei enth<74>lt die Informationen */
/* wer wann wie die Nachricht gesprochen hat. */
log(LOG_D, "Creating \"%s\"...\n", voicename_vbox);
errno = 0;
if ((vbox = fopen(voicename_vbox, "w")))
{
fprintf(vbox, "Name: %s\n" , voicevboxcall->name );
fprintf(vbox, "ID : %s\n" , voicevboxuser->incomingid);
fprintf(vbox, "Time: %ld\n", time(NULL) );
fclose(vbox);
voicevbox = 1;
}
else log(LOG_E, "Can't create \"%s\" (%s).\n", voicename_vbox, strerror(errno));
}
}
else
{
if (voicedesc != -1)
{
log(LOG_D, "Stopping voice recording...\n");
log(LOG_D, "Closing \"%s\"...\n", voicename_ulaw);
close(voicedesc);
}
voicedesc = -1;
}
return(0);
}
/*************************************************************************
** voice_hear(): Schaltet das mith<74>ren der Voicedaten <20>ber /dev/audio **
** ein oder aus. **
*************************************************************************
** => mode > 0 um das Mith<74>ren einzuschalten; alle anderen Werte **
** schalten das Mith<74>ren aus. **
** **
** Die Funktion gibt 0 zur<75>ck wenn die Aktion ausgef<65>hrt werden konnte **
** oder -1 wenn nicht. **
*************************************************************************/
int voice_hear(int mode)
{
if (mode > 0)
{
if (audiodesc == -1)
{
log(LOG_D, "Starting audio playback...\n");
if ((audiodesc = audio_open_dev("/dev/audio")) == -1) return(-1);
}
}
else
{
if (audiodesc != -1)
{
log(LOG_D, "Stopping audio playback...\n");
audio_close_dev(audiodesc);
}
audiodesc = -1;
}
return(0);
}
/*************************************************************************
** FIXME
*************************************************************************/
static void voice_stop_vtxrtx(void)
{
unsigned char line[4];
int have;
if (!vboxmodem.nocarrier)
{
/* DLE/DC4 an den Modememluator schicken um den Record-Modus */
/* zu stoppen. Der Emulator sollte mit DLE/ETX antworten. */
log_line(LOG_D, "Sending \"<DLE><DC4>\" to stop record mode...\n");
printstring(line, "%c%c", DLE, DC4);
vboxmodem_raw_write(&vboxmodem, line, 2);
have = 0;
modem_set_timeout(modemsetup.commandtimeout);
while (vboxmodem_raw_read(&vboxmodem, line, 1) == 1)
{
log_char(LOG_D, *line);
if (*line != DLE)
{
if ((*line == ETX) && (have))
{
have++;
break;
}
else have = 0;
}
else have = 1;
}
modem_set_timeout(0);
if (have == 2) log_line(LOG_D, "Found <DLE><ETX>!\n");
log_line(LOG_D, "Sending \"<DLE><ETX>\" to stop playback mode...\n");
printstring(line, "%c%c", DLE, ETX);
vboxmodem_raw_write(&vboxmodem, line, 2);
have = 0;
modem_set_timeout(modemsetup.commandtimeout);
while (vboxmodem_raw_read(&vboxmodem, line, 1) == 1)
{
log_char(LOG_D, *line);
if (*line != DLE)
{
if ((*line == DC4) && (have))
{
have++;
break;
}
else have = 0;
}
else have = 1;
}
modem_set_timeout(0);
if (have == 2) log_line(LOG_D, "Found <DLE><DC4>!\n");
}
}
/*************************************************************************
** voice_create_vboxcall(): Erzeugt eine Call Datei mit Informationen **
** <09>ber den aktuellen Anruf. **
*************************************************************************/
static void voice_create_vboxcall(void)
{
FILE *call;
log(LOG_D, "Creating \"%s\"...\n", voicename_call);
if ((call = fopen(voicename_call, "w")))
{
/* CallerID, Name, tty-Device und die aktuelle PID in der */
/* Datei speichern. */
fprintf(call, "%s:%s:%s:%d\n", voicevboxuser->incomingid, voicevboxuser->name, savettydname, getpid());
fclose(call);
}
else log(LOG_E, "Can't create \"%s\" (%s)!\n", voicename_call, strerror(errno));
}
/*************************************************************************
** voice_remove_vboxcall(): L<>scht die Call Datei. **
*************************************************************************/
static void voice_remove_vboxcall(void)
{
log(LOG_D, "Removing \"%s\"...\n", voicename_call);
if (remove(voicename_call) != 0)
{
if (errno != ENOENT) log(LOG_E, "Can't remove \"%s\" (%s)!\n", voicename_call, strerror(errno));
}
}
/*************************************************************************
** voice_mkdir(): Erzeugt die Verzeichnisse im Userspool. **
*************************************************************************/
static void voice_mkdir(unsigned char *name)
{
if (name)
printstring(temppathname, "%s/%s", voicevboxuser->home, name);
else
printstring(temppathname, "%s", voicevboxuser->home);
log(LOG_D, "Creating directory \"%s\"...\n", temppathname);
errno = 0;
if (mkdir(temppathname, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH) == -1)
{
if (errno != EEXIST)
{
log(LOG_E, "Can't create \"%s\" (%s).\n", temppathname, strerror(errno));
}
}
}
/*************************************************************************
** voice_check_touchtone(): Pr<50>ft ob es sich bei einem Zeichen um **
** einen Touchtone handelt. **
*************************************************************************
** => c Zeichen **
** **
** R<>ckgabe ist 0 wenn es ein Touchtone war oder -1 wenn nicht. **
*************************************************************************/
static int voice_check_touchtone(int c)
{
unsigned char tone[2];
unsigned char *stop;
int i;
switch (c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'A':
case 'B':
case 'C':
case 'D':
case '#':
case '*':
{
if (c != '*')
{
tone[0] = c;
tone[1] = 0;
if (*voice_touchtone_sequence == '*')
{
/* Wenn das erste Zeichen in der Sequenz ein '*' */
/* ist (Sequenzstart) wird der neue Touchtone am */
/* Ende angeh<65>ngt - es sei denn die Sequenz ent- */
/* h<>lt ein '#' (Sequenzende). */
if (!(stop = rindex(voice_touchtone_sequence, '#')))
{
if (strlen(voice_touchtone_sequence) >= VBOXVOICE_SEQUENCE)
{
/* Die Sequenz ist bereits voll; der neue */
/* Touchtone wird nicht angeh<65>ngt! */
log(LOG_E, "Internal touchtone sequence is full (touchtone ignored).\n");
}
else strcat(voice_touchtone_sequence, tone);
}
else strcpy(voice_touchtone_sequence, tone);
}
else
{
/* Erstes Zeichen in der Sequenz ist kein '*', der */
/* Touchtone ersetzt die alte Sequenz. */
strcpy(voice_touchtone_sequence, tone);
}
}
else strcpy(voice_touchtone_sequence, "*");
log(LOG_I, "Touchtone \"%c\" entered (%s).\n", c, voice_touchtone_sequence);
/* Pr<50>fen ob die eingegebene Sequenz in der Breakliste */
/* vorkommt. */
for (i = 0; i < VBOXBREAK_MAX_ENTRIES; i++)
{
if (breaklist[i])
{
if (strcmp(breaklist[i], voice_touchtone_sequence) == 0) return(0);
}
}
}
break;
case 'q': /* Quiet */
case 's': /* Silence */
case 'c': /* Fax calling tone */
case 'b': /* Busy tone */
break;
default:
log_line(LOG_W, "Illegal \"<DLE>\" shielded code \"");
log_char(LOG_W, c);
log_text(LOG_W, "\" (ignored)...\n");
break;
}
return(-1);
}