#!/bin/bash #Global variables queue_total=`exim -bpc` # Saving how many emails are currently in the queue version='1.1.1' # Logfile checking variables: LOGDIR=/var/log LOGFILE=exim_mainlog GREP="grep" NUM_RCPTS=15 ############################################# # User changeable variables to choose which # # checks are performed during log parsing # ############################################ # Check for emails that were sent from scripts # by searching for CWD CHECK_FOR_SCRIPTS="true" # Checks for emails that were sent by a user # that logged in with a password CHECK_FOR_AUTH="true" # Checks for emails sent by # a cpanel/system account CHECK_FOR_ACCOUNT="true" # Show the address the most bounce backs # were returned to CHECK_FOR_BOUNCES="false" # Does a check for emails sent by a email address # However this can be forged, may get rid of this function CHECK_MOST_DOMAIN="false" # Shows what IP's sent the most emails # 127.0.0.1 is common if sent via script/webmail CHECK_MOST_IP="false" # Shows single emails that had the most recipients CHECK_MOST_RCPTS="true" ################################################## # !!!END OF USER DEFINABLE VARIABLES!!! # ################################################## # Sends script version information to my server #commented out because the domain expired #curl --connect-timeout 5 -d "version=$version" http://morzain.net/spamfu/spamfu.php ################################################### ################################################### # This is the main menu of the script # # This asks you to check the logs, queue, or exit # ################################################### ################################################### function spamfu_init { echo "####################################" echo "" echo "SpamFu for Dummies:" echo "There are currently ${queue_total} emails in the queue" echo "" echo "What would you like to do?:" echo " (1) Check for spammers via email logs" echo " (2) Check for spammers via emails in the queue" echo " (3) Exit" echo -n "Select option: " read spamfu_option if [ -z $spamfu_option ]; then spamfu_init else echo "You have selected $spamfu_option" fi if ! [ $spamfu_option -ge 1 -a $spamfu_option -le 3 ];then echo Invalid option spamfu_init fi if [ $spamfu_option = "1" ]; then logs_init fi if [ $spamfu_option = "2" ]; then queue_init fi if [ $spamfu_option = "3" ]; then echo "Exiting" exit fi } ####################################################### ####################################################### # This is the menu for checking the queue # # It asks you how many seconds to check the queue for # ####################################################### ####################################################### function queue_init { echo "There are currently ${queue_total} emails in the queue" echo "" echo "Parsing the entire queue can take a while" echo "Instead we can look at a snapshot of the emails in the queue" echo -n "How many seconds would you like to parse the queue for? 0 is unlimited [0]: " read queue_option if [ -z $queue_option ]; then echo "" echo "You have selected: 0 (unlimited)" echo "" queue_timeout="0" check_queue elif echo $queue_option | grep -Eq '^[0-9]{0,3}?\.?[0-9]+$'; then queue_timeout=$queue_option echo "Setting timeout to ${queue_timeout} seconds" check_queue else echo "" echo "****************************" echo "Please enter a valid integer" echo "****************************" echo "" queue_init fi } ####################################################### ####################################################### # This is the menu for checking the logs # ####################################################### ####################################################### function logs_init { echo "" echo "##########################################################" echo "Parsing a large file with lots of checks can take a while" echo "Choose options to pick which logfile, which checks to perform" echo "as well as how many lines of the file to parse" echo "" echo "LOGFILE: `echo $LOGFILE | awk '{print $1}'`" echo "SIZE: `du -sh $LOGDIR/$LOGFILE | awk '{print $1}'`" echo " (1) Proceed with check" echo " (2) Change log file" echo " (3) Change which checks are performed" echo " (4) Change how many lines to parse" echo " (5) Main Menu" echo -n "Select option: " read logmenu_option if ! [ $logmenu_option -ge 1 -a $logmenu_option -le 5 ];then echo Invalid option logs_init fi if [ $logmenu_option = "1" ]; then check_logs fi if [ $logmenu_option = "2" ]; then logfile_menu fi if [ $logmenu_option = "3" ]; then logcheck_menu fi if [ $logmenu_option = "4" ]; then logline_menu fi if [ $logmenu_option = "5" ]; then spamfu_init fi } ################################ function logfile_menu { LOG_NUMBER=0 echo "" echo "######################################" echo "choose one of the following:" # create an array called LOG_LIST with a list of any file in $LOGDIR # that starts with exim_mainlog for logfile in `ls $LOGDIR/exim_mainlog*`; do let "LOG_NUMBER += 1" LOG_LIST[$LOG_NUMBER]=`echo $logfile | awk -F/ '{print $NF}'` done # save the number of files in the array and subtract 1 # becuase we want to start at 1 instead of 0 LOG_NUMBER=${#LOG_LIST[@]} # let "LOG_NUMBER -= 1" # Create the menu with a loop of 1 to however many files are in the array for i in `seq 1 $LOG_NUMBER`; do echo " ($i) `du -sh $LOGDIR/${LOG_LIST[$i]} | awk '{print $1}'` ${LOG_LIST[$i]}" done echo -n "Select option: " read logmenu_option if [ $(echo "$logmenu_option" | grep -E "^[0-9]+$") ]; then if [ $logmenu_option -le $LOG_NUMBER -a $logmenu_option -gt 0 ]; then LOGFILE=${LOG_LIST[$logmenu_option]} echo "Using ${LOG_LIST[$logmenu_option]}" if [[ $(file $LOGDIR/$LOGFILE | grep "gzip") ]]; then GREP="zgrep" echo "using zgrep" else GREP="grep" echo "using grep" fi logs_init else echo "Invalid option" logfile_menu fi else echo "Invalid option" logfile_menu fi } ########################### function logline_menu { echo "not implemented yet" logs_init } function logcheck_menu { echo "not implemented yet" echo "these can be modified by editing the script" logs_init } #################################################### #################################################### #Function to check the logs # #Called if you choose that option on the main menu # #################################################### #################################################### check_logs() { # This sets a variable so we can ignore emails sent to domains on the server, as we only want outgoing emails. # it takes the list of domains, adds "for .*@" to the front of each domain # then replaces the newline characters with pipes, and removes the pipe that ends up at the end of the line LOCAL_DOMAINS=`cat /etc/localdomains | sed 's/^/for .*@/g' | tr '\n' '|' | sed 's/|$//'` check_for_scripts() { echo "Checking for scripts..." SCRIPTED_EMAILS=`$GREP -o " cwd=[[:alnum:][:graph:]]*" $LOGDIR/$LOGFILE | grep -v spool | sort | uniq -c | sort -rn | head` echo "Emails sent from scripts:" echo "$SCRIPTED_EMAILS" echo } check_for_auth() { echo "Checking for auth users..." AUTH_EMAILS=`$GREP '<=' $LOGDIR/$LOGFILE | egrep -o " A\=(fixed|courier|dovecot)_(login|plain):[[:alnum:][:graph:]]*" | cut -d: -f2 | sort | uniq -c | sort -rn | head` echo "Most emails sent by authenticated users:" echo "$AUTH_EMAILS" echo } check_for_account() { echo "Checking for cpanel/system accounts..." ACCOUNT_EMAILS=`$GREP '<=' $LOGDIR/$LOGFILE | egrep -v "$LOCAL_DOMAINS" | grep -v ' U=mailnull' | grep -o " U=[[:alnum:][:graph:]]*" | cut -d= -f2 | sort | uniq -c | sort -rn | head` echo "Emails sent from cpanel/system accounts:" echo "$ACCOUNT_EMAILS" echo } check_for_bounces() { echo "Checking for bounces..." BOUNCE_BACKS=`$GREP " U=mailnull.*returning message" $LOGDIR/$LOGFILE | grep -o " for [[:alnum:][:graph:]]*@[[:alnum:][:graph:]]*" | cut -d' ' -f3 | sort | uniq -c | sort -rn | head` echo "Most bounces returned to:" echo "$BOUNCE_BACKS" echo } check_most_domain() { echo "Checking 'from' addresses..." MOST_SENT_DOMAIN=`$GREP '<=' $LOGDIR/$LOGFILE | grep -v mailnull | egrep -v "$LOCAL_DOMAINS" | cut -d" " -f6 | sort | uniq -c | sort -rn | head` echo "Most frequent senders by 'from' address:" echo "Note: Could be forged addresses" echo "$MOST_SENT_DOMAIN" echo } check_most_ip() { echo "Checking for sender IP..." MOST_SENT_IP=`$GREP '<=' $LOGDIR/$LOGFILE | egrep -v "$LOCAL_DOMAINS" | grep -o ' H=.*\ \[[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\]' | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | sort | uniq -c | sort -rn | head` echo "Most frequent senders by IP address:" echo "$MOST_SENT_IP" echo } check_most_rcpts() { echo "Checking for recipients..." MOST_RCPTS=`$GREP '<=' $LOGDIR/$LOGFILE | grep -v mailnull | egrep -v "$LOCAL_DOMAINS" | awk -v NUM_RCPTS="$NUM_RCPTS" ' function shift_list(x) { y=x x++ while (x <= NUM_RCPTS) { TOP_RCPTS[x] = TOP_RCPTS[y] MAIL_ID[x] = MAIL_ID[y] SENDER_ID[x] = SENDER_ID[y] x++ y++ } } function save_current() { SENDER_ID[CUR_NUM]=$6 MAIL_ID[CUR_NUM]=$4 TOP_RCPTS[CUR_NUM]=NUM_ADDRESSES } function display_results() { CUR_NUM=1 while (CUR_NUM <= NUM_RCPTS) { print MAIL_ID[CUR_NUM], "with", TOP_RCPTS[CUR_NUM], "recipients was sent by", SENDER_ID[CUR_NUM] CUR_NUM++ } } function duplicate_check() { x=NUM_RCPTS y=x-1 while (x > 0) { if (MAIL_ID[x] == MAIL_ID[y]) { MAIL_ID[x]=0 TOP_RCPTS[x]=0 SENDER_ID[x]=0 } x-- y-- } } BEGIN { CUR_NUM=NUM_RCPTS while (CUR_NUM > 0){ MAIL_ID[CUR_NUM] = 0 TOP_RCPTS[CUR_NUM] = 0 SENDER_ID[CUR_NUM] = 0 CUR_NUM-- } } { split($0,RCPTS_TMP,"from.*for ") split(RCPTS_TMP[2],RCPTS," ") NUM_ADDRESSES=0 for (ADDRESSES in RCPTS) ++NUM_ADDRESSES CUR_NUM=1 for (EACH in TOP_RCPTS) { if (NUM_ADDRESSES > TOP_RCPTS[CUR_NUM]) { shift_list(CUR_NUM) save_current() duplicate_check() break } CUR_NUM++ } } END { display_results() } '` echo "Most recipients by Mail and Sender ID's:" echo "$MOST_RCPTS" } # This is where the functions that were declared above # Are actually called, if their variables are set to true if [ $CHECK_FOR_SCRIPTS = "true" ]; then check_for_scripts fi if [ $CHECK_FOR_AUTH = "true" ]; then check_for_auth fi if [ $CHECK_FOR_ACCOUNT = "true" ]; then check_for_account fi if [ $CHECK_FOR_BOUNCES = "true" ]; then check_for_bounces fi if [ $CHECK_MOST_DOMAIN = "true" ]; then check_most_domain fi if [ $CHECK_MOST_IP = "true" ]; then check_most_ip fi if [ $CHECK_MOST_RCPTS = "true" ]; then check_most_rcpts fi } ############################################# ############################################# # Function to check the emails in the queue # ############################################# ############################################# check_queue() { #Function that stops the find command after X number of seconds kill_it() { sleep $queue_timeout PID=`ps aux | grep "find /var/spool/exim/input" | grep -v grep | awk '{print $2}'` if [ -n "${PID}" ]; then kill $PID fi } # Set timer for parsing the queue unless it is 0 (unlimited) if [ "$queue_timeout" != "0" ]; then kill_it & fi # Find emails in exim's spool folder queue_tmp=`find /var/spool/exim/input -name '*-H' | sed '$d' | xargs grep 'auth_id'` echo "Done finding emails, starting to parse" # Parse the queue_tmp variable to sort by auth_id queue_senders=`echo -e "$queue_tmp" | cut -d: -f2 | sort | uniq -c | sort -rn | head -n5` echo -e "Parsed `echo -e "$queue_tmp" | wc -l` emails out of $queue_total in the queue with a $queue_timeout second timeout" echo -e "Highest number of emails in queue by auth_id:" echo -e "$queue_senders" echo "" queue_senders_tmp=`echo "$queue_senders" | awk '{print $3}'` for each in `echo "$queue_senders_tmp"` do echo "Example emails sent by $each:" echo "$queue_tmp" | grep $each | cut -d: -f1 | head -n5 echo "" done } clear spamfu_init