#!/bin/bash

LOCAL_SERVER=196.25.1.1
INTERNATIONAL_SERVER=196.23.168.147
LOCAL_DNS_ENDPOINT=telkom.co.za
INTERNATIONAL_DNS_ENDPOINT=google.com
TODAY=$(date --rfc-3339='date')
FXO_DOWN_THRESHOLD=200

#----------------------------------------------------------------------------

printf "Far South Networks (Pty) Ltd\n"
printf "Starting Com.X audit (revision 5259)...\n\n"

NUMERRORS=0
NUMWARNINGS=0

inc_errors() {
    NUMERRORS=$(( $NUMERRORS + 1 )) 
}

inc_warnings() {
    NUMWARNINGS=$(( $NUMWARNINGS + 1 )) 
}

show_error() {
  printf "[ERROR] %s\n" "$1"
  inc_errors
}

show_warning() {
  printf "[WARN] %s\n" "$1"
  inc_warnings
}

show_info() {
  printf "[INFO] %s\n" "$1"
}

show_debug() {
  printf "[DEBUG] %s\n" "$1"
}

#----------------------------------------------------------------------------

check_uname(){
    UNAME=$(uname -r)
    if [ "2.6.24-27-generic" != "$UNAME" ]; then
        show_error "System linux version is not 2.6.24-27-generic but rather $UNAME"
    fi
}

find_package_in_archive(){
    RESULT="UNKNOWN"
    PACKAGES=$(ls $1 | grep $2 | tr -s ' ')
    for f in $(echo $PACKAGES); do
        PACKAGENAME=$(echo $f | cut -d'_' -f1)
        if [ "$PACKAGENAME" == "$2" ]; then
            RESULT=$f
        fi
    done
}

select_newest_file() {
  CURRENTDIR=$(pwd)
  cd $1
  NEWESTFILE=$(ls -t $2 | head -n 1)
  cd $CURRENTDIR
}

sw_warranty_audit_package() {
TMPXTRACT=/tmp/hsdkfjhruifeirfgjskjdsfuigsdfugifshkjsdhkjgsh743y7kjrghi78yt1
rm -rf $TMPXTRACT
mkdir -p $TMPXTRACT
ARCHIVE=/var/cache/apt/archives
PACKAGE=$1
show_info "Examining $PACKAGE"
find_package_in_archive $ARCHIVE $PACKAGE
DEBFILE=$RESULT


if [ "$DEBFILE" == "" -o "$DEBFILE" == "UNKNOWN" ]; then
    show_warning "sw_warranty_audit_package: Could not find original package $PACKAGE"
else
    select_newest_file $ARCHIVE "$DEBFILE"
    DEBFILE=$NEWESTFILE

    #Check FSN packages signatures
    if [ "$2" == "sign" ]; then
        #echo "control: dpkg --control $ARCHIVE/$DEBFILE $TMPXTRACT/"
        dpkg --control $ARCHIVE/$DEBFILE $TMPXTRACT/
        #more $TMPXTRACT/control
        FSN=$(grep farsouthnet $TMPXTRACT/control)
        if [ "" == "$FSN" ]; then
            show_error "$PACKAGE is not a valid Far South Networks package"
        fi
    fi

    PACKAGES=$(dpkg -l | grep $PACKAGE | tr -s ' ' | sed 's/ /=/g')
    for f in $(echo $PACKAGES); do
        PACKAGENAME=$(echo $f | cut -d'=' -f2)
        if [ "$PACKAGENAME" == "$PACKAGE" ]; then
            INSTALLEDVER=$(echo $f | cut -d"=" -f3)
            CACHEVER=$(echo $DEBFILE | cut -d "_" -f2 | sed 's/%3a/:/g')
            if [ "$CACHEVER" != "$INSTALLEDVER" ]; then
                show_error "$PACKAGE installed version ($INSTALLEDVER) differs from cache ($CACHEVER)"
            fi
        fi
    done

fi
rm -rf $TMPXTRACT
}

