# outbound-spam-report.sh
# Written by Ryan C <ryan@imhadmin.net> / x 769
# This script examines /var/log/exim_mainlog to find outbound messages
# rejected by cpaneleximscanner (SpamAssassin) and sends an STR when it
# finds an address with a number of rejected messages greater than the
# ARGV1 to this script can be an integer in order to adjust
CONTACT_EMAIL=str@imhadmin.net
EMAIL_SUBJECT="[AUTO STR] Outbound SPAM on ${HOSTNAME}"
grep -q 'offset' -- <<<"$i"
OFFSET_FILE="/opt/sharedrads/ops/outbound-spam-offset"
OFFSET=$(grep -E '[0-9]+' $OFFSET_FILE)
[[ -z $OFFSET ]] && OFFSET=0
# Was a threshold specified? We're just looking for some number
grep -qE '^[0-9]+$' -- <<<"$i"
grep -q 'send-str' -- <<<"$i"
grep -qE -- '-h|--help' <<<"$i"
${0} [-h|--help] [--offset] [--send-str] [SPAM_THRESHOLD]
-h|--help Print help and exit.
--offset Use offset file. Primarily intended to be used when run from
--send-str Send STR to ${CONTACT_EMAIL} for addresses which exceed the
SPAM_THRESHOLD Default: 30; An integer representing the minumum number of
blocked messages required in order to be included in the
- Suggested cron invocation:
- Specify a specific threshold and print to STDOUT
[[ -z $OFFSET ]] && OFFSET=0
[[ -z $SPAM_THRESHOLD ]] && SPAM_THRESHOLD=30
[[ -z $SEND_STR ]] && SEND_STR=0
# If the offset is greater than the number of lines, start from 0
LOG_LINES=$(wc -l /var/log/exim_mainlog | awk '{print $1}')
[[ $LOG_LINES -lt $OFFSET ]] && OFFSET=0
# Use mawk if it's available
if command mawk 2>/dev/null; then
RESULTS=$($AWK -v "OFFSET=$OFFSET" \
-v "OFFSET_FILE=$OFFSET_FILE" \
-v "SPAM_THRESHOLD=$SPAM_THRESHOLD" \
ACL = "/usr/local/cpanel/etc/exim/acls/ACL_OUTGOING_SMTP_CHECKALL_BLOCK/custom_begin_outgoing_smtp_checkall"
while ((getline < ACL) > 0) {
SCORE_MATCH=match($0, /[$]spam_score_int[{}]+[0-9]+[}{]+1/)
ACL_MAX_SCORE = substr($0, SCORE_MATCH + 17, RLENGTH - 20)
# spam_score_int should be divided by 10 to get the actual spam score that
# will be used to filter mail
MAX_SCORE = ACL_MAX_SCORE / 10
if ($0 ~ /cpaneleximscanner detected OUTGOING.*as spam/) {
SPAM_SCORE = substr($NF, match($NF, /-?[0-9.]+/), RLENGTH)
if (SPAM_SCORE > MAX_SCORE) {
if (MSG_ID in SPAM_IDS) {
ADDR_MATCH = match($0, /F=<[a-zA-Z0-9@-.]+>/)
SPAMMER = substr($0, ADDR_MATCH + 3, RLENGTH - 4)
for (ACCOUNT in SPAM_COUNT) {
if (SPAM_COUNT[ACCOUNT] > SPAM_THRESHOLD) {
printf("%5s %s\n", SPAM_COUNT[ACCOUNT], ACCOUNT)
}' /var/log/exim_mainlog)
if [[ $(wc -l <<<"$RESULTS" | awk '{print $1}') -gt 1 ]]; then
REPORT=$(echo -n "Outbound SPAM Report"
if [[ ! -z $OFFSET_FILE ]]; then
echo -ne " for lines ${OFFSET}-$(cat $OFFSET_FILE) of /var/log/exim_mainlog.\n"
echo -e "Please review mail logs and customer accounts for abuse.\n"
printf "%9s %5s %s\n" User "#" Address
# Time for an ugly BASH loop
while read number address ; do
domain=$(cut -d@ -f2<<<"$address")
user=$(grep -im1 "$domain" /etc/userdomains | cut -d: -f2 )
# Check to see if it was sent by the cPanel user
grep -iq "@${HOSTNAME}" <<<"$address"
user=$(cut -d@ -f1 <<<"$address")
printf "%9s %5s %s\n" -- $number $address
printf "%9s %5s %s\n" $user $number $address
done <<<"$RESULTS" | sort -nk2)
# Send the report or print it
if [[ $SEND_STR -eq 1 ]]; then
echo "$REPORT" | mail -s "${EMAIL_SUBJECT}" $CONTACT_EMAIL
echo "Nothing to report!"