#!/usr/bin/bash

# Created: Mar 30, 2020 by Jon Tulk
# Last edited: May 11, 2021 by jlc

# Server Hangup Extension:
# Allow user to call back in a limited amount of time and be let through.
# Script is not called if caller number or name is in ncidd.whitelist.
#
# Check if the number is in the greylist file and that the expiration
# time has not passed.  If so, return OK to allow the call through.
#
# For all other calls, add the number to the greylist file with an
# expiration time that is the current time plus the time limit in
# minutes ($WAITTIME).  Then hang up the call after (optionally)
# playing a recording.  
#
# If used, the default recording is Callback.rmd.  It instructs
# the caller to call back within a time limit but does not give
# the specific time limit.
#
# Each line in the greylist file contains a number and name as received
# in the NMBR and NAME fields plus an expire time.  The file uses the
# same format as the data line passed from NCID.  The file is
# managed by this script and should not be edited manually.
#
# If EXTENDWAIT is set to "Y" (the default), the expire time will be
# updated when a call is accepted.  This keeps a regular caller in
# the greylist until time expires or they are black/white listed by
# the admin.
#
# Script is not called if caller number or name is in ncidd.whitelist.
#
# Only hangup modes 1 and 3 are supported.
#
# Make sure you 'set hupmode = 1' or 'set hupmode = 3' in ncidd.conf.
# If 'hupmode == 2', script exits with "abort" and will allow the call.
#
# This script will create the greylist file if it does not already
# exist.  The script must have permissions to create files in the
# directory that will contain the greylist file.  If the file already
# exists, the script must have read and write permissions for the file.
#
# Be sure this script has the execute permission set and is in
# /usr/share/ncid/extensions or in /usr/local/share/ncid/extensions
#
# RECORDING is set to the greylist hangup recording name.

RECORDING="Callback.rmd"
GREYLISTDIR="/var/cache/ncid"
GREYLIST="$GREYLISTDIR/ncidd.greylist"
WAITTIME=7200   # Default wait time = 7200 (5 days)
EXTENDWAIT="Y"  # set to "Y" to reset wait time when call accepted

########################
# Function Definitions #
########################

usage() {
   cat | more <<EOF

Usage: $script [options] <string>

       Server hangup extension to allow callers to get through if they
       call back within a limited time.
       
       Input: The server passes one <string> to this script:

              *DATE*<mmddyyyy>\\
              *TIME*<hhmm>\\
              *LINE*<lineid>\\
              *NMBR*<number>\\
              *MODE*<1|2|3>\\
              *FNMBR*<formatted number>\\
              *NTYPE*<type of device>\\
              *CTRY*<country code>\\
              *LOCA*<location>\\
              *CARI*<carrier>\\
              *NAME*<name>*

              <number> and <name> have already been changed to aliases 
              if applicable.

              When testing, just use the input fields needed. This 
              example script only requires the NMBR field. The <string> 
              must be enclosed in double quotes.

              For example:
                  $script "*NMBR*4075551212*NAME*Freddie*MODE*3"
                  $script "*NMBR*4075551213"
                                                      
       Output: hupmode value received and description
               if MODE == 2:
                    FAX hangup is not supported
                    abort
                    OK
               if greylist doesn't exist and cannot be created:
                    Cannot create greylist file ($GREYLIST)
                    abort
                    OK
               if greylist not read/write accessible:
                    Cannot read/write greylist file ($GREYLIST)
                    abort
                    OK
               if hangup required and MODE == 1:
                    HangupReason: added to greylist
                    hangup
               if hangup required and MODE == 3:
                    Recording: <file name or full path>
                    HangupReason: added to greylist
                    hangup
               if hangup not required:
                    OK
       
Options are only used for manual testing and are NEVER SENT by the server:

       [-h] [-v]

       -h = show this help
       
       -v = turns verbose on and sends additional data to STDOUT for 
            troubleshooting

EOF
exit 1
}

CreateGreylist() {
    cat <<EOF > $GREYLIST
# ncidd.greylist file

# This file is used by the ncidd hangup-greylist extension.  It
# contains telephone numbers that are permitted to call within
# a specific time period to avoid an automatic hangup.

###############################
# Definitions and Line format #
###############################

# For CID calls, the caller number in /var/log/cidcall.log is
# compared to each entry in this file to determine if the call
# is allowed.
#
# The hupmode 1 or hupmode 3 mode must be set in ncidd.conf.
#
# There are three types of lines: blank, comment and entry.
# Blank lines and comment lines are ignored.
# Entry lines are processed.
#
# Entry line format: 
#  *NMBR*<number>*NAME*<name>*EXPIRES*<seconds since 1970-01-01 00:00:00 UTC>

EOF
}

DoAbort() {
    echo $1
    echo abort
    echo OK
    exit 0
}

###############################
# End of function definitions #
###############################

script=`basename $0`

# Options on command line
while getopts :hv opt ; do
    case $opt in
        h) usage;;
        v) verbose=1;;
        :) echo "Option -$OPTARG requires an argument."; usage;;
        *) echo "Invalid option: -$OPTARG"; usage;;
    esac
done
shift $((OPTIND-1)) # skip over command line args (if any)

# All passed fields from the server are parsed below, use only fields needed.
tmp=${1#*NAME?}; NAME=${tmp%%\**}
tmp=${1#*NMBR?}; NMBR=${tmp%%\**}
tmp=${1#*LINE?}; LINE=${tmp%%\**}
tmp=${1#*DATE?}; DATE=${tmp%%\**}
tmp=${1#*TIME?}; TIME=${tmp%%\**}
tmp=${1#*MODE?}; MODE=${tmp%%\**}

: ${NMBR:=_nmbr_} # default value if null
: ${NAME:=_name_} # default value if null
: ${MODE:=3}      # default value if null

if [ -n "$verbose" ]
then
    echo "NAME:  $NAME"
    echo "NMBR:  $NMBR"
    echo "LINE:  $LINE"
    echo "DATE:  $DATE"
    echo "TIME:  $TIME"
    echo "MODE:  $MODE"

    echo -n "received HUPMODE $MODE - "
    case $MODE in
        1) echo "Normal hangup" ;;
        2) echo "FAX hangup" ;;
        3) echo "VOICE hangup" ;;
        *) echo "unknown MODE" ;;
    esac
fi

[ $MODE = "2" ] && DoAbort "FAX hangup not supported."

if [ ! -f $GREYLIST  ]
then
    [ -d $GREYLISTDIR ] || mkdir -p $GREYLISTDIR >& /dev/null
    CreateGreylist >& /dev/null
    [ $? -ne 0 ] && DoAbort "Cannot create greylist file ($GREYLIST)."
fi

[ ! -r $GREYLIST -o ! -w $GREYLIST ] && \
    DoAbort "Cannot read/write greylist file ($GREYLIST)."

# Clean out expired entries from the greylist file.
> $GREYLIST.new

NOW=`date +%s`

while read ENTRY
do
    [[ $ENTRY = \#* ]] || [ -z "$ENTRY" ] && \
    {
            echo "$ENTRY" >> $GREYLIST.new
            continue
    }

    tmp=${ENTRY#*EXPIRES?}; EXPIRY=${tmp%%\**}
    if [ -n "$EXPIRY" ]
    then
        if [ $NOW -lt $EXPIRY ]
        then
            echo $ENTRY >> $GREYLIST.new
        fi
    fi
done < $GREYLIST

mv $GREYLIST.new $GREYLIST
[ $? -ne 0 ] && DoAbort "Failed to rename $GREYLIST.new to $GREYLIST"

# Now look to see if this number is in the greylist file.
ENTRY=`sed -n "/NMBR\*$NMBR\*/p" $GREYLIST`
if [ -z "$ENTRY" ]
then
    # Number not found in greylist file
    if [ -n "$verbose" ]
    then
        echo "Adding new greylist entry: $NMBR"
    fi

    # Add number to greylist file along with Current Time + Wait Time
    EXPIRY=$((`date +%s` + $WAITTIME * 60))
    echo "*NMBR*$NMBR*NAME*$NAME*EXPIRES*$EXPIRY" >> $GREYLIST

    # Exit and cause a hangup after playing the recording.
    if [ $MODE = "3" ]
    then
        echo "Recording: $RECORDING"
    fi
    echo "HangupReason: added to greylist"
    echo "hangup"
    exit 0
else
    # Extend the time limit if EXTENDWAIT is "Y"
    if [ -n "$EXTENDWAIT" -a "$EXTENDWAIT" = "Y" ]
    then
        # Remove existing entry
        sed -i.bak "/NMBR\*$NMBR\*/d" $GREYLIST && rm $GREYLIST.bak
        if [ $? -ne 0 ]
        then
            # If sed returns an error, call is still accepted but
            # expiry time is not extended.
            echo "Cannot extend expiration time"
        else
            # Add new entry with extended expiry time
            EXPIRY=$((`date +%s` + $WAITTIME * 60))
            echo "*NMBR*$NMBR*NAME*$NAME*EXPIRES*$EXPIRY" >> $GREYLIST
        fi
    fi

    # Accept the call.
    echo "HangupReason: greylist call accepted"
    echo "OK"
    exit 0
fi
