#!/bin/sh
#-------------------------------------------------------------------------
# ans A bash script to simulate an answering machine.
# Discriminate voice, fax and data calls. Password-protected
# remote mode to retrieve and delete messages. Remote change
# of greeting and password.
#
# Version: 0.5 Jan 4 1997
#
# Author: Niccolo Rigacci <fd131@cleveland.freenet.edu>
#
# Copyright (C) 1996 Niccolo Rigacci
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
# USER CONFIGURABLE PARAMETERS
#-------------------------------------------------------------------------
WAITRINGS=4
UMASK=0117
# Discard message if shorter than SHORTMSG bytes. My modem
# with default settings records approx. 3.5 kb per second.
SHORTMSG=25000
# Max messages length (seconds).
TIMEOUT=120
# Devices, directories and filenames
#------------------------------------------
TTYS="ttyS1"
DEVICE="/dev/${TTYS}"
NULL="/dev/null"
LIBDIR="/usr/local/lib/ans"
FAXDIR="/var/spool/fax"
VOICEDIR="/var/spool/voice"
TMPDIR="/tmp"
LOCK="/var/lock/LCK..${TTYS}"
PASSWD="${LIBDIR}/passwd"
STOPFILE="/var/run/ans.${TTYS}.stop"
DOITNOW="/var/run/ans.${TTYS}.now"
TMP1=${TMPDIR}/${$}.1.tmp
TMP2=${TMPDIR}/${$}.2.tmp
EXT=r4
# Auxiliary binaries
#------------------------------------------
VMCP="/usr/bin/vmcp -d${TTYS}"
LINRES="/usr/bin/linres"
EFAX="/usr/local/bin/efax"
# Standard auxiliary binaries
#------------------------------------------
# AGETTY="/sbin/agetty" # Changed for DosLinux to uugetty.
AGETTY="/sbin/uugetty"
CAT="cat"
CP="cp"
CUT="cut"
DATE="date"
ECHO="echo"
DIFF="diff"
FIND="find"
KILL="kill"
LS="ls"
MV="mv"
RM="rm"
SED="sed"
SORT="sort"
TOUCH="touch"
WC="wc"
# Modem commands
#------------------------------------------
# AT_LINE string must instructs the modem to:
# - raises CD on remote carrier
# - DTR commutation causes hangup and return to command mode
# - uses RTS/CTS hardware flow control
AT_LINE="AT&C1&D2&K3"
AT_A="ATA"
AT_H="ATH"
AT_RESET="AT&F"
AT_VINIT="AT#CLS=8#VLS=4#VBS=4#VSD=1#VSS=2"
AT_VOICE="AT#CLS=8#BDR=16"
AT_PLAY="AT#CLS=8#VLS=2"
AT_BEEP="AT#VBT=3#VTS=#"
AT_VTX="AT#VTX"
AT_VRX="AT#VRX"
AT_VSS0="AT#VSS=0"
AT_VSS2="AT#VSS=2"
AT_DATA="AT#CLS=0"
AT_FAX="AT#CLS=2"
# Builtin messages files
#------------------------------------------
ASK_PASSWD=${LIBDIR}/ask_passwd.$EXT
CH_GREETING=${LIBDIR}/ch_greeting.$EXT
GET_MSGS_MNU=${LIBDIR}/get_msgs_mnu.$EXT
GREETING=${LIBDIR}/greeting.$EXT
MSG_DELETED=${LIBDIR}/msg_deleted.$EXT
PASSWD_CH=${LIBDIR}/passwd_ch.$EXT
PASSWD_NOT_CH=${LIBDIR}/passwd_not_ch.$EXT
REMOTE_MENU=${LIBDIR}/remote_menu.$EXT
WRONG_PASSWD=${LIBDIR}/wrong_passwd.$EXT
DLE_ETX=${LIBDIR}/dle_etx
DLE_CAN=${LIBDIR}/dle_can
#-------------------------------------------------------------------------
# END OF USER CONFIGURABLE PARAMETERS
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
# FUNCTIONS
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
# log(message)
#-------------------------------------------------------------------------
log()
{
$ECHO "$($DATE +%m-%d\ %H:%M) ${0}: ${1}"
}
#-------------------------------------------------------------------------
# reset_modem(void)
#-------------------------------------------------------------------------
reset_modem()
{
$LINRES $DEVICE
if [ $? -ne 0 ]; then
log "Can't reset $DEVICE line"
exit
fi
$VMCP -c"$AT_RESET" -wOK
$VMCP -c"$AT_LINE" -wOK
$VMCP -c"$AT_VINIT" -wOK
if [ $? -ne 0 ]; then
log "Modem is not responding"
exit
fi
}
#-------------------------------------------------------------------------
# play_beep(void)
#-------------------------------------------------------------------------
play_beep()
{
$VMCP -c"$AT_BEEP" -wOK
}
#-------------------------------------------------------------------------
# wait_rings(rings_number)
# Return after receiving rings_number RINGs and if $STOPFILE not found.
# Return also if $DOITNOW file is created ("ans now" command was ran).
#-------------------------------------------------------------------------
wait_rings()
{
local N
local ABORT
ABORT=true
while [ $ABORT = true ]; do
if [ -f $STOPFILE ]; then
log "Found ${STOPFILE}: waiting"
while [ -f $STOPFILE ]; do sleep 15; done
log "Stopfile removed"
fi
# Reset the modem here! If left in voice mode no RING reported.
reset_modem
$VMCP -l$LOCK -wRING -t0
if [ -f $DOITNOW ]; then
ABORT=false
elif [ ! -f $STOPFILE ]; then
N=1
ABORT=false
while [ $N -lt $1 -a $ABORT = false ]; do
N=$[$N + 1]
$VMCP -wRING -t5
if [ $? -ne 0 ]; then
ABORT=true
fi
if [ -f $DOITNOW ]; then
ABORT=false
N=$1
fi
done
fi
done
}
#-------------------------------------------------------------------------
# play_message(filename, dle_stop_string)
#-------------------------------------------------------------------------
play_message()
{
local N
$VMCP -c"$AT_VTX" -wCONNECT
$VMCP -e -q -t$TIMEOUT -i$1 -x$2
N=$?
if [ $N -eq 0 ]; then
# Terminate sending message gracefully.
$VMCP -i$DLE_ETX -wVCON
else
# Abort sending message: escaped char received.
$VMCP -i$DLE_CAN -wVCON
fi
return $N
}
#-------------------------------------------------------------------------
# record_message(filename, dle_stop_string)
#-------------------------------------------------------------------------
record_message()
{
local N
play_beep
# Record a message
$VMCP -c"$AT_VRX" -wCONNECT
$VMCP -e -t$TIMEOUT -o$1 -x$2
N=$?
$VMCP -cX -wVCON
play_beep
return $N
}
#-------------------------------------------------------------------------
# voice_call(filename)
# Receive a voice call and store it in ${VOICEDIR}/filename.${EXT}
#-------------------------------------------------------------------------
voice_call()
{
local FNAME
local N
FNAME=${VOICEDIR}/${1}.${EXT}
record_message $FNAME "bqd#sthc2e3"
N=$?
# Discard message if too short.
if [ $($WC --bytes < $FNAME) -lt $SHORTMSG ]; then
log "Short message discarded"
$RM -f $FNAME
fi
case $N in
5|6|7)
# Silence, off-hook or on-hook detected.
log "Message discarded"
$RM -f $FNAME
;;
8|9)
log "After voice receiving a Fax"
fax_call $1
;;
10|11)
log "After voice receiving a Data call"
data_call
;;
esac
}
#-------------------------------------------------------------------------
# get_passwd(filename)
# Ask for a password via DTMF (terminated with a "#") and store it in $1.
# Return 1 if password entered correctly.
#-------------------------------------------------------------------------
get_passwd()
{
local N
play_message $ASK_PASSWD "#"
play_beep
# Disable silence detection and listen for the password.
$VMCP -c"$AT_VSS0" -wOK
$VMCP -c"$AT_VRX" -wCONNECT
$VMCP -e -t20 -s$1 -x#b
N=$?
# Stop listening and enable silence detection.
$VMCP -cX -wVCON
$VMCP -c"$AT_VSS2" -wOK
# Append a new-line to the file.
$ECHO >> $1
return $N
}
#-------------------------------------------------------------------------
# change_password(void)
#-------------------------------------------------------------------------
change_password()
{
get_passwd $TMP1
if [ $? -eq 1 ]; then
get_passwd $TMP2
if [ $? -eq 1 ]; then
$DIFF -q $TMP1 $TMP2 > $NULL
if [ $? -ne 0 ]; then
log "Password not changed"
play_message $PASSWD_NOT_CH "#"
else
$MV -f $TMP1 $PASSWD
log "Password changed"
play_message $PASSWD_CH "#"
fi
fi
fi
$RM -f $TMP1
$RM -f $TMP2
}
#-------------------------------------------------------------------------
# remote_mode(void)
#-------------------------------------------------------------------------
remote_mode()
{
local N
local ABORT
local OPTIONS
get_passwd $TMP1
if [ $? -eq 1 ]; then
$DIFF -q $PASSWD $TMP1 > $NULL
if [ $? -ne 0 ]; then
play_message $WRONG_PASSWD "#"
log "Wrong passwd: $($CAT $TMP1)"
else
OPTIONS="1234#bd"
ABORT=false
while [ $ABORT = false ]; do
play_message $REMOTE_MENU $OPTIONS
N=$?
play_beep
if [ $N -eq 0 ]; then
# Listen for the command.
$VMCP -c"$AT_VRX" -wCONNECT
$VMCP -e -t20 -x$OPTIONS
N=$?
$VMCP -cX -wVCON
fi
case $N in
1)
log "Retrieving messages"
retrieve_messages
;;
2)
play_message $GREETING "#"
play_beep
;;
3)
log "Changing greeting"
change_greeting
;;
4)
log "Changing password"
change_password
;;
*)
log "Leaving remote menu"
ABORT=true
;;
esac
done
fi
fi
$RM -f $TMP1
}
#-------------------------------------------------------------------------
# retrieve_messages(void)
#-------------------------------------------------------------------------
retrieve_messages()
{
local I
local N
local LC
local FNAME
play_message $GET_MSGS_MNU "#"
# Create a list of messages to play.
$FIND $VOICEDIR -name "*.${EXT}" -type f -maxdepth 1 | $SORT > $TMP1
# Create a list of messages to delete.
$CP $NULL $TMP2
I=1
LC=$($WC -l < $TMP1)
while [ $I -le $LC ]; do
FNAME=$($SED -n ${I},${I}p $TMP1)
play_message $FNAME "649#"
case $? in
0|1)
# Next message.
I=$[$I + 1]
;;
2)
# Previous message.
if [ $I -gt 1 ]; then I=$[$I - 1]; fi
;;
3)
# Add filename to delete.
$ECHO $FNAME >> $TMP2
play_message $MSG_DELETED "#"
I=$[$I + 1]
;;
*)
# Exit from retrieving messages.
I=$[$LC + 1]
;;
esac
# Signal end of message.
play_beep
done
$RM -f $(cat $TMP2)
$RM -f $TMP1
$RM -f $TMP2
}
#-------------------------------------------------------------------------
# change_greeting(void)
#-------------------------------------------------------------------------
change_greeting()
{
play_message $CH_GREETING "#"
record_message $TMP1 "#9bqsd"
case $? in
1)
$MV -f $TMP1 $GREETING
log "Greeting changed"
;;
*)
$RM -f $TMP1
log "Greeting unchanged"
;;
esac
}
#-------------------------------------------------------------------------
# fax_call(filename)
# Receive a fax and store it in ${FAXDIR}/filename
#-------------------------------------------------------------------------
fax_call()
{
$VMCP -c"$AT_FAX" -wOK
exec $EFAX -d $DEVICE -v chewmainr -x "#${LOCK}" -or -i "E0X3" -i "+FCLASS=2;+FCR=1;\Q1" -c "1,5,0,2,0,0,0,0" -l "+39 55 291568" -z "&F" -r ${FAXDIR}/${1}
}
#-------------------------------------------------------------------------
# data_call(void)
#-------------------------------------------------------------------------
data_call()
{
$VMCP -c"$AT_DATA" -wOK
$VMCP -c"$AT_A" -t60 -wCONNECT
if [ $? -eq 0 ]; then
# exec $AGETTY -h -t60 38400,19200,9600,4800,2400,1200 $TTYS
exec $AGETTY $TTYS -t60 38400,19200,9600,4800,2400,1200
fi
}
#-------------------------------------------------------------------------
# do_stop(void)
#-------------------------------------------------------------------------
do_stop()
{
if [ -f $STOPFILE ]; then
log "Stopfile already exists: it will NOT answer"
else
if $TOUCH $STOPFILE; then
log "Stopfile created: it will NOT answer"
fi
fi
if [ -f $LOCK ]; then
if $KILL $($CAT $LOCK); then
log "$DEVICE unlocked"
else
log "Can't unlock $DEVICE"
fi
else
log "It seems that $DEVICE is not locked"
fi
}
#-------------------------------------------------------------------------
# do_start(void)
#-------------------------------------------------------------------------
do_start()
{
if $RM -f $STOPFILE; then
log "Stopfile removed: it WILL answer"
fi
}
#-------------------------------------------------------------------------
# do_play(void)
#-------------------------------------------------------------------------
do_play()
{
local FILE
local WASSTOP
if [ -f $STOPFILE ]; then WASSTOP=true; else WASSTOP=false; fi
do_stop
$ECHO "Playing $($LS ${VOICEDIR}/*.$EXT | $WC -w) message(s) in ${VOICEDIR}"
for FILE in ${VOICEDIR}/*.${EXT}; do
if [ -f $FILE ]; then
$LS -lG $FILE | $CUT -b21-
$VMCP -c"$AT_PLAY" -wVCON
$VMCP -c"$AT_VTX" -wCONNECT
$VMCP -k -e -q -t$TIMEOUT -i$FILE
$VMCP -i$DLE_ETX -wVCON
fi
done
$VMCP -c"$AT_H" -wOK
$VMCP -c"$AT_RESET" -wOK
if [ $WASSTOP = false ]; then do_start; fi
}
#-------------------------------------------------------------------------
# do_delete(void)
#-------------------------------------------------------------------------
do_delete()
{
local R
$ECHO -n "Remove all messages from ${VOICEDIR}? [N/y] "
read R
if [ "$R" = "y" -o "$R" = "Y" ]; then
$RM ${VOICEDIR}/*.${EXT}
fi
}
#-------------------------------------------------------------------------
# do_now(void)
#-------------------------------------------------------------------------
do_now()
{
if [ -f $STOPFILE ]; then
log "Can't answer now: ans is stopped"
else
if [ -f $LOCK ]; then
$TOUCH $DOITNOW
if $KILL $($CAT $LOCK); then
log "$DEVICE unlocked"
else
log "Can't unlock $DEVICE"
$RM -f $DOITNOW
fi
else
log "Can't answer now: $DEVICE is not locked by ans"
fi
fi
}
#-------------------------------------------------------------------------
# MAIN LOOP
#-------------------------------------------------------------------------
# Check for command line argument.
if [ $# -gt 0 ]; then
case $1 in
now)
do_now; exit
;;
stop)
do_stop; exit
;;
start)
do_start; exit
;;
play)
do_play; exit
;;
delete)
do_delete; exit
;;
*)
$ECHO "Answering machine script for voice modems."
$ECHO -e "Usage: $0 [OPTION]"
$ECHO -e "\tnow\t\tforce a running process to answer immediately"
$ECHO -e "\tplay\t\tplay received messages"
$ECHO -e "\tdelete\t\tdelete received messages"
$ECHO -e "\tstop\t\tprevent a running process from answering the phone"
$ECHO -e "\tstart\t\tresume a running process answering the phone"
$ECHO -e "Without option, start waiting for a call."
exit
;;
esac
fi
if [ ! -d $VOICEDIR -o ! -w $VOICEDIR ]; then
log "$VOICEDIR: No such directory or permission denied"
exit
fi
if [ ! -d $FAXDIR -o ! -w $FAXDIR ]; then
log "$FAXDIR: No such directory or permission denied"
exit
fi
if [ -f $LOCK ]; then
log "Device $DEVICE locked by PID $($CAT $LOCK)"
exit
fi
umask $UMASK
log "Resetting the modem and waiting for $WAITRINGS rings"
wait_rings $WAITRINGS
# Generate a filename for the incoming message.
MSGNAME=$($DATE +%y%m%d%H%M%S)
# Answer the call.
$VMCP -c"$AT_VOICE" -wOK
$VMCP -c"$AT_A" -wVCON
# Play the greeting only if needed.
if [ -f $DOITNOW ]; then
$RM -f $DOITNOW
N=0
else
play_message $GREETING "c2e3#ht"
N=$?
fi
# Discriminate call...
case $N in
0)
log "Receiving a Voice call"
voice_call $MSGNAME
;;
1|2)
log "Receiving a Fax"
fax_call $MSGNAME
;;
3|4)
log "Receiving a Data call"
data_call
;;
5)
log "Entering remote mode"
remote_mode
;;
6|7)
log "Answer aborted"
;;
esac
$VMCP -c"$AT_H" -wOK