- Masked Pumpkin
- May 10, 2008
-
|
I've got a server that has to allow multiple a reasonably high smtpd_connection_rate_limit as it's managing (sasl_authenticated) mail for multiple domains, each with many users behind a single (ISP allocated, DHCP) IP. As a result, when an account gets hacked and used to send spam, it can take several minutes for the alert regarding a suddenly increased mailq size to come to our attention and get fixed. While the postfix service could simply be stopped when a high mailq is detected, that's not an elegant solution, and so I decided to try my hand at a bash script to run every minute and fix the problem:
code:#!/bin/bash
#This script will check for the top senders in X lines of postfix maillog,
#judging by sasl_username. If a given username appears too often on that list,
#the script will find the most common originating IP and ban it with iptables.
#It will then clean the mailq of any emails that apply with that address, and
#(hopefully) send an email to a given address to alert them of the action
###############################################################################
#User specified variables:
#Maximum number of messages from any one user in the last 1500 lines of code
MAXMESSAGES="50"
#Path to your mail.log file
MAILLOGPATH="/var/log/maillog"
#Email Address to receive notification - must be properly set up in your /etc/msmtp.conf
ADMINEMAIL="youremail@gmail.com"
###############################################################################
if [ -f /tmp/topips ]; #First, some cleanup
then
rm /tmp/topips
fi
if [ -f /tmp/topqueues ];
then
rm /tmp/topqueues
fi
NUMMSGS=`tail -n1500 $MAILLOGPATH | grep sasl_username | awk '{print $9}' | sort | uniq -c | sort -n | tail -n1 | awk '{print $1}'` #I really shouldn't have to do this twice?
if [ "$NUMMSGS" -ge "$MAXMESSAGES" ];
then
USER=`tail -n1500 $MAILLOGPATH | grep sasl_username | awk '{print $9}' | sort | uniq -c | sort -n | tail -n1 | cut -d= -f2`
echo $USER
#Find the queue IDs for any offending messages with that username, and feed the IP into /tmp/topips
mailq | grep $USER | awk '{print $1}'| while read LINE; do
IP=`postcat -q $LINE | grep -E "^Received:(.)*[.]*\[[0-9]*.[0-9]*.[0-9]*.[0-9]*\]\)" | grep -oE "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | uniq | tail -n1 >> /tmp/topips`;
echo $LINE >> /tmp/topqueues;
done
#Change that user's password
mysql -u POSTFIXUSER -pPASSWORD -e 'UPDATE postfix.USERS SET password = "ChangedPass!34NotEvenMD5" WHERE username = "$USER";'
#Find the most regularly occurring IP in that file - if it exists
if [ -f /tmp/topips ];
then
TOPIP=`uniq /tmp/topips -c | sort | tail -n1 | awk '{print $2}'`
echo IP to ban is $TOPIP
iptables -I INPUT 1 -p tcp -s $TOPIP -j DROP #This could be set to ACCEPT while testing, drop later
#Now we can clear the mailq of all the offending messages
cat /tmp/topqueues | while read LINE; do
#postsuper -d $LINE
echo $LINE will be deleted;
done
#Now let's send off an email to let us know what's happened
echo The user $USER was blocked for sending $NUMMSGS emails from $TOPIP | msmtp --file=/etc/msmtp.conf --account=gmail $ADMINEMAIL
else
echo $USER has had all emails delivered already, no IP to ban
fi
#And lastly, clean up after ourselves
if [ -f /tmp/topips ]; #First, some cleanup
then
rm /tmp/topips
fi
if [ -f /tmp/topqueues ];
then
rm /tmp/topqueues
fi
else
echo Highest messages at $NUMMSGS, going back to sleep
fi
My lack of experience with bash scripting should be evident - it feels like I'm making multiple unnecessary calls (for example, to find the highest user and then the highest number of messages). The use of temporary files instead of arrays also doesn't feel right. On the other hand, the script does work - it's caught a handful of chancers in the last few weeks I've been testing it.
I'd like to try and get this script as polished as possible to aid with my own study of bash, and if anyone else finds it useful they're welcome to it. Otherwise, am I perhaps barking up the wrong tree entirely?
|
#
¿
Oct 14, 2014 08:38
|
|
- Adbot
-
ADBOT LOVES YOU
|
|
#
¿
Apr 29, 2024 06:23
|
|
- Masked Pumpkin
- May 10, 2008
-
|
Thank you so much! Based on your advice, I've updated and tweaked the script somewhat -
Changelist:
- Mailq is now called instead of trawling through the log file - this still requires multiple calls to mailq and subsequently to postcat, which I'd like to trim down if I can
- MAXMESSAGES is now also checked with MAXMESSAGESPERUSER, to help avoid blocking a user who happens to have just 5 messages in the queue when the server is very congested for whatever reason
- The password change query to MySQL wasn't being processed properly thanks to the way the $USER variable was passed to it - fixed
- Exit codes now used to find bad messages in the queue and remove them, and also to check whether an IP hasn't already been banned
- Removed checking for the existence of temporary files
- IP variable actually set and used now - a good thing, since we were previously getting a DNS name, which works but is not ideal for IPTables
- Found a bad if/then check, fixed and cleaned up indenting to reflect properly
- Integrated fail2ban for temporary blocking, if it's installed (and the sendmail-auth jail is configured)
Any other comments/suggestions are greatly appreciated - I've learned a lot about bash from this, but I can't help but feel that the biggest thing I've learned is to not use bash if I can use PHP or Python (both of which I know reasonably well and allow for variable management that doesn't drive you to drink).
code:#!/bin/bash
#This script will check for the top senders in X lines of postfix maillog, judging by sasl_username.
#If a given username appears too often on that list, the script will find the most common originating
#IP and ban it with iptables. It will then clean the mailq of any emails that apply with that address,
#and (hopefully) send an email to a given address to alert them of the action.
#AMENDED - This script will keep an eye on the postfix queue - if it grows too big, it'll look for the
#highest (sasl_auth) sender in that queue - if that sender is above a threshold, it'll change that
#users password, block the IP, and remove all of those messages from the mailq
###############################################################################
#User specified variables:
#Maximum number of messages from any one user in the last 1500 lines of code
MAXMESSAGES="50"
#Maximum number of messages from any one user
MAXMESSAGESPERUSER="20"
#Path to your mail.log file
MAILLOGPATH="/var/log/maillog"
#Email Address to receive notification - must be properly set up in your /etc/msmtp.conf
ADMINEMAIL="youremail@gmail.com"
#If you have Fail2Ban installed (with the sendmail-auth jail enabled) then change this to 1 so an IP
#can be banned but automatically unbanned after whatever bantime you have set in Fail2Ban
FAIL2BAN="1"
###############################################################################
#First, some cleanup
rm /tmp/topips
rm /tmp/topuser
NUMMSGS=`mailq | tail -n1 | awk '{print $5}'`
if [ "$NUMMSGS" -ge "$MAXMESSAGES" ];
then
echo Number of messages in queue at $NUMMSGS, checking for single high user;
mailq | grep -P "^[0-9A-f]{10}" | awk '{print $1}' | while read LINE; do
postcat -q $LINE | grep -E "Authenticated sender: [A-Za-z0-9]*@[A-Za-z0-9\.]*\)" | grep -oE "[a-zA-Z0-9]*@[a-zA-Z0-9\.]*" >> /tmp/topuser;
done
USERMSGS=`uniq -c /tmp/topuser |sort -n |tail -n1| awk '{print $1}'`;
if [ "$USERMSGS" -ge "$MAXMESSAGESPERUSER" ];
then
echo Highest user at $USERMSGS, taking action;
USER=`uniq -c /tmp/topuser |sort -n |tail -n1| awk '{print $2}'`;
echo User is $USER;
#Find the queue IDs for any offending messages with that username, and feed the
#IP into /tmp/topips - Sadly, mailq will only give us the provided from_address,
#not the sasl_sender, so this must be run through postcat
mailq | grep -P "^[0-9A-f]{10}" | awk '{print $1}' | while read LINE; do
postcat -q $LINE | grep -iE "Authenticated sender: $USER";
if [ $? -eq 0 ];
then
postcat -q $LINE | grep -E "^Received:(.)*[.]*\[[0-9]*.[0-9]*.[0-9]*.[0-9]*\]\)" | grep -oE "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | uniq | tail -n1 >> /tmp/topips;
fi
done
BADIP=`uniq -c /tmp/topips |sort -n |awk '{print $2}'`
echo Offending IP is $BADIP
#Change that user's password
echo Changing password for $USER
mysql -u POSTFIXUSER -pPASSWORD -e "UPDATE postfix.mailbox SET password = 'ChangedPass!23324234' WHERE username = \"$USER\";"
#Ban the IP, first checking it's not being blocked already
if [ "$FAIL2BAN" -eq "1" ];
then
echo Jailing $BADIP with fail2ban;
fail2ban-client sendmail-auth banip $BADIP;
else
iptables -L|grep -E "DROP tcp -- $BADIP[ ]*anywhere";
if [ $? -eq 1 ];
then
echo banning IP $BADIP with iptables;
iptables -I INPUT 1 -p tcp -s $BADIP -j DROP;
fi
fi
#Now we can clear the mailq of all the offending messages
mailq | grep -P "^[0-9A-f]{10}" | awk '{print $1}' | while read LINE; do
postcat -q $LINE | grep -iE "Authenticated sender: $USER"
if [ $? -eq 0 ];
then
echo $LINE will be deleted;
#postsuper -d $LINE;
fi
done
#Now let's send off an email to let us know what's happened
echo The user $USER was blocked for sending $NUMMSGS emails from $BADIP | msmtp --file=/etc/msmtp.conf --account=gmail $ADMINEMAIL
#And lastly, clean up after ourselves
rm /tmp/topips
rm /tmp/topuser
else
echo Not enough messages from one user in queue
fi
else
echo Highest messages at $NUMMSGS, going back to sleep
fi
|
#
¿
Oct 19, 2014 09:31
|
|