Monday, March 4, 2013

Asterisk or FreePBX call alert script (e.g. for sudden spike in international calls) that runs independently or via Nagios (Nagios compatible)

Lets face it, some of us may have had our share of FreePBX/Asterisk compromises and abusers almost always use the facility to make international calls to exotic numbers. The bill at the end of the month is already too late and there’s significant monetary damage. While its good to know in realtime of what’s going on, its of course best to prevent anything to compromise your server. see this guide here to get you started.

Here’s a small script we wrote that works for me to check as often as cronjob/Nagios allows it to, based on the parameters you set and report back via email if the script is triggered.

This script here does the following;

  • It checks the CDR for n minutes of past records also set by flags
  • Uses filters based on the dst column on your CDR to match that you specify during execution (the parameters), this can be prefixes or whole numbers, and run multiple instances. 
  • You can set the flag to check the prefix and the number of digits which is same or greater so that you won’t catch local calls, normally international calls have higher number of called digits, i.e. > 10
  • To automatically check or do it almost realtime, you can use cron on your server locally (It can also work with Nagios too, however, this guide does not cover configuring on Nagios, set NAGIOSMODE=YES). If you set Nagiosmode, it will not independently send out email and instead your Nagios server will decide what to do according to what you’ve set it to.
  • Be sure to change SYSADMINEMAIL, FROMUSER,SERVER,PORT,DATABASE,TABLE,USER,PASS in the script relevant to your setup

Getting started:

  1. Copy paste this script below in /usr/bin/checkintl.sh (or any location of your choice)
  2. Modify the parameters as described below
  3. Make it executable (chmod +x /usr/bin/checkintl.sh)
  4. Put this script in a cronjob if you wish to automate checking (crontab –e), e.g. like this     */15 * * * * /usr/bin/checkintl.sh -w 5 -c 10 -i 30 -p 00:6,900:6"
    • Above cronjob does the checks for every 15 minutes 30 records in CDR and warns on 5 critical alerts on 10 for pattern 00, with length greater than or equals 6 numbers and for pattern 900 with length greater than or equals 6 numbers
  5. Test manually. You surely can run this manually and try to invoke the trigger by making n number of calls and you should get an email alert based on the email address you specified
  6. This script requires a MySQL CDR for Asterisk (therefore making it perfect for use with FreePBX, out of the box)
  7. IMPORTANT: THIS SCRIPT CHECKS AFTER ASTERISK WRITES TO CDR AND ALSO WHILE IN CALL COMBINED, SO CHECK BOTH PLACES

Set these below before running the script

  • The emailtoSYSADMINEMAIL, emailFROMUSER,SERVERip,serverPORT,cdrDATABASE,cdrTABLE,dbUSER,dbPASS
  • If using Nagios, just set the flag NAGIOSMODE=YES
  • Everything copied from below must be in a single line, no line breaks

Here’s the script, copy paste this as per item 1. getting started guide.

###PASTE START###

#!/bin/bash
RELEASE="Version 2"
AUTHOR="sanjay@astiostech.com"
PROGNAME="checkintl.sh"
#
# PRE-REQ
# – BASH 4.1.X or higher
# – FREEPBX and MYSQL of course
# - ASTERISK WITH CDR SUPPORT
# - ACCESS TO ASTERISKCDR DB
# – CLI tools like mail, awk, tr, curl,grep,mysql bin

# - ABLE TO SEND EMAIL OUT USING #mail with exim etc..
#
# ChangeLog
# - FIRST RELEASE
# - 1.1 Minor changes encapsulating output for compatibility
# - 2.0 Updated checking algorithm
#
# Place a cronjob, e.g. check every 5 minutes
# */15 * * * * /<locationofscript>/checkintl.sh
#
# NOTHING TO CHANGE HERE
STATE_OK=0
STATE_WARNING=1
STATE_CRITICAL=2
STATE_UNKNOWN=3
USERDATEFORMAT=`date +%d-%m-%Y_%H:%M:%S`
#
########MODIFY HERE
########
NAGIOSMODE=NO
SYSADMINEMAIL=youradmin@yourdomain.com #add more separated by commas
FROMUSER=youruser@yourdomain.com
EMAILSUBJECT="CALL ALERTER – By Astiostech"
server="localhost"
port="3306"
database="asteriskcdrdb"
table="cdr"
user="<mysqluser>"
pass="<mysqlpass>"
########END MODIFY########
#
#
##FUNCTIONS
function sendoutmail()
{
( echo [$USERDATEFORMAT] - $2 ) | mail -aFrom:$FROMUSER -s "$EMAILSUBJECT" $SYSADMINEMAIL
return
}
#
print_release() {
    echo "$RELEASE $AUTHOR"
}
#
print_usage() {
    clear
    echo ""
    echo "$PROGNAME $RELEASE - Checks international calls"
    echo ""
    echo "Usage: checkintl.sh [flags]"
    echo ""
    echo "e.g  : ./checkintl.sh -w 5 -c 10 -i 30 -p 00:6,900:6"
    echo "Where it warns at 5 intl calls, and makes a critcal alert 10 calls at every 30 minutes "
    echo "of past CDR record for prefixes 00 and 900 combined total of equals to or higher than 6 digits"
    echo ""
    echo "Flags:"
    echo "  -w <number> : Warning value for number of international calls."
    echo "  -c <number> : Critical value for number of international calls"
    echo "  -i <number> : How many minutes of CDR data to poll to evaluate"
    echo "  -p <prefix:length,prefix:length,prefix:length> : Comma separated prefixes with IDD, e.g. 900:5,800:5,00:5. Length value is equals or higher. Default is 5 or higher chars"
    echo "  -h  Show this page"
    echo ""
    echo "Usage: $PROGNAME --help"
    echo ""
}
print_help() {
    print_usage
        echo ""
        echo "This plugin will check international calls made on Asterisk CDR"
        echo ""
    exit 0
}