sw_warranty_audit_package_file() {
TMPXTRACT=/tmp/hsdkfjhruifeirfgjskjdsfuigsdfugifshkjsdhkjgsh743y7kjrghi78yt
rm -rf $TMPXTRACT
mkdir -p $TMPXTRACT
ARCHIVE=/var/cache/apt/archives
PACKAGE=$1
FILE=$2
#show_info "Examining $PACKAGE : $FILE"
DEBFILE=$(ls $ARCHIVE | grep $PACKAGE)

select_newest_file $ARCHIVE "$DEBFILE"
DEBFILE=$NEWESTFILE
echo "Newest is $DEBFILE"

if [ "$DEBFILE" == "" ]; then
    show_error "sw_warranty_audit_package_file: Could not find original package $PACKAGE"
else
    dpkg -x $ARCHIVE/$DEBFILE $TMPXTRACT/
    MD5SUM=$(md5sum $TMPXTRACT/$FILE | cut -d " " -f1)
    MD5SUM2=$(md5sum $FILE | cut -d " " -f1)
    if [ "$MD5SUM" != "$MD5SUM2" ]; then
        show_error "sw_warranty_audit_package_file: $PACKAGE : $FILE modified [$MD5SUM / $MD5SUM2]"
    fi
fi
}

sw_warranty_audit() {
    #show_info "Auditing configuration"
    sw_warranty_audit_package_file asterisk-config-freepbx /etc/asterisk/extensions.conf
    sw_warranty_audit_package_file asterisk-config-freepbx /etc/asterisk/misdn.conf
    sw_warranty_audit_package_file asterisk-config-freepbx /etc/asterisk/zapata.conf
    sw_warranty_audit_package_file asterisk-config-freepbx /etc/asterisk/iax.conf
    sw_warranty_audit_package_file asterisk-config-freepbx /etc/asterisk/sip.conf
    sw_warranty_audit_package_file asterisk-config-freepbx /etc/asterisk/indications.conf

    #show_info "Auditing Freepbx"
    sw_warranty_audit_package asterisk-config-freepbx sign
    sw_warranty_audit_package asterisk-sounds-moh-freepbx sign
    sw_warranty_audit_package freepbx-comma-gui sign
    sw_warranty_audit_package freepbx-common-comma-gui sign
    sw_warranty_audit_package freepbx-modules-comma-gui sign
    sw_warranty_audit_package freepbx-mysql sign
    sw_warranty_audit_package freepbx-panel sign
    sw_warranty_audit_package freepbx-sounds-extra sign

    #show_info "Auditing Asterisk"
    # Examine asterisk
    ASTVER=$(dpkg -l | grep asteriskcomma | grep "Open Source" | grep "1:1.8")
    if [ "$ASTVER" != "" ]; then
        #show_info "Detected asterisk 1.8"
        # 1.8
        sw_warranty_audit_package asterisk-dahdi
        sw_warranty_audit_package asterisk-misdn
        sw_warranty_audit_package asterisk-mp3
        sw_warranty_audit_package asterisk-mysql
        sw_warranty_audit_package asterisk-sounds-extra
        sw_warranty_audit_package asterisk-sounds-main
        sw_warranty_audit_package asterisk-sounds-moh-freepbx
        sw_warranty_audit_package asterisk-voicemail
        sw_warranty_audit_package asteriskcomma-1.8 sign
        sw_warranty_audit_package asteriskcomma-sounds-main sign
        sw_warranty_audit_package asteriskcomma1.8-g729-eval sign
    else
        # 1.4
        sw_warranty_audit_package asterisk
        sw_warranty_audit_package asterisk-mysql
        sw_warranty_audit_package asterisk-sounds-extra
        sw_warranty_audit_package asterisk-sounds-main
        sw_warranty_audit_package asterisk-sounds-moh-freepbx
        sw_warranty_audit_package asteriskcomma sign
        sw_warranty_audit_package asteriskcomma-sounds-main sign
        sw_warranty_audit_package asteriskcomma-g729-eval sign
    fi

    #show_info "Auditing FSN packages"
    sw_warranty_audit_package comma-config
    sw_warranty_audit_package comma-gui
    sw_warranty_audit_package comma-gui-libs
    sw_warranty_audit_package comma-recovery
    sw_warranty_audit_package comma-selftest
    sw_warranty_audit_package comma-ups
    sw_warranty_audit_package commaimg 
    sw_warranty_audit_package commaman3000
    sw_warranty_audit_package commamgr

    #show_info "Auditing drivers"
    sw_warranty_audit_package misdn-modules
    sw_warranty_audit_package dahdi-modules
    sw_warranty_audit_package misdn
    sw_warranty_audit_package dahdi
}

