This is am.c in view mode; [Download] [Up]
/*********************************************************************/
/* */
/* Programmer: */
/* Olaf Mueller <olaf@orest.escape.de> */
/* */
/* Purpose: */
/* Answering Machine */
/* main module */
/* */
/* History: */
/* 29-08-96 Initial Release Olaf Mueller */
/* */
/* Notes: */
/* */
/*********************************************************************/
#include <libc.h>
#include <sys/dir.h>
#define NoTESTMODE
#include "defaults.h"
#include "const.h"
#include "misc.h"
#include "device.h"
#include "iodev.h"
#include "timer.h"
#include "zyxel.h"
/* ZyXEL MODEM opinions */
#define RZYX_SILENCE 11
#define RZYX_QUIET 12
#define RZYX_ENDOFTEXT 13
#define RZYX_FAX 14
#define RZYX_BUSY 15
#define RZYX_HANGUP 16 /* <DLE>h is not documented. <DLE>d means dialtone detected */
#define RZYX_DATACALL 17 /* data calling tone detected (>=6.11a) */
/* other return codes */
#define ROTH_COMPLETED 0
#define ROTH_STAR 1
#define ROTH_PWDENTERED 2
#define ROTH_TIMEOUT 3
#define ROTH_EXPIRATION 4
#ifndef TESTMODE
#define LOGFILE "/usr/spool/uucp/LOGFILE"
#define DEBUGFILE "/usr/spool/uucp/DEBUG"
#define ERRLOGFILE "/usr/spool/uucp/ERRLOG"
#define SYSLOGFILE "/usr/spool/uucp/SYSLOG"
#else
#define LOGFILE "/dev/console"
#define DEBUGFILE "/dev/console"
#define ERRLOGFILE "/dev/console"
#define SYSLOGFILE "/dev/console"
#endif
/* Voice compression mode for receiving voice */
#define RECVCM ZYX_AD3
//#define RECVCM ZYX_AD2
/* Silent detection for voice */
#define SILENTSENSITIVITY 15 /* 0 - 31 */
#define SILENTPERIOD 60 /* 0 - 255 (.1 sec) */
#define TEMPVOICE "/tmp/tmpvoice.zsnd"
/* System voices */
#define ZSND ".zsnd"
#define VOC_WELCOME "Welcome"
#define VOC_NOMSGFORFRIEND "NoMessageForFriend"
#define VOC_ROOTMENU "RootMenu"
#define VOC_NOMSGTOPLAY "NoMessageToPlay"
#define VOC_NOMSGTOARCHIVE "NoMessageToArchive"
#define VOC_LASTMSGARCHIVED "LastMessageArchived"
#define VOC_BADINPUT "BadInput"
#define VOC_TIMELIMIT "TimeLimit"
#define FAXSIGNAL "/tmp/faxsignal.am"
/*****
***** configuration of am
*****/
static int cfg_rings ;
static int cfg_speaker ;
static int cfg_volume ;
static char cfg_localfaxid [200] ;
static char cfg_port ;
static char cfg_voicesystemdir [MAXPATHLEN+1] ;
static int cfg_voicerootnumber ;
static char cfg_voicefrienddir [MAXPATHLEN+1] ;
static int cfg_voiceexpiration ;
static int cfg_beepexpiration ;
static char cfg_voicespooldir [MAXPATHLEN+1] ;
static char cfg_faxspooldir [MAXPATHLEN+1] ;
static char cfg_mailaddresses [MAXPATHLEN+1] ;
static int cfg_voicemailmode ;
static int cfg_faxmailmode ;
static char cfg_execvoicemail [MAXPATHLEN+1] ;
static char cfg_execfaxmail [MAXPATHLEN+1] ;
static void setDefaults (void)
{
cfg_port = PORT ;
cfg_rings = RINGS ;
cfg_speaker = SPEAKER ;
cfg_volume = VOLUME ;
strcpy (cfg_localfaxid,LOCALFAXID) ;
strcpy (cfg_voicesystemdir,VOICESYSTEMDIR) ;
cfg_voicerootnumber = VOICEROOTNUMBER ;
strcpy (cfg_voicefrienddir,VOICEFRIENDDIR) ;
cfg_voiceexpiration = VOICEEXPIRATION ;
cfg_beepexpiration = BEEPEXPIRATION ;
strcpy (cfg_voicespooldir,VOICESPOOLDIR) ;
strcpy (cfg_faxspooldir,FAXSPOOLDIR) ;
strcpy (cfg_mailaddresses,MAILADDRESSES) ;
cfg_voicemailmode = VOICEMAILMODE ;
cfg_faxmailmode = FAXMAILMODE ;
strcpy (cfg_execvoicemail,EXECVOICEMAIL) ;
strcpy (cfg_execfaxmail,EXECFAXMAIL) ;
}
static char *stbeg (char *string)
{
while (*string != '\0' && (*string == ' ' || *string == '\t'))
string++ ;
return string ;
}
static int configurate (char *filename)
{
FILE *fp ;
char buffer [1000] , *cp ;
int ivalue ;
if ((fp = fopen(filename,"r")) != NULL)
{
while (fgets(buffer,sizeof(buffer),fp) != NULL)
if (buffer[0] != '#' && (cp = strchr(buffer,'=')) != NULL)
if (!memcmp("PORT",buffer,(int)(cp-buffer)))
cfg_port = *stbeg(cp+1) ;
else if (sscanf(buffer,"RINGS=%d",&ivalue) == 1)
cfg_rings = ivalue ;
else if (sscanf(buffer,"SPEAKER=%d",&ivalue) == 1)
cfg_speaker = ivalue ;
else if (sscanf(buffer,"VOLUME=%d",&ivalue) == 1)
cfg_volume = ivalue ;
else if (!memcmp("LOCALFAXID",buffer,(int)(cp-buffer)))
{
strcpy (cfg_localfaxid,stbeg(cp+1)) ;
*(cfg_localfaxid+strlen(cfg_localfaxid)-1) = '\0' ;
}
else if (!memcmp("VOICESYSTEMDIR",buffer,(int)(cp-buffer)))
{
strcpy (cfg_voicesystemdir,stbeg(cp+1)) ;
*(cfg_voicesystemdir+strlen(cfg_voicesystemdir)-1) = '\0' ;
}
else if (sscanf(buffer,"VOICEROOTNUMBER=%d",&ivalue) == 1)
cfg_voicerootnumber = ivalue ;
else if (!memcmp("VOICEFRIENDDIR",buffer,(int)(cp-buffer)))
{
strcpy (cfg_voicefrienddir,stbeg(cp+1)) ;
*(cfg_voicefrienddir+strlen(cfg_voicefrienddir)-1) = '\0' ;
}
else if (sscanf(buffer,"VOICEEXPIRATION=%d",&ivalue) == 1)
cfg_voiceexpiration = ivalue ;
else if (sscanf(buffer,"BEEPEXPIRATION=%d",&ivalue) == 1)
cfg_beepexpiration = ivalue ;
else if (!memcmp("VOICESPOOLDIR",buffer,(int)(cp-buffer)))
{
strcpy (cfg_voicespooldir,stbeg(cp+1)) ;
*(cfg_voicespooldir+strlen(cfg_voicespooldir)-1) = '\0' ;
}
else if (!memcmp("FAXSPOOLDIR",buffer,(int)(cp-buffer)))
{
strcpy (cfg_faxspooldir,stbeg(cp+1)) ;
*(cfg_faxspooldir+strlen(cfg_faxspooldir)-1) = '\0' ;
}
else if (!memcmp("MAILADDRESSES",buffer,(int)(cp-buffer)))
{
strcpy (cfg_mailaddresses,stbeg(cp+1)) ;
*(cfg_mailaddresses+strlen(cfg_mailaddresses)-1) = '\0' ;
}
else if (sscanf(buffer,"VOICEMAILMODE=%d",&ivalue) == 1)
cfg_voicemailmode = ivalue ;
else if (sscanf(buffer,"FAXMAILMODE=%d",&ivalue) == 1)
cfg_faxmailmode = ivalue ;
else if (!memcmp("EXECVOICEMAIL",buffer,(int)(cp-buffer)))
{
strcpy (cfg_execvoicemail,stbeg(cp+1)) ;
*(cfg_execvoicemail+strlen(cfg_execvoicemail)-1) = '\0' ;
}
else if (!memcmp("EXECFAXMAIL",buffer,(int)(cp-buffer)))
{
strcpy (cfg_execfaxmail,stbeg(cp+1)) ;
*(cfg_execfaxmail+strlen(cfg_execfaxmail)-1) = '\0' ;
}
fclose (fp) ;
}
return fp ? 0 : -1 ;
}
/*****
***** signal handler
*****/
static void signalhandler (int sig)
{
switch (sig)
{
case SIGALRM:
break ;
default:
log (ERRLOGFILE,"ERROR: Signal %d caught (should never happen)\n",sig) ;
break ;
}
}
/*
There are 3 (break) modes reading and writing voice data
mode 0: no MFV Tone breaks
mode 1: MFV Tone '*' or a digit breaks
mode 2: MFV Tone '*' breaks and returns the 'pwdnr'
In case of init, 'pwdnr' should be set to -1 before.
*/
#define VBLEN 72
#define IOTIMEOUT 1000L
static int writeVoice (int line,char *block,int blocksize,int *pwdnr,int breakmode)
{
int pos = 0 , ii , recsize , transmit = 1 , dlestate = 0 ;
char rec [20] , cc ;
while (pos < blocksize)
{
if (transmit)
{
setTimer (ITIMER_REAL,IOTIMEOUT) ;
if ((ii = write(line,block+pos,pos+VBLEN < blocksize ? VBLEN : blocksize-pos)) > 0)
{
setTimer (ITIMER_REAL,0L) ;
pos += ii ;
}
else
return ROTH_TIMEOUT ;
}
/* read bytes immediately */
ii = fcntl (line,F_GETFL,0);
fcntl (line,F_SETFL,ii|O_NDELAY) ;
recsize = read (line,rec,sizeof(rec)) ;
fcntl (line,F_SETFL,ii) ;
/* check received bytes */
for (ii = 0 ; ii < recsize ; ii++)
{
cc = *(rec+ii) ;
if (cc == XON)
{
transmit = 1 ;
log (DEBUGFILE,"XON from modem - started") ;
}
else if (cc == XOFF)
{
transmit = 0 ;
log (DEBUGFILE,"XOFF from modem - stopped") ;
}
else if (dlestate)
{
dlestate = 0 ;
switch (cc)
{
case '*':
if (breakmode)
return (*pwdnr < 0) ? ROTH_STAR : ROTH_PWDENTERED ;
break ;
case '#':
*pwdnr = 0 ;
break ;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (*pwdnr >= 0)
{
*pwdnr = 10 * *pwdnr + (cc-'0') ;
if (breakmode == 1)
return ROTH_PWDENTERED ;
}
break ;
case 'c':
return RZYX_FAX ;
case 'b':
return RZYX_BUSY ;
case 'h':
case 'd':
return RZYX_HANGUP ; /* 'd' = Dialtone (6.11a) */
case 'e':
return RZYX_DATACALL ; /* 'e' = Data Calling Tone (6.11a) */
case ETX:
return RZYX_ENDOFTEXT ;
}
}
else if (cc == DLE)
dlestate = 1 ;
}
}
return ROTH_COMPLETED ;
}
static int readVoice (int line,long *exptime,char *block,int *blocksize,int *dlestate,int *pwdnr,int breakmode)
{
int ii , recsize ;
char rec [*blocksize] , cc ;
setTimer (ITIMER_REAL,IOTIMEOUT) ;
recsize = read (line,rec,*blocksize) ;
if (recsize <= 0)
return ROTH_TIMEOUT ;
*exptime -= IOTIMEOUT - setTimer (ITIMER_REAL,0L) ;
for (ii = *blocksize = 0 ; ii < recsize ; ii++)
{
cc = *(rec+ii) ;
if (*dlestate)
{
*dlestate = 0 ;
switch (cc)
{
case '*':
if (breakmode)
return (*pwdnr < 0) ? ROTH_STAR : ROTH_PWDENTERED ;
case '#':
*pwdnr = 0 ;
break ;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (*pwdnr >= 0)
{
*pwdnr = 10 * *pwdnr + (cc-'0') ;
if (breakmode == 1)
return ROTH_PWDENTERED ;
}
break ;
case 'c':
return RZYX_FAX ;
case 'b':
return RZYX_BUSY ;
case 'h':
case 'd':
return RZYX_HANGUP ; /* 'd' = Dialtone (6.11a) */
case 'e':
return RZYX_DATACALL ; /* 'e' = Data Calling Tone (6.11a) */
case ETX:
return RZYX_ENDOFTEXT ;
case 's':
return RZYX_SILENCE ;
case 'q':
return RZYX_QUIET ;
case DLE:
*(block + (*blocksize)++) = cc ;
break ;
}
}
else if (cc == DLE)
*dlestate = 1 ;
else
*(block + (*blocksize)++) = cc ;
}
return *exptime > 0 ? ROTH_COMPLETED : ROTH_EXPIRATION ;
}
/* breakmode = 1 */
static int readDigitDuringVoice (int line,long exptime,int *pwdnr)
{
int ii , recsize , dlestate = 0 ;
char rec [100] , cc ;
while (exptime > 0)
{
setTimer (ITIMER_REAL,IOTIMEOUT) ;
recsize = read (line,rec,sizeof(rec)) ;
if (recsize <= 0)
return ROTH_TIMEOUT ;
exptime -= IOTIMEOUT - setTimer (ITIMER_REAL,0L) ;
for (ii = 0 ; ii < recsize ; ii++)
{
cc = *(rec+ii) ;
if (dlestate)
{
dlestate = 0 ;
switch (cc)
{
case '*':
return (*pwdnr < 0) ? ROTH_STAR : ROTH_PWDENTERED ;
case '#':
*pwdnr = 0 ;
break ;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (*pwdnr == 0)
{
*pwdnr = cc - '0' ;
return ROTH_PWDENTERED ;
}
break ;
case 'c':
return RZYX_FAX ;
case 'b':
return RZYX_BUSY ;
case 'h':
case 'd':
return RZYX_HANGUP ; /* 'd' = Dialtone (6.11a) */
case 'e':
return RZYX_DATACALL ; /* 'e' = Data Calling Tone (6.11a) */
case ETX:
return RZYX_ENDOFTEXT ;
case 's':
return RZYX_SILENCE ;
case 'q':
return RZYX_QUIET ;
}
}
else if (cc == DLE)
dlestate = 1 ;
}
}
return ROTH_EXPIRATION ;
}
static int dialog (int line,long msec,char *until,char *question,...)
{
va_list ap ;
char buffer [1000] ;
long usedtime ;
char sbuf [1000] , rbuf [1000] ;
int len , rc = -1 ;
va_start (ap,question) ;
vsprintf (buffer,question,ap) ;
va_end (ap) ;
debugsputs (sbuf,buffer) ; debugsputs (rbuf,until) ;
log (DEBUGFILE,"send \"%s\" wait for \"%s\"",sbuf,rbuf) ;
len = strlen (buffer) ;
if (write(line,buffer,len) == len)
{
if ((usedtime = readUntil(line,msec,until)) >= 0L)
{
log (DEBUGFILE,"got it after %ld msec",usedtime) ;
rc = 0 ;
}
else
log (DEBUGFILE,"reading failed (Timeout after %ld msec)",msec) ;
}
else
log (DEBUGFILE,"writing failed") ;
return rc ;
}
static int dialogWithAnswer (int line,long msec,char *answer,int answerlength,char *until,char *question,...)
{
va_list ap ;
char buffer [1000] ;
long usedtime ;
char sbuf [1000] , rbuf [1000] ;
int len , rc = -1 ;
va_start (ap,question) ;
vsprintf (buffer,question,ap) ;
va_end (ap) ;
debugsputs (sbuf,buffer) ; debugsputs (rbuf,until) ;
log (DEBUGFILE,"send \"%s\" wait for \"%s\"",sbuf,rbuf) ;
len = strlen (buffer) ;
if (write(line,buffer,len) == len)
{
if ((usedtime = readAnswerUntil(line,msec,answer,answerlength,until)) >= 0L)
{
log (DEBUGFILE,"got \"%s\" after %ld msec",debugsputs(rbuf,answer),usedtime) ;
rc = 0 ;
}
else
log (DEBUGFILE,"reading failed (Timeout after %ld msec)",msec) ;
}
else
log (DEBUGFILE,"writing failed") ;
return rc ;
}
static int playVoice (int line,char *filename,int *pwdnr,int breakmode)
{
ZyxelSND zsnd ;
int rc ;
if (ZSNDread(filename,&zsnd) != 0)
{
log (DEBUGFILE,"Error reading ZyXEL voice file.") ;
return -1 ;
}
if (ZSNDmakePlayable(&zsnd) != 0)
{
log (DEBUGFILE,"Error making playable ZyXEL voice.") ;
return -1 ;
}
log (DEBUGFILE,"Playing voice %s",strrchr(filename,'/')+1) ;
/* set Voice compression mode */
dialog (line,1000L,"OK\r\n","AT+VSM=%d\r",VSM(zsnd.zyxel.voice)) ;
/* set to voice transmission */
dialog (line,1000L,"CONNECT\r\n","AT+VTX\r") ;
rc = writeVoice (line,zsnd.play,zsnd.playlen,pwdnr,breakmode) ;
/* set to end of voice */
dialog (line,4000L,"VCON\r\n","%c%c",DLE,ETX) ;
ZSNDfree (&zsnd) ;
return rc ;
}
static int receiveVoice (int line,int voiceCompressionMode,int *pwdnr,int breakmode)
{
ZyxelHeader zyxhead = { 0 } ;
FILE *fpzsnd ;
char buf [1000] ;
int rc , buflen , dlestate = 0 ;
long exptime = cfg_voiceexpiration * 1000L ;
memcpy (zyxhead.title,ZYX_TITLE,strlen(ZYX_TITLE)) ;
zyxhead.mode = ZYX_VOC ;
zyxhead.voice = voiceCompressionMode ;
if ((fpzsnd = fopen(TEMPVOICE,"w")) == NULL)
{
log (DEBUGFILE,"receiveVoice: cannot open temp voice file.") ;
return -1 ;
}
if (fwrite(&zyxhead,sizeof(ZyxelHeader),1,fpzsnd) != 1)
{
log (DEBUGFILE,"receiveVoice: cannot write to temp voice file.") ;
fclose (fpzsnd) ;
return -1 ;
}
/* set silent detection values */
dialog (line,1000L,"OK\r\n","AT+VSD=%d,%d\r",SILENTSENSITIVITY,SILENTPERIOD) ;
/* set voice compression mode */
dialog (line,1000L,"OK\r\n","AT+VSM=%d\r",VSM(voiceCompressionMode)) ;
/* set to Voice transmission */
dialog (line,1000L,"CONNECT\r\n","AT+VRX\r") ;
do
{
buflen = sizeof(buf) ;
if ((rc = readVoice(line,&exptime,buf,&buflen,&dlestate,pwdnr,breakmode)) != ROTH_TIMEOUT)
fwrite (buf,buflen,1,fpzsnd) ;
} while (rc == ROTH_COMPLETED) ;
/* set to end of voice */
dialog (line,4000L,"VCON\r\n","%c%c",DLE,ETX) ;
fclose (fpzsnd) ;
return rc ;
}
static int receiveDigitDuringVoice (int line,int voiceCompressionMode,int *pwdnr)
{
int rc ;
/* set silent detection off */
dialog (line,1000L,"OK\r\n","AT+VSD=%d,%d\r",0,SILENTPERIOD) ;
/* set voice compression mode */
dialog (line,1000L,"OK\r\n","AT+VSM=%d\r",VSM(voiceCompressionMode)) ;
/* set to Voice transmission */
dialog (line,1000L,"CONNECT\r\n","AT+VRX\r") ;
rc = readDigitDuringVoice (line,cfg_beepexpiration*1000L,pwdnr) ;
/* set to end of voice */
dialog (line,4000L,"VCON\r\n","%c%c",DLE,ETX) ;
return rc ;
}
static int receiveFax (int line,char *fileexpression)
{
char faxparameter [200] , *dcs , *pts , *et , *remoteid ;
char FaxPageHeader [3] = { '0' , '0' , '0' } ;
char buffer [2000] ;
int rc , pages , dlestate ;
char filename [MAXPATHLEN+1] ;
FILE *fp ;
char dc2 = DC2 , cc ;
dialog (line,2000L,"OK\r\n","AT+FCLASS=2\r") ;
dialog (line,2000L,"OK\r\n","AT&H3\r") ; /* RTS/CTS */
dialog (line,2000L,"OK\r\n","AT+FBOR=1\r") ; /* reverse bit order */
dialog (line,2000L,"OK\r\n","AT+FCR=1\r") ;
dialog (line,2000L,"OK\r\n","AT+FLID=%s\r",cfg_localfaxid) ;
dialogWithAnswer (line,30000L,faxparameter,sizeof(faxparameter),"\r\nOK","ATA\r") ;
if ((dcs = strstr(faxparameter,"+FDCS:")) != NULL)
{
int vr , br , wd , ln , df , ec , bf , st ;
char *VR [] = { "Normal, 98 lpi" , "Fine, 196 lpi" } ;
char *BR [] = { "2400 bit/s V.27 ter" , "4800 bit/s V.27 ter" ,
"7200 bit/s V.29 or v.17" , "9600 bit/s V.29 or v.17" ,
"12000 bit/s V.33 or v.17" , "14400 bit/s V.33 or v.17" } ;
char *WD [] = { "1728 pixels in 215 mm" , "2048 pixels in 255 mm" ,
"2432 pixels in 303 mm" , "1216 pixels in 151 mm" ,
"864 pixels in 107 mm" } ;
char *LN [] = { "A4, 297 mm" , "B4, 364 mm" , "unlimited length" } ;
char *DF [] = { "1-D modified Huffman" , "2-D modified Read" ,
"2-D uncompressed mode" , "2-D modified Read" } ;
char *EC [] = { "Disable ECM" , "Enable ECM,64 bytes/frame" ,
"Enable ECM,256 bytes/frame" } ;
char *BF [] = { "Disable BFT" , "Enable BFT" } ;
char *ST [] = { "0 ms" , "5 ms" , "10 ms" , "10 ms" ,
"20 ms" , "20 ms" , "40 ms" , "40 ms" } ;
sscanf (dcs,"+FDCS:%d,%d,%d,%d,%d,%d,%d,%d",&vr,&br,&wd,&ln,&df,&ec,&bf,&st) ;
log (LOGFILE,"remote capabilities") ;
log (LOGFILE," VR = %s",VR[vr]) ;
log (LOGFILE," BR = %s",BR[br]) ;
log (LOGFILE," WD = %s",WD[wd]) ;
log (LOGFILE," LN = %s",LN[ln]) ;
log (LOGFILE," DF = %s",DF[df]) ;
log (LOGFILE," EC = %s",EC[ec]) ;
log (LOGFILE," BF = %s",BF[bf]) ;
log (LOGFILE," ST = %s",ST[st]) ;
FaxPageHeader [0] = vr + '0' ;
}
if ((remoteid = strstr(faxparameter,"+FTSI:")) != NULL)
{
remoteid += strlen ("+FTSI:") ; *strchr(remoteid,'\r') = '\0' ;
log (LOGFILE,"connected to %s",remoteid) ;
}
for ( pages = 1 ; ; )
{
dialogWithAnswer (line,8000L,buffer,sizeof(buffer),"\r\nCONNECT\r\n","AT+FDR\r") ;
log (LOGFILE,"--- reading page %d",pages) ;
sprintf (filename,"%s.%03d",fileexpression,pages) ;
fp = fopen (filename,"w") ;
fwrite (FaxPageHeader,sizeof(FaxPageHeader),1,fp) ;
write (line,&dc2,1) ;
for (dlestate = 0 , rc = ROTH_COMPLETED ; rc == ROTH_COMPLETED ; )
{
setTimer (ITIMER_REAL,IOTIMEOUT) ;
if (read(line,&cc,1) <= 0)
{
rc = ROTH_TIMEOUT ;
break ;
}
setTimer (ITIMER_REAL,0L) ;
if (dlestate)
{
dlestate = 0 ;
switch (cc)
{
case ETX:
rc = RZYX_ENDOFTEXT ;
break ;
case DLE:
fwrite (&cc,1,1,fp) ;
break ;
default:
break ;
}
}
else if (cc == DLE)
dlestate = 1 ;
else
fwrite (&cc,1,1,fp) ;
}
fclose (fp) ;
if (rc == ROTH_TIMEOUT)
{
log (ERRLOGFILE,"receiveFax TIMEOUT (should never happen)") ;
break ;
}
/* read result of page receiving */
readAnswerUntil (line,2000L,buffer,sizeof(buffer),"OK\r\n") ;
if ((pts = strstr(buffer,"+FPTS:")) != NULL)
{
int ppr , lc ;
char *PPR [] = { "Partial page errors" , "Page Good" ,
"Page bad, retrain requested" , "Page good, retrain requested" ,
"Page bad, interrupt requested" , "Page good, interrupt requested" } ;
sscanf (pts,"+FPTS:%d,%d",&ppr,&lc) ;
log (LOGFILE,"Result of page %d: %s. %d lines",pages,PPR[ppr],lc) ;
}
if ((et = strstr(buffer,"+FET:")) != NULL)
{
int ppm ;
sscanf (et,"+FET:%d",&ppm) ;
log (DEBUGFILE,"FET result = %d",ppm) ;
if (ppm == 0)
pages++ ;
else
{
if (ppm == 2)
{
char *hng ;
int hangup ;
dialogWithAnswer (line,4000L,buffer,sizeof(buffer),"\r\nOK\r\n","AT+FDR\r") ;
if ((hng = strstr(buffer,"+FHNG:")) != NULL)
{
sscanf (hng,"+FHNG:%d",&hangup) ;
log (LOGFILE,"hangup [%d]",hangup) ;
}
}
rc = ROTH_COMPLETED ;
break ;
}
}
}
if (rc == ROTH_COMPLETED)
{
/* generating an empty fax file */
sprintf (filename,"%s.fax",fileexpression) ;
fp = fopen (filename,"w") ; fclose (fp) ;
sprintf (filename,"/usr/lib/NextPrinter/faxcleanup -X -Q -c \"%s\" %d %s",
remoteid,pages,fileexpression) ;
system (filename) ;
}
return rc ;
}
static int dataCall (int line)
{
log (LOGFILE,"dataCall: not yet implemented.") ;
return 0 ;
}
static char *MODEM_manufacturer (int line,char *buffer,int buflen)
{
return (dialogWithAnswer(line,1000L,buffer,buflen,"\r\nOK\r\n","AT+FMFR?\r") == 0) ?
strrchr (buffer,'\n') + 1 : NULL ;
}
static char *MODEM_revision (int line,char *buffer,int buflen)
{
return (dialogWithAnswer(line,1000L,buffer,buflen,"\r\n\r\nOK\r\n","AT+FREV?\r") == 0) ?
strrchr (buffer,'\n') + 1 : NULL ;
}
#define MODEM_beep(line) dialog (line,1000L,"OK\r\n","AT+VBT=2+VTS=A\r")
#define MODEM_speaker(line,how) dialog (line,1000L,"VCON\r\n","AT+VLS=%d\r",how ? 16 : 0) ;
static void sendVoiceMail (char *filename)
{
char cmd [MAXPATHLEN+1] ;
char newname [MAXPATHLEN+1] ;
if (*cfg_mailaddresses)
{
switch (cfg_voicemailmode)
{
case 0:
break ;
case 1:
sprintf (cmd,"echo \"Please look at your Voice Reader.\" | /usr/ucb/mail "
"-s \"AnsweringMachine\" %s > /dev/null &",cfg_mailaddresses) ;
system (cmd) ;
break ;
case 2:
sprintf (cmd,"%s %s \"%s\" > /dev/null &",
cfg_execvoicemail,filename,cfg_mailaddresses) ;
system (cmd) ;
break ;
case 3:
sprintf (newname,"%s/__archive%s",cfg_voicespooldir,strrchr(filename,'/')) ;
if (rename(filename,newname) == 0)
{
sprintf (cmd,"%s %s \"%s\" > /dev/null &",
cfg_execvoicemail,newname,cfg_mailaddresses) ;
system (cmd) ;
}
break ;
}
}
}
static void sendFaxMail (char *fileexpression)
{
char cmd [MAXPATHLEN+1] ;
// char newname [MAXPATHLEN+1] ;
if (*cfg_mailaddresses)
{
switch (cfg_faxmailmode)
{
case 0:
break ;
case 1:
sprintf (cmd,"echo \"Please look at your Fax Reader.\" | /usr/ucb/mail "
"-s \"AnsweringMachine\" %s > /dev/null &",cfg_mailaddresses) ;
system (cmd) ;
break ;
case 2:
sprintf (cmd,"%s %s \"%s\" > /dev/null &",
cfg_execfaxmail,fileexpression,cfg_mailaddresses) ;
system (cmd) ;
break ;
case 3:
/* sorry, archiving not finished yet */
sprintf (cmd,"%s %s \"%s\" > /dev/null &",
cfg_execfaxmail,fileexpression,cfg_mailaddresses) ;
system (cmd) ;
break ;
}
}
}
static int moveVoiceAssumingSilence (char *oldname,char *newname)
{
ZyxelSND zsnd ;
int len ;
FILE *fpzsnd ;
if (ZSNDread(oldname,&zsnd) != 0)
{
log (DEBUGFILE,"Error reading ZyXEL voice file.") ;
return -1 ;
}
if ((len = zsnd.voicelen - (SILENTPERIOD * VBPS(RECVCM)) / 10) < 0)
{
ZSNDfree (&zsnd) ;
remove (oldname) ;
return 1 ;
}
if ((fpzsnd = fopen(newname,"w")) == NULL)
{
log (DEBUGFILE,"Error writing ZyXEL voice file.") ;
ZSNDfree (&zsnd) ;
return rename (oldname,newname) ;
}
if (fwrite(&zsnd.zyxel,sizeof(ZyxelHeader),1,fpzsnd) != 1 ||
fwrite(zsnd.voice,len,1,fpzsnd) != 1)
{
log (DEBUGFILE,"Error writing ZyXEL voice file.") ;
fclose (fpzsnd) ;
remove (newname) ;
ZSNDfree (&zsnd) ;
return rename (oldname,newname) ;
}
fclose (fpzsnd) ;
ZSNDfree (&zsnd) ;
remove (oldname) ;
return 0 ;
}
static void passwordReceived (int line,int pwdnr,char *timebuffer)
{
char filename [MAXPATHLEN+1] ;
int rc ;
if (pwdnr == cfg_voicerootnumber)
{
DIR *dirp = opendir (cfg_voicespooldir) ;
struct direct *dp = NULL ;
int extlen = strlen (ZSND) ;
for ( ; ; )
{
sprintf (filename,"%s/%s%s",cfg_voicesystemdir,VOC_ROOTMENU,ZSND) ;
pwdnr = -1 ;
rc = playVoice (line,filename,&pwdnr,1) ;
if (rc != ROTH_COMPLETED && rc != ROTH_STAR && rc != ROTH_PWDENTERED)
break ;
if (rc != ROTH_PWDENTERED)
rc = receiveDigitDuringVoice (line,RECVCM,&pwdnr) ;
if (rc == ROTH_PWDENTERED)
{
switch (pwdnr)
{
case 1:
while ((dp = readdir(dirp)) != NULL)
{
int namelen = strlen (dp->d_name) ;
if (namelen >= extlen && !strcmp(dp->d_name+namelen-extlen,ZSND))
break ;
}
if (dp)
sprintf (filename,"%s/%s",cfg_voicespooldir,dp->d_name) ;
else
sprintf (filename,"%s/%s%s",cfg_voicesystemdir,VOC_NOMSGTOPLAY,ZSND) ;
break ;
case 2:
if (dp)
{
char newname [MAXPATHLEN+1] ;
sprintf (filename,"%s/%s",cfg_voicespooldir,dp->d_name) ;
sprintf (filename,"%s/__archive/%s",cfg_voicespooldir,dp->d_name) ;
rename (filename,newname) ;
sprintf (filename,"%s/%s%s",cfg_voicesystemdir,VOC_LASTMSGARCHIVED,ZSND) ;
}
else
sprintf (filename,"%s/%s%s",cfg_voicesystemdir,VOC_NOMSGTOARCHIVE,ZSND) ;
break ;
default:
sprintf (filename,"%s/%s%s",cfg_voicesystemdir,VOC_BADINPUT,ZSND) ;
break ;
}
rc = playVoice (line,filename,&pwdnr,2) ;
if (rc != ROTH_COMPLETED && rc != ROTH_STAR && rc != ROTH_PWDENTERED)
break ;
}
else if (rc == ROTH_EXPIRATION)
{
sprintf (filename,"%s/%s%s",cfg_voicesystemdir,VOC_TIMELIMIT,ZSND) ;
rc = playVoice (line,filename,&pwdnr,0) ;
break ;
}
else
break ;
}
closedir (dirp) ;
}
else
{
log (DEBUGFILE,"Next error dont care.") ;
sprintf (filename,"%s/%d%s",cfg_voicefrienddir,pwdnr,ZSND) ;
if ((rc = playVoice(line,filename,&pwdnr,2)) == -1)
{
sprintf (filename,"%s/%s%s",cfg_voicesystemdir,VOC_NOMSGFORFRIEND,ZSND) ;
rc = playVoice (line,filename,&pwdnr,2) ;
}
if (rc == ROTH_COMPLETED || rc == ROTH_STAR || rc == ROTH_PWDENTERED)
{
MODEM_beep (line) ;
rc = receiveVoice (line,RECVCM,&pwdnr,0) ;
switch (rc)
{
case RZYX_FAX:
case RZYX_DATACALL:
remove (TEMPVOICE) ;
log (LOGFILE,"receiveVoice: Unexpected event Data/Fax.") ;
break ;
case RZYX_SILENCE:
remove (TEMPVOICE) ;
log (DEBUGFILE,"receiveVoice: silent voice removed.") ;
break ;
case RZYX_BUSY:
case RZYX_HANGUP:
case RZYX_ENDOFTEXT:
case ROTH_EXPIRATION:
log (DEBUGFILE,"receiveVoice: rc = %d.",rc) ;
sprintf (filename,"%s/%s%s",cfg_voicespooldir,timebuffer,ZSND) ;
log (LOGFILE,"received call %s",filename) ;
if (rename(TEMPVOICE,filename) == 0)
sendVoiceMail (filename) ;
break ;
case RZYX_QUIET:
log (DEBUGFILE,"receiveVoice: rc = %d.",rc) ;
sprintf (filename,"%s/%s%s",cfg_voicespooldir,timebuffer,ZSND) ;
log (LOGFILE,"received call %s",filename) ;
if (moveVoiceAssumingSilence(TEMPVOICE,filename) == 0)
sendVoiceMail (filename) ;
break ;
case ROTH_TIMEOUT:
remove (TEMPVOICE) ;
log (ERRLOGFILE,"receiveVoice: Timeout (Should never happen).") ;
break ;
case -1:
log (ERRLOGFILE,"receiveVoice: File system problems (Should never happen).") ;
break ;
}
}
}
}
static void phoneCall (int line)
{
char filename [MAXPATHLEN+1] ;
char timebuffer [60] ;
int pwdnr = -1 , rc ;
currentTimeString (timebuffer,sizeof(timebuffer)) ;
sprintf (filename,"%s/%s%s",cfg_voicesystemdir,VOC_WELCOME,ZSND) ;
switch ((rc = playVoice(line,filename,&pwdnr,2)))
{
case ROTH_PWDENTERED:
log (LOGFILE,"Password #%d entered.",pwdnr) ;
passwordReceived (line,pwdnr,timebuffer) ;
break ;
case RZYX_FAX:
log (LOGFILE,"Trying to make a fax connection.") ;
sprintf (filename,"%s/%s",cfg_faxspooldir,timebuffer) ;
if (receiveFax(line,filename) == ROTH_COMPLETED)
sendFaxMail (filename) ;
break ;
case RZYX_DATACALL:
log (LOGFILE,"Trying to make a data connection.") ;
dataCall (line) ;
break ;
case ROTH_STAR:
case RZYX_BUSY:
case RZYX_HANGUP:
case RZYX_ENDOFTEXT:
log (DEBUGFILE,"playVoice: rc = %d. No action.",rc) ;
break ;
case ROTH_COMPLETED:
MODEM_beep (line) ;
rc = receiveVoice (line,RECVCM,&pwdnr,2) ;
switch (rc)
{
case ROTH_PWDENTERED:
remove (TEMPVOICE) ;
log (LOGFILE,"Password #%d entered.",pwdnr) ;
passwordReceived (line,pwdnr,timebuffer) ;
break ;
case RZYX_FAX:
remove (TEMPVOICE) ;
log (LOGFILE,"Trying to make a fax connection.") ;
sprintf (filename,"%s/%s",cfg_faxspooldir,timebuffer) ;
if (receiveFax(line,filename) == ROTH_COMPLETED)
sendFaxMail (filename) ;
break ;
case RZYX_DATACALL:
remove (TEMPVOICE) ;
log (LOGFILE,"Trying to make a data connection.") ;
dataCall (line) ;
break ;
case RZYX_SILENCE:
remove (TEMPVOICE) ;
log (LOGFILE,"receiveVoice: silent voice.") ;
break ;
case ROTH_STAR:
case RZYX_BUSY:
case RZYX_HANGUP:
case RZYX_ENDOFTEXT:
case ROTH_EXPIRATION:
log (DEBUGFILE,"receiveVoice: rc = %d.",rc) ;
sprintf (filename,"%s/%s%s",cfg_voicespooldir,timebuffer,ZSND) ;
log (LOGFILE,"received call %s",filename) ;
if (rename(TEMPVOICE,filename) == 0)
sendVoiceMail (filename) ;
break ;
case RZYX_QUIET:
log (DEBUGFILE,"receiveVoice: rc = %d.",rc) ;
sprintf (filename,"%s/%s%s",cfg_voicespooldir,timebuffer,ZSND) ;
log (LOGFILE,"received call %s",filename) ;
if (moveVoiceAssumingSilence(TEMPVOICE,filename) == 0)
sendVoiceMail (filename) ;
break ;
case ROTH_TIMEOUT:
remove (TEMPVOICE) ;
log (ERRLOGFILE,"receiveVoice: Timeout (Should never happen).") ;
break ;
case -1:
log (ERRLOGFILE,"receiveVoice: File system problems (Should never happen).") ;
break ;
}
break ;
case -1:
case ROTH_TIMEOUT:
log (ERRLOGFILE,"playVoice: rc = %d (Should never happen).",rc) ;
break ;
}
}
static void takeOver (int line)
{
char filename [MAXPATHLEN+1] ;
char timebuffer [60] ;
int pwdnr = -1 , rc ;
dialog (line,4000L,"VCON\r\n","ATA\r") ;
rc = receiveDigitDuringVoice (line,RECVCM,&pwdnr) ;
switch (rc)
{
case RZYX_FAX:
log (LOGFILE,"Trying to make a fax connection.") ;
currentTimeString (timebuffer,sizeof(timebuffer)) ;
sprintf (filename,"%s/%s",cfg_faxspooldir,timebuffer) ;
if (receiveFax(line,filename) == ROTH_COMPLETED)
sendFaxMail (filename) ;
break ;
case RZYX_DATACALL:
log (LOGFILE,"Trying to make a data connection.") ;
dataCall (line) ;
break ;
default:
log (DEBUGFILE,"receiveDigitDuringVoice: rc = %d.",rc) ;
break ;
}
}
static int initModem (int line)
{
char buf [400] ;
log (LOGFILE,"----- init modem -----") ;
dialog (line,2000L,"OK\r\n","AT\r") ;
dialog (line,2000L,"OK\r\n","ATZ\r") ;
log (LOGFILE,"%s modem",MODEM_manufacturer(line,buf,sizeof(buf))) ;
log (LOGFILE,"rev %s",MODEM_revision(line,buf,sizeof(buf))) ;
dialog (line,2000L,"OK\r\n","ATE1M%dL%dS0=%d\r",cfg_speaker,cfg_volume,cfg_rings) ;
dialog (line,2000L,"OK\r\n","AT&D3&S1\r") ;
dialog (line,2000L,"OK\r\n","AT+FCLASS=8\r") ;
return 0 ;
}
/*****
***** Strategy for accessing and locking device DIALOUT
***** After every foreign access (and at our start time) we have to
***** initialize the modem, which we can only do, if we have locked the device.
***** If we cannot lock the device, we wait 2 seconds and try again.
***** The initialization of the modem after a foreign access is necessary,
***** because we have no idea, what the other process has done with it.
*****
***** Now we wait ad infinitum for any bytes on the device we could read
***** (via 'select'). If there are some, we try to lock the device for reading
***** these bytes. If we cannot (because device is already locked), we take
***** hands off and ckeck the lock from time to time to see, whether the device
***** is unlocked. If we can, we assume, that the modem wants to tell US something,
***** in general 'RING'.
***** If not, probably a stupid process tries to talk with the modem without
***** locking the device. Great confusion will occur, and no process will
***** understand a word. But who cares.
*****
***** Now we handle the phone call, unlock the device and finish.
*****/
int main (int argc,char *argv[])
{
int line , ringcount ;
char buf [1000] ;
#ifndef TESTMODE
fd_set fdset ;
#endif
log (LOGFILE,"***** Starting Answering Machine *****") ;
setDefaults () ;
#ifndef TESTMODE
if (argc > 2)
#else
if (argc > 1)
#endif
{
if (configurate(argv[1]) == 0)
log (LOGFILE," using config file \'%s\'",argv[1]) ;
else
log (LOGFILE," config file \'%s\' not found (using defaults)",argv[1]) ;
}
else
log (LOGFILE," using defaults") ;
signal (SIGALRM,signalhandler) ;
siginterrupt (SIGALRM,1) ;
//signal (SIGINT,signalhandler) ;
//signal (SIGTERM,signalhandler) ;
if ((line = openDevice(DIALOUT,cfg_port)) < 0)
{
log (LOGFILE,"cannot open dial out line") ;
sleep (2) ;
return 1 ;
}
#ifndef TESTMODE
FD_ZERO (&fdset) ;
FD_SET (line,&fdset) ;
/* endless loop to get control over the modem after activity */
for ( ; ; )
{
while (lockDevice(DIALOUT,cfg_port) != 0)
{
log (LOGFILE,"cannot lock dial out line") ;
sleep (2) ;
}
initDevice (line,B38400) ;
initModem (line) ;
unlockDevice (DIALOUT,cfg_port) ;
select (1,&fdset,NULL,NULL,NULL) ;
log (LOGFILE,"Activity on port") ;
if (lockDevice(DIALOUT,cfg_port) != 0)
{
log (LOGFILE,"port in use") ;
do
{
sleep (2) ;
} while (deviceLocked(DIALOUT,cfg_port) == 0) ;
log (LOGFILE,"port freed") ;
}
else
break ;
}
remove (FAXSIGNAL) ;
/* endless loop to check, what does the modem wants to tell us */
for ( ringcount = 0 ; ; )
{
if (readUntil(line,6000L,"\r\n") != -1L &&
readAnswerUntil(line,500L,buf,sizeof(buf),"\r\n") != -1L)
{
if (strstr(buf,"RING"))
{
log (LOGFILE,"ringing %d",++ringcount) ;
}
else if (strstr(buf,"VCON"))
{
log (LOGFILE,"Voice connection.") ;
phoneCall (line) ;
break ;
}
else
{
log (LOGFILE,"no reaction to modem opinion \"%s\"",buf) ;
break ;
}
}
else
{
if (ringcount)
{
/* we wait 10 seconds for a fax signal */
for (ringcount = 0 ; ringcount < 50 ; ringcount++)
{
if (access(FAXSIGNAL,F_OK) == 0)
{
takeOver (line) ;
remove (FAXSIGNAL) ;
break ;
}
usleep (200000L) ;
}
}
else
log (LOGFILE,"cannot understand modem speech") ;
break ;
}
}
unlockDevice (DIALOUT,cfg_port) ;
#else
initDevice (line,B38400) ;
initModem (line) ;
/* endless loop to check, what does the modem wants to tell us */
for ( ringcount = 0 ; ; )
{
if (readUntil(line,6000L,"\r\n") != -1L &&
readAnswerUntil(line,500L,buf,sizeof(buf),"\r\n") != -1L)
{
if (strstr(buf,"RING"))
{
log (LOGFILE,"ringing %d",++ringcount) ;
}
else if (strstr(buf,"VCON"))
{
log (LOGFILE,"Voice connection.") ;
MODEM_speaker (line,1) ;
phoneCall (line) ;
break ;
}
else
{
log (LOGFILE,"no reaction to modem opinion \"%s\"",buf) ;
break ;
}
}
else
log (LOGFILE,"timeout for connection") ;
}
#endif
closeDevice (line) ;
return 0 ;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.