Metropoli BBS
VIEWER: ans MODE: TEXT (ASCII)
#!/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
[ RETURN TO DIRECTORY ]