hw_timing() {
  NUMITAS=$(grep macaddr /etc/comma.conf | wc -l)
  NUMTIMING=$(grep sync /etc/comma.conf | wc -l)
  if [ $NUMTIMING -lt $NUMITAS ]; then
    show_warning "Of $NUMITAS iTAs, only $NUMTIMING have timing other than local. This could cause signalling errors due to electrical timing jitter / drift. Please ensure that timing is correctly configured on the GUI (Start - Configuration - Hardware panel - Edit the iTA(s) in question and set the timing mode)"
  fi
}

hw_earthing() {
  FXODOWN=$(grep $TODAY /var/log/comma/comma.log | grep "is DOWN" | grep FXO | wc -l)
  if [ $FXODOWN -gt $FXO_DOWN_THRESHOLD ]; then
    show_error "$FXODOWN FXO port DOWN events were reported today. This probably indicates an earthing problem of some sort or excessive FXO card restarts."
  fi
}

hw_disk() {
  DRDY=$(grep DRDY /var/log/syslog | wc -l)
  if [ $DRDY -gt 0 ]; then
    show_warning "DRDY errors reported in /var/log/syslog. This could be indicative of an impending hard drive / solid state drive failure"
  fi
}

net_nslookup() {
  LOCAL=$(nslookup $LOCAL_DNS_ENDPOINT | grep "can't find" | wc -l)
  INTERNATIONAL=$(nslookup $INTERNATIONAL_DNS_ENDPOINT | grep "can't find" | wc -l)
  if [ "1" == "$LOCAL" ]; then
    show_warning "Local DNS lookup seems to be unavailable. Please check the DNS server configuration in the GUI (Start - Configuration - Global Settings - Primary and secondary DNS. Please also ensure that Primary DNS is the adress of your local router and that the DNS server addresses are correct)"
  fi
  if [ "1" == "$INTERNATIONAL" ]; then
    show_warning "Internationsl DNS lookup seems to be unavailable. Please check the DNS server configuration in the GUI (Start - Configuration - Global Settings - Primary and secondary DNS. Please also ensure that Primary DNS is the adress of your local router and that the DNS server addresses are correct)"
  fi
}

net_ping() {
  INTERNATIONAL=$(ping -c 1 $INTERNATIONAL_SERVER | grep "bytes from" | wc -l)
  LOCAL=$(ping -c 1 $LOCAL_SERVER | grep "bytes from" | wc -l)
  if [ "1" != "$LOCAL" ]; then
    show_warning "Local internet connectivity seems to be down"
  fi
  if [ "1" != "$INTERNATIONAL" ]; then
    show_warning "International internet connectivity seems to be down"
  fi
}

net_gw() {
  NUMGW=$(ip route | grep default | wc -l)
  if [ "$NUMGW" == "0" ]; then
    show_warning "No default gateway is configured on any of the interfaces"
  else
    if [ $NUMGW -gt 1 ]; then
      show_error "More than one default gateway has been configured:"
      ip route | grep default
    fi
  fi
}

net_dns() {
  DNS=$(grep dns-nameservers /etc/network/interfaces | cut -d' ' -f2)
  if [ "$DNS" == "" ]; then
    show_warning "No DNS servers configured"
  else
    # Check if traceroute is installed
    PRESENT=$(dpkg -l | grep traceroute | grep -v tiny | grep ii)
    if [ "" == "$PRESENT" ]; then
        show_warning "traceroute is not installed. Could not determine DNS server validity. Please install using 'sudo aptitude install traceroute'"
    else
        NUM=$(traceroute $DNS | grep -v "traceroute to" | grep "$DNS" | cut -d' ' -f2)
        if [ "$NUM" != "1" ]; then
            show_warning "The primary DNS $f is not on the local LAN. Should access to this DNS server be lost, system services could lock up. Please configure the primary DNS to your router address and the secondary DNS to $f from the GUI (Start - Configuration - Setup - Global settings)"
        fi
    fi
  fi
}

net_internal() {
  X1=$(hostname | grep FSN1X | wc -l)
  if [ "$X1" == "1" ]; then
    CORRECT=$(ifconfig eth3 | grep 192.168.103.1 | wc -l)
    if [ "$CORRECT" != "1" ]; then
      show_error "The internal Com.X1 ethernet LAN (eth3) does not have the default IP (192.168.103.1). This interface should not be reconfigured. The internal iTA may stop working."
    fi
  fi
}

net_overlap() {
  NETTMP=/tmp/fsdhfkjshgdfkjbsdbdaejkhaejkdkjlfnetoverflow
  NETTMP2=/tmp/fsdhfkjshgdfkjbsdbdaejkhaejkdkjlfnetoverflow2
  rm -f $NETTMP
  rm -f $NETTMP2
  for f in $(ifconfig | sed "s/ /_/g" | grep "inet_addr");do
    FRONT=$(echo $f | cut -d":" -f2 | cut -d "." -f1,2,3)
    BACK=$(echo $f | cut -d":" -f4)
    printf "%s.x:%s\n" $FRONT $BACK>> $NETTMP
  done
  cat $NETTMP | uniq > $NETTMP2
  OUTPUT=$(diff $NETTMP $NETTMP2)
  NUM=$(echo $OUTPUT | wc -l)
  if [ "" = "$OUTPUT" ]; then
    NUM=0
  fi
  if [ "$NUM" != "" -a $NUM -gt 0 ]; then
    show_error "$NUM overlapping IP subnets found: $OUTPUT"
  fi
}

net_confusion() {
 CONFUSIONC=$(grep "but seeing messages on" /var/log/commagui/other.log | wc -l)
 CONFUSION=$(grep "but seeing messages on" /var/log/commagui/other.log)
  if [ $CONFUSIONC -gt 0 ]; then
    show_warning "Ethernet confusion detected: $CONFUSION"
  fi
}

ast_realtime() {
  DEFAULTS=$(grep "AST_REALTIME" /etc/default/asterisk | sed 's/ //g' | grep "AST_REALTIME=no" | grep -v "#AST" | wc -l)
  INITD=$(grep "AST_REALTIME" /etc/init.d/asterisk | sed 's/ //g' | grep "AST_REALTIME=yes" | grep -v "#AST" | wc -l)
  if [ "1" != "$DEFAULTS" ]; then
    show_warning "AST_REALTIME is not set to 'no' as recommended in /etc/default/asterisk This could cause Com.X lock-ups under certain invalid configuration of invalid call flow conditions"
  fi
  if [ "1" == "$INITD" ]; then
    show_warning "AST_REALTIME is not set to 'no' as recommended in /etc/init.d/asterisk This could cause Com.X lock-ups under certain invalid configuration of invalid call flow conditions"
  fi
}

dialplan_queues() {
  for f in $(echo "select extension, dest from queues_config" | mysql -u root --skip-column-names asterisk | sed 's/\t/_/g' | cut -d'_' -f1); do
    NUM=$(echo "select dest from queues_config where extension like $f" | mysql -u root --skip-column-names asterisk| grep $f | wc -l)
    if [ "$NUM" == "1" ]; then
        show_warning "Queue $f routes to itself. When no agents in the queue are available, this could spin the CPU in rapid queue iterations, possibly affecting service."
    fi
  done
}

system_capacity() {
  X1=$(hostname | grep FSN1X | wc -l)
  if [ "$X1" == "1" ]; then
    ILBC=$(grep -i ilbc /etc/asterisk/sip_additional.conf | wc -l)
    G729=$(grep -i g729 /etc/asterisk/sip_additional.conf | wc -l)
    SUM=$(($ILBC*4 + $G729))
    if [ $SUM -gt 12 ]; then
      show_warning "This Com.X1 system has a potential transcoding index of $SUM, which is out of spec for the system (maximum recommended: 12). The transcoding index is calculated as ILBC * 4 + G729. Should the upper limit be crossed, system performance might degrade. If this system is not being used in a pass-through installation, please consider configuring less G.729 or ILBC peers: ILBC $ILBC G729 $G729 INDEX $SUM"
    fi
  fi
}

system_loadavg() {
  RESULT=$(grep "load average" /var/log/syslog)
  COUNT=$(grep "load average" /var/log/syslog | wc -l)
  if [ "$COUNT" != "0" ]; then
    show_error "Load average reports in /var/log/syslog:"
    echo $RESULT
  fi
}

sec_passwords() {
  ROOT=$(whoami | grep root | wc -l)
  if [ "1" != "$ROOT" ]; then
    show_info "Not running as root. Cannot evaluate the comma user password. Please ensure that the password for the comma user has been changed from the default. Alternatively run this utility as root. 'sudo ./audit.sh'\n"
  else
    SALT=$(grep comma /etc/shadow | cut -d':' -f2 | cut -d'$' -f3)
    PWD=$(grep comma /etc/shadow | cut -d':' -f2 | cut -d'$' -f4)
    DEFAULT=$(python -c "import crypt, getpass, pwd; print crypt.crypt('farsouth', '\$1\$$SALT\$')" | cut -d'$' -f4)
    if [ "$DEFAULT" == "$PWD" ]; then
      show_warning "The default comma login password is still active. Please change the comma user's password using the 'passwd' command"
    fi
  fi

  GUIADMINPWD=$(echo "select password from ampusers where username LIKE 'admin'" | mysql -u root --skip-column-names asterisk;)
  if [ "$GUIADMINPWD" == "admin" ]; then
    show_warning "The default admin GUI login password is still active. Please change the admin user's password from the GUI (Start - Change password)"
  fi

  for f in $(grep "\[" /etc/asterisk/sip_additional.conf); do
    PEER=$(echo "$f" | sed 's/\[//g' | sed 's/\]//g')
    VALUE=$(grep $PEER /etc/asterisk/sip_additional.conf | grep -i secret | head -n1 | sed 's/ //g')
    if [ "$VALUE" == "secret=$PEER" ]; then
      show_error "SIP peer $PEER has its password set the same as its username. This is very insecure."
    fi
  done

  for f in $(grep "\[" /etc/asterisk/iax_additional.conf); do
    PEER=$(echo "$f" | sed 's/\[//g' | sed 's/\]//g')
    VALUE=$(grep $PEER /etc/asterisk/iax_additional.conf | grep -i secret | head -n1 | sed 's/ //g')
    if [ "$VALUE" == "secret=$PEER" ]; then
      show_error "IAX peer $PEER has its password set the same as its username. This is very insecure."
    fi
  done
}

sec_fail2ban() {
  PRESENT=$(dpkg -l | grep fail2ban | grep ii | wc -l)
  if [ "1" != "$PRESENT" ]; then
    show_warning "The fail2ban package is not installed. We recommend installing fail2ban for security against DDOS and brute force attacks: 'sudo aptitude install fail2ban'"
  fi
}

sec_ssh() {
  ROOT=$(whoami | grep root | wc -l)
  if [ "1" != "$ROOT" ]; then
    show_info "Not running as root. Cannot analyze authentication logs for signs of break-in.'\n"
  else
    cd /tmp/
    cp /var/log/auth* /tmp/
    for f in $(ls /tmp/auth* | grep gz); do gunzip -f $f > /dev/null; done
    INVALID=$(grep "Invalid user" auth* | wc -l)
    DENIED=$(grep "authentication failure" auth* | wc -l)
    if [ "$INVALID" -gt 0 -o "$DENIED" -gt 0 ]; then
      show_warning "$INVALID invalid SSH users specified and $DENIED SSH username/password combination authentication failures. Please see at /var/log/auth* and ensure fail2ban is installed."
    fi
  fi
}

fw_version() {
  COMMAIMG=$(dpkg -l | grep commaimg | cut -d"." -f2 | cut -d" " -f1)
  if [ "$COMMAIMG" -lt 57 ]; then
    show_warning "iTA application image is not the latest (sub version $COMMAIMG). Please update."
  fi
}

check_db_user_flag() {
    FLAG=$(asterisk -rx 'database show' | grep "\/$1\/")
    COUNT=$(asterisk -rx 'database show' | grep "\/$1\/" | wc -l)
    if [ "0" != "$COUNT" ]; then
      show_warning "$COUNT extensions have $2 set:"
      show_info "$FLAG"
    fi
}

db_ast() {
  ROOT=$(whoami | grep root | wc -l)
  if [ "1" != "$ROOT" ]; then
    show_info "Not running as root. Cannot evaluate the asterisk database. Please run this utility as root. 'sudo ./audit.sh'\n"
  else
    check_db_user_flag CF "call forward unconditional"
    check_db_user_flag CFU "call forward no answer"
    check_db_user_flag CFB "call forward on busy"
    check_db_user_flag DND "do not disturb"
  fi
}


#----------------------------------------------------------------------------

check_uname
sw_warranty_audit
hw_timing
net_ping
net_nslookup
net_gw
net_dns
net_internal
net_overlap
net_confusion
hw_earthing
hw_disk
ast_realtime
dialplan_queues
system_capacity
system_loadavg
sec_passwords
sec_fail2ban
sec_ssh
fw_version
db_ast

printf "\nAudit completed. %d error(s) and %d warning(s)\n" $NUMERRORS $NUMWARNINGS

