ftp.nice.ch/pub/next/unix/communication/newam.0.1.s.tar.gz#/newam-0.1/src/am.c

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.