# Parse parameters
while [ $# -gt 0 ]; do
    case "$1" in
        -h | --help)
            print_help
            exit $STATE_OK
            ;;
        -v | --version)
                print_release
                exit $STATE_OK
                ;;
        -w | --warning)
                shift
                WARNING_THRESHOLD=$1
                ;;
        -c | --critical)
               shift
                CRITICAL_THRESHOLD=$1
                ;;
        -i | --interval)
               shift
               INTERVAL=$1
                ;;
        -p | --prefix)
               shift
               PREFIXES=$1
                ;;
        *)  echo "Unknown argument: $1"
            print_usage
            exit $STATE_UNKNOWN
            ;;
        esac
shift
done
#
if  [[ "$WARNING_THRESHOLD" == "" ]]; then
    echo "Warning value not set, quitting.."
    exit $STATE_OK
fi
if  [[ "$CRITICAL_THRESHOLD" == "" ]]; then
    echo "Critical value not set, quitting.."
    exit $STATE_OK
fi
if  [[ "$INTERVAL" == "" ]]; then
    echo "Interval value not set, quitting.."
    exit $STATE_OK
fi
if  [[ "$PREFIXES" == "" ]]; then
    echo "You must define at least one prefix, quitting.."
    exit $STATE_OK
fi
#
# SET VARIABLES
warning_value="$WARNING_THRESHOLD"
critical_value="$CRITICAL_THRESHOLD"
interval="$INTERVAL"
PREFIX="$PREFIXES"
MYSQLBIN=`which mysql`
MYAST=`which asterisk`
#
# CONSTANTS
todate=`/bin/date`
unixtime=`/bin/date --date="$todate" +%s`
filename=mycdrlogforintl.$unixtime.txt
#
# DB CONNECTION CHECKER
checkdb=`$MYSQLBIN -b -n -N -s -h $server -P $port -u $user -p$pass -D $database -e "show databases;" | grep -c $database`
# START CHECKING CALLS
IFS=$OLDIFS
IFS=,
if  [[ $checkdb -lt 1 ]]; then
    echo "Error connecting to MySQL CDR DB"
    if [[ "$NAGIOSMODE" == "NO" ]]; then
        sendoutmail -f "Error connecting to MySQL CDR DB"
    fi
    exit $STATE_UNKNOWN
else
    for i in $PREFIX; do
        i=`echo $i | awk -F':' '{print $1}'`
        length=`echo $i | awk -F':' '{print $2}'`
        if [[ $length == 0 ]]; then
            length=5
        fi
        rawdata=`$MYSQLBIN -b -n -N -s -h $server -P $port -u $user -p$pass -D $database -e "SELECT dst FROM $table WHERE cdr.calldate > now() - INTERVAL $interval MINUTE and CHAR_LENGTH(dst) >= '$length';" | grep -o '[0-9]*' | grep -e '\<'$i'.*\>' | tr -d ' '`
        subtotal=`echo $rawdata | awk 'NF > 0' | wc -l`
        echo $rawdata >> /tmp/$filename
        ((totalcalls+=$subtotal))
    done
    for n in $PREFIX; do
        n=`echo $n | awk -F':' '{print $1}'`
        length=`echo $n | awk -F':' '{print $2}'`
        if [[ $length == 0 ]]; then
            length=5
        fi
        rawdata2=`$MYAST -rx "core show channels concise" |grep Outgoing | grep -e '\<'$n'.*\>' `
        if [[ "$rawdata2" != "" ]]; then
            subtotal=`echo $rawdata2 | awk 'NF > 0' | wc -l`
            echo $rawdata2 >> /tmp/$filename       
            ((totalcalls+=$subtotal))
        fi   
    done
IFS=$OLDIFS
fi
csved=`cat /tmp/$filename | tr -d ' ' | awk 'NF > 0' | tr "\n" ","`
#
# Now we start reporting to Nagios/sendmail if required
#
if [[ "$totalcalls" == "0" ]]; then
    echo "OK:$totalcalls |intl_calls=$totalcalls;$warning_value;$critical_value"
    exit $STATE_OK
elif [[ "$totalcalls" -ge "$critical_value" ]]; then
    echo "CRITICAL:$totalcalls LISTING:[START] $csved[END]|intl_calls=$totalcalls;$warning_value;$critical_value"
    if [[ "$NAGIOSMODE" == "NO" ]]; then
        sendoutmail -f "CRITICAL:$totalcalls LISTING:[START]$csved[END] | Critical threshold set: $critical_value"
    fi
    exit $STATE_CRITICAL
elif [[ "$totalcalls" -ge "$warning_value" ]]; then
    echo "WARNING:$totalcalls LISTING:[START] $csved[END]|intl_calls=$totalcalls;$warning_value;$critical_value"
    if [[ "$NAGIOSMODE" == "NO" ]]; then
        sendoutmail -f "WARNING:$totalcalls LISTING:[START] $csved[END] | Warning threshold set: $warning_value"
    fi
    exit $STATE_WARNING
else
    echo "TOTAL:$totalcalls LISTING:[START] $csved[END]|intl_calls=$totalcalls;$warning_value;$critical_value"
    exit $STATE_OK
fi

###PASTE END###