Konfiguracja MTA Exim.
Jeżeli masz ochotę sprzęgnąć swojego exim'a z tym RBL, to zrób tak:
Jeżeli masz ochotę sprzęgnąć swojego exim'a z tym RBL, to zrób tak:
#W exim conf uaktywnij sprawdzanie nadawcy (chyba że już masz):
acl_smtp_rcpt = acl_check_recip
#Następnie po linii ropoczynającej sprawdzanie ACL (begin_acl) dodaj wpis:
acl_check_recip:
# ---- tutaj jakieś własne regułki sprawdzające
#A zaraz po nich (lub przed nimi umieść poniższy kod:
deny
message = Blocked by rhsbl.rbl.polspam.pl
!authenticated = *
dnslists = rhsbl.rbl.polspam.pl/$sender_address_domain
log_message = Blocked by rhsbl.rbl.polspam.pl(bad domains senders)
deny
message = Blocked by rhsbl-danger.rbl.polspam.pl
!authenticated = *
dnslists = rhsbl-danger.rbl.polspam.pl/$sender_address_domain
log_message = Blocked by rhsbl-danger.rbl.polspam.pl(bad domains senders)
deny
message = Blocked by bl.rbl.polspam.pl
!authenticated = *
dnslists = bl.rbl.polspam.pl
log_message = Blocked by bl.rbl.polspam.pl
defer
message = Blocked by bl-h1.rbl.polspam.pl. We check now your reputation.
!authenticated = *
dnslists = bl-h1.rbl.polspam.pl
log_message = Blocked by bl-h1.rbl.polspam.pl
delay = 30s
defer
message = Blocked by bl-h2.rbl.polspam.pl We check now your reputation.
!authenticated = *
dnslists = bl-h2.rbl.polspam.pl
log_message = Blocked by bl-h2.rbl.polspam.pl
delay = 30s
defer
message = Blocked by bl-h3.rbl.polspam.pl We check now your reputation.
!authenticated = *
dnslists = bl-h3.rbl.polspam.pl
log_message = Blocked by bl-h3.rbl.polspam.pl
delay = 30s
defer
message = Blocked by bl-h4.rbl.polspam.pl
!authenticated = *
dnslists = bl-h4.rbl.polspam.pl
log_message = Blocked by bl-h4.rbl.polspam.pl
Uaktywnienie powyższych ustawień dla ACL acl_check_recip, spowoduje odbijanie poczty z kodem 5xx (dla deny) oraz niewpuszczanie poczty z kodem 4xx (dla defer) po odpytaniu RBL polspam.
Od czasu do czasu zachodzi potrzeba wycięcia skubańca korzystającego z poczty domeny, której z różnych względów nie można zablokować w całości a ma się ochotę na podrażnienie się z pojedynczym szkodnikiem, który śle SPAM bez opamiętania. Taka sytuacja często dotyczy domen używanych przez dostawców darmowych skrzynek e-mail, jak np. googlowy gmail.
Dla MTA Exim można to zrobić z wykorzystaniem PERL, ale aby to zrobić, powinien on być wkompilowany. Jak to zrobić można przeczytać tutaj Następnie uzupełniając istniejący lub pisząc nowy skrypt dla Exim, wywoływany w exim.conf w następujący sposób (na czas testów deny zastąpić przez defer i obserwować działanie):
#pokazanie eximowi perlowego pliku
perl_startup = do '/etc/exim/emailbanfilter-polspam-b-emails.pl'
#ACL w którym wołamy funkcję (sub) perl
acl_smtp_rcpt:
deny message = Private e-mail BlackList in progress ... Bye bye dear spammer :)
!authenticated = *
condition = ${perl{polspam_black_email_rbl_check}}
logwrite = Sender $sender_address_local_part@$sender_address_domain is blacklisted on bl-emails.rbl.polspam.pl
A perlowy malutki kod, wołany z exim.conf, który odpytuje RBL i załatwia nam sprawę pojedynczych adresów e-mail (/etc/exim/emailbanfilter-polspam-b-emails.pl) wygląda następująco:
use Socket;
use Net::DNS;
sub polspam_black_email_rbl_check
{
my $usr = Exim::expand_string('$sender_address_local_part');
my $dmn = Exim::expand_string('$sender_address_domain');
my $rbl = 'bl-emails.rbl.polspam.pl';
my $que = "$usr\@$dmn.$rbl";
my $res = Net::DNS::Resolver->new;
my $odp = $res->search($que, "A");
if ($odp)
{
foreach my $rr($odp->answer)
{
next unless $rr->type eq "A";
Exim::log_write("PRV_EMAIL_BL: YES $usr\@$dmn LISTED on RBL: $rbl !!! (" . $rr->address . ")");
return "yes";
}
}
else
{
Exim::log_write("PRV_EMAIL_BL: NO $usr\@$dmn NOT listed.");
return "no"
}
}
return true;
#end
Przykład wykorzystania logu /var/log/exim/rejectlog oraz przedstawienie współpracy ipset z iptables do zablokowania upierdliwych adresów:
W pierwszej kolejności zbudujemy odpowiedni zbiór adresow IPv4, przechowywany za pomocą ipset.
Tworzymy plik o nazwie np. scan-exim-rejectlog.
#!/bin/bash
####################################################################################################################
### User manual settings (depending on the version of the operating system and individual program settings) ###
####################################################################################################################
export BANTIME=86400 #only later than (CURRENT_UNIX_TIMESTAMP - BANTIME) sec
export SPECFUNC="no" #Execution of special functions: yes or other to no
export SPECFUNC_SPAMMAX=35 #Min SPAM points (from spamassassin), before ban sender
export LOGGING="on" #Logging: on - yes, other value - no
export EXIM_REJECTLOG="/var/log/exim/rejectlog" #Exim reject log
export EXIM_MAINLOG="/var/log/exim/mainlog" #Exim main log
export LOGFILE="/var/log/${0##*/}.log" #Logging into this file
export PIDFILE="/var/run/${0##*/}.pid" #Pid file
export WHEREIS="/usr/bin/whereis" #whereis command
export CUT="/usr/bin/cut" #cut command
# Names of set's, BAN4_BLOCK_NAME for IPv4 and BAN6_BLOCK_NAME for IPv6, used to manage sets of IP addresses.
# The contents of these set's are used by iptables & ip6tables, to block unwanted connections.
# These names must match the set's names used by iptables.
# At the end of the script, there are examples of how to use the collected data.
export BAN4_BLOCK_NAME="IP4BLOCKMAIL"
export BAN6_BLOCK_NAME="IP6BLOCKMAIL"
####################################################################################################################
####################################################################################################################
####################################################################################################################
#!!! Don't change anything below this line !!! [ Unless you know what you are doing :) ]
#Location of the files depends on the operating system
export DATE=`${WHEREIS} date | cut -d ' ' -f 2`
export CAT=`${WHEREIS} cat | cut -d ' ' -f 2`
export ECHO=`${WHEREIS} echo | cut -d ' ' -f 2`
export GREP=`${WHEREIS} grep | cut -d ' ' -f 2`
export GZIP=`${WHEREIS} gzip | cut -d ' ' -f 2`
export HOST=`${WHEREIS} host | cut -d ' ' -f 2`
export IPSET=`${WHEREIS} ipset | cut -d ' ' -f 2`
export MV=`${WHEREIS} mv | cut -d ' ' -f 2`
export RM=`${WHEREIS} rm | cut -d ' ' -f 2`
export PS=`${WHEREIS} ps | cut -d ' ' -f 2`
export SORT=`${WHEREIS} sort | cut -d ' ' -f 2`
export STAT=`${WHEREIS} stat | cut -d ' ' -f 2`
export UNIQ=`${WHEREIS} uniq | cut -d ' ' -f 2`
export TMP_FILE="/tmp/${0##*/}"
export LOGROTATE="/etc/logrotate.d/${0##*/}"
####################################################################################################################
export VER="2022010601"
####################################################################################################################
#Calling special functions
####################################################################################################################
# #
# Special functions start #
# Special functions called after main process. #
# #
####################################################################################################################
function fun_Domain_With_Spam_Tag_To_File()
{
#If You use spamassassin software, and want use this function, make:
#1 - in the /etc/mail/spamassassin/local.cf file add (or change) line: rewrite_header Subject [SPAM (_SCORE_)]
#2 - in exim.conf file add (or change to:) log_selector = +subject
# *** This function is executed when variable SPECFUNC="yes" ***
if [ ! -f ${EXIM_MAINLOG} ]; then exit; fi
while IFS= read -r line
do
if [[ ! $line =~ '[SPAM' ]]; then continue; fi
#Date and time from ${EXIM_MAINLOG}
ldt=`${ECHO} $line | ${CUT} -d ' ' -f 1`
lti=`${ECHO} $line | ${CUT} -d ' ' -f 2`
uts=`${DATE} -d "$ldt $lti" +"%s"`
cts=`${DATE} +"%s"`
ivl=$[ $cts - $uts ]
if [ $ivl -lt ${BANTIME} ]
then
spam=`${ECHO} $line | ${CUT} --complement -d '"' -f 1`
spam=`${ECHO} $spam | ${CUT} --complement -d '(' -f 1`
spam=`${ECHO} $spam | ${CUT} -d ')' -f 1`
spam=`${ECHO} $spam | ${CUT} -d '.' -f 1`
dmn=`${ECHO} $line | ${CUT} -d ' ' -f 5`
if [[ $dmn =~ "@" ]]
then
dmn=`${ECHO} $dmn | ${CUT} --complement -d '@' -f 1`
hst=`${HOST} $dmn | ${GREP} -o '[^ ]*$'`
if [[ ! $hst = '' ]]
then
lastchar=${hst: -1}
if [[ $lastchar != '.' ]] && [[ $spam -gt ${SPECFUNC_SPAMMAX} ]]
then
${ECHO} "$hst" >> ${TMP_FILE}
fi
fi
fi
fi
done < ${EXIM_MAINLOG}
}
#
####################################################################################################################
# #
# Special functions stop #
# #
####################################################################################################################
####################################################################################################################
# * ********************* #
# * Here starting script* #
# * Main process * #
# *********************** #
####################################################################################################################
#Check programs - tools
if [ ! -f ${ECHO} ] || [ ! -f ${GREP} ] || [ ! -f ${MV} ] || [ ! -f ${RM} ] || [ ! -f ${SORT} ] || \
[ ! -f ${UNIQ} ] || [ ! -f ${DATE} ] || [ ! -f ${STAT} ] || [ ! -f ${GZIP} ] || [ ! -f ${HOST} ]
then
echo -e "Commands: date echo grep gzip host mv rm sort stat uniq are needed.\nInstall it's first."
if [ ${LOGGING} = "on" ]
then
stamp=`${DATE} '+%Y-%m-%d %H:%M:%S'`
${ECHO} "$stamp [${VER}] Not all needed programs are installed :(" >> ${LOGFILE}
fi
exit 1
fi
#
starttime=`${DATE} +"%s"`
#
#Check ipset program
if [ ! -f ${IPSET} ]
then
${ECHO} -e "${IPSET} does not exists in your OS !!!\nInstall it first."
if [ ${LOGGING} = "on" ]
then
stamp=`${DATE} '+%Y-%m-%d %H:%M:%S'`
${ECHO} "$stamp [${VER}] ipset programm not installed :(" >> ${LOGFILE}
fi
exit 2
fi
#
#Check exim rejectlog file
if [ ! -f ${EXIM_REJECTLOG} ]
then
${ECHO} -e "Exim rejectlog file does not exists.\nPlease configure it in EXIM_REJECTLOG variable in this file and try again."
if [ ${LOGGING} = "on" ]
then
stamp=`${DATE} '+%Y-%m-%d %H:%M:%S'`
${ECHO} "$stamp [${VER}] Can't find exim logfile (${EXIM_REJECTLOG} defined into EXIM_REJECTLOG variable)" >> ${LOGFILE}
fi
exit 3
fi
#Checking if another instance is not working at this time
if [ -f ${PIDFILE} ]
then
pid=`${CAT} ${PIDFILE}`
check=`${PS} -p $pid | ${CUT} -d ' ' -f 1`
if [ "$pid" = "$check" ]
then
#Process ${PID} working
if [ ${LOGGING} = "on" ]
then
stamp=`${DATE} '+%Y.%m.%d %H:%M:%S'`
${ECHO} "$stamp [${VER}] Another instance ($pid) is working now. PIDFILE ${PIDFILE} exists. Exiting." >> ${LOGFILE}
exit 4
fi
else
#Abandoned file by previous process (eg. kill)
${RM} -f ${PIDFILE}
${ECHO} "$$" > ${PIDFILE}
fi
else
${ECHO} "$$" > ${PIDFILE}
fi
#
# Delete old temp file if exists
if [ -f ${TMP_FILE} ]; then ${RM} -f ${TMP_FILE}; fi
####################################################################################################################
# Starting parsing the exim log file
# Read data's from the exim rejectlog
while IFS= read -r line
do
if [[ $line =~ "535 Incorrect" ]] || \
[[ $line =~ "does not conform to RFC2822" ]] || \
[[ $line =~ "HELO should be a FQDN" ]] || \
[[ $line =~ "impersonating" ]] || \
[[ $line =~ "is the local host" ]] || \
[[ $line =~ "is no valid sender" ]] || \
[[ $line =~ "MX record points" ]] || \
[[ $line =~ "MX records point" ]] || \
[[ $line =~ "rejected RCPT" ]] || \
[[ $line =~ "rejected EHLO" ]] || \
[[ $line =~ "rejected HELO" ]] || \
[[ $line =~ "relay not permitted" ]] || \
[[ $line =~ "Sender verify failed" ]] || \
[[ $line =~ "TLS error" ]] || \
[[ $line =~ "too many syntax" ]] || \
[[ $line =~ "too many unrecognized" ]] || \
[[ $line =~ "unrouteable address" ]] || \
[[ $line =~ "verify a sender in a header" ]]
then
#Date and time from ${EXIM_REJECTLOG}
ldt=`${ECHO} $line | ${CUT} -d ' ' -f 1`
lti=`${ECHO} $line | ${CUT} -d ' ' -f 2`
uts=`${DATE} -d "$ldt $lti" +"%s"`
cts=`${DATE} +"%s"`
ivl=$[ $cts - $uts ]
#IPv4 addresses when log line is in ${BANTIME} range
if [ $ivl -lt ${BANTIME} ]
then
if [[ $line =~ "([" ]]
then
ip=`${ECHO} $line | ${CUT} -d ']' -f 2`
ip=`${ECHO} $ip | ${CUT} -d '[' -f 2`
else
ip=`${ECHO} $line | ${CUT} -d '[' -f 2`
ip=`${ECHO} $ip | ${CUT} -d ']' -f 1`
fi
if [[ $ip != '127.0.0.1' ]] && [[ $ip != "::1" ]]
then
${ECHO} $ip >> ${TMP_FILE}
fi
fi
fi
done < ${EXIM_REJECTLOG}
###########################################################################################################################
# *** Here is special functions call *** #
###########################################################################################################################
if [ ${SPECFUNC} = "yes" ]
then
fun_Domain_With_Spam_Tag_To_File
fi
###########################################################################################################################
# Add IP's ipv4 to ipset data's
# Common section for exim & postfix below
if [ ! -f ${TMPFILE} ]
then
#IPv4
chain_exists=`${IPSET} list | ${GREP} ${BAN4_BLOCK_NAME}`
if [[ $chain_exists != "" ]]
then
chain_exists=`${ECHO} $chain_exists | ${CUT} -d ' ' -f 2`
if [[ "$chain_exists" -eq "${BAN4_BLOCK_NAME}" ]]
then
${IPSET} flush ${BAN4_BLOCK_NAME} > /dev/null 2>&1
fi
fi
#IPv6
chain_exists=`${IPSET} list | ${GREP} ${BAN6_BLOCK_NAME}`
if [[ $chain_exists != "" ]]
then
chain_exists=`${ECHO} $chain_exists | ${CUT} -d ' ' -f 2`
if [[ "$chain_exists" -eq "${BAN6_BLOCK_NAME}" ]]
then
${IPSET} flush ${BAN6_BLOCK_NAME} > /dev/null 2>&1
fi
fi
#Log to file
if [ ${LOGGING} = "on" ]
then
stamp=$(date '+%Y.%m.%d %H:%M:%S')
${ECHO} "$stamp [${VER}] Empty data. Exiting." >> ${LOGFILE}
fi
exit 0
fi
###########################################
#Make ${BAN4_BLOCK_NAME} initial operations
chain_exists=`${IPSET} list | grep ${BAN4_BLOCK_NAME}`
if [[ $chain_exists != "" ]]
then
chain_exists=`${ECHO} $chain_exists | ${CUT} -d ' ' -f 2`
if [[ "$chain_exists" -eq "${BAN4_BLOCK_NAME}" ]]
then
${IPSET} flush ${BAN4_BLOCK_NAME} >/dev/null 2>&1
else
${IPSET} create ${BAN4_BLOCK_NAME} hash:ip hashsize 4096 maxelem 16777216 family inet >/dev/null 2>&1
fi
else
${IPSET} create ${BAN4_BLOCK_NAME} hash:ip hashsize 4096 maxelem 16777216 family inet >/dev/null 2>&1
fi
#
###########################################
#Make ${BAN6_BLOCK_NAME} initial operations
chain_exists=`${IPSET} list | grep ${BAN6_BLOCK_NAME}`
if [[ $chain_exists != "" ]]
then
chain_exists=`${ECHO} $chain_exists | ${CUT} -d ' ' -f 2`
if [[ "$chain_exists" -eq "${BAN6_BLOCK_NAME}" ]]
then
${IPSET} flush ${BAN6_BLOCK_NAME} >/dev/null 2>&1
else
${IPSET} create ${BAN6_BLOCK_NAME} hash:ip hashsize 4096 maxelem 16777216 family inet6 >/dev/null 2>&1
fi
else
${IPSET} create ${BAN6_BLOCK_NAME} hash:ip hashsize 4096 maxelem 16777216 family inet6 >/dev/null 2>&1
fi
###########################################
#Fill set's: ${BAN4_BLOCK_NAME} && ${BAN6_BLOCK_NAME}
while read ip
do
if [[ $ip =~ "." ]]
then
warn=`${IPSET} test ${BAN4_BLOCK_NAME} $ip 2>&1`
exists=${warn:0:4}
if [[ $exists != "Warn" ]]
then
${IPSET} -A ${BAN4_BLOCK_NAME} $ip >/dev/null 2>&1
fi
elif [[ $ip =~ ":" ]]
then
warn=`${IPSET} test ${BAN6_BLOCK_NAME} $ip 2>&1`
exists=${warn:0:4}
if [[ $exists != "Warn" ]]
then
${IPSET} -A ${BAN6_BLOCK_NAME} $ip >/dev/null 2>&1
fi
fi
done < ${TMP_FILE}
#
#Save log file
if [ ${LOGGING} = "on" ]
then
entries4=`${IPSET} list ${BAN4_BLOCK_NAME} | ${GREP} 'Number of entries' | ${CUT} -d ' ' -f 4 2>&1`
entries6=`${IPSET} list ${BAN6_BLOCK_NAME} | ${GREP} 'Number of entries' | ${CUT} -d ' ' -f 4 2>&1`
stamp=`${DATE} '+%Y.%m.%d %H:%M:%S'`
stoptime=`${DATE} +"%s"`
let "stoptime=stoptime+1"
worktime=$[ $stoptime - $starttime ]
${ECHO} "$stamp [${VER}] SET's: ${BAN4_BLOCK_NAME} has $entries4 entries, ${BAN6_BLOCK_NAME} has $entries6 entries (BANTIME=${BANTIME}). Work time is: $worktime s." >> ${LOGFILE}
#Install logrotate entry
if [ -d "/etc/logrotate.d" ]
then
if [ ! -f ${LOGROTATE} ]
then
${ECHO} "${LOGFILE}" > ${LOGROTATE}
${ECHO} "{" >> ${LOGROTATE}
${ECHO} " compress" >> ${LOGROTATE}
${ECHO} " missingok" >> ${LOGROTATE}
${ECHO} " notifempty" >> ${LOGROTATE}
${ECHO} " nocreate" >> ${LOGROTATE}
${ECHO} " rotate 4" >> ${LOGROTATE}
${ECHO} " weekly" >> ${LOGROTATE}
${ECHO} "}" >> ${LOGROTATE}
fi
fi
fi
#
${RM} -f ${TMP_FILE}
${RM} -f ${PIDFILE}
exit 0
#############################################################################################################################################
# Rules for your firewall: #
# IPv4 #
# iptables -A INPUT -m tcp -p tcp -m multiport --dports 25,110,143,465,587,993,995 -m set --match-set ${BAN4_BLOCK_NAME} src -j DROP #
# iptables -A INPUT -m udp -p udp -m multiport --dports 25,110,143,465,587,993,995 -m set --match-set ${BAN4_BLOCK_NAME} src -j DROP #
# iptables -A INPUT -m icmp -p icmp --icmp-type any -m set --match-set ${BAN4_BLOCK_NAME} src -j DROP #
#eg: #
# iptables -A INPUT -m tcp -p tcp -m multiport --dports 25,110,143,465,587,993,995 -m set --match-set IP4BLOCKMAIL src -j DROP #
# iptables -A INPUT -m udp -p udp -m multiport --dports 25,110,143,465,587,993,995 -m set --match-set IP4BLOCKMAIL src -j DROP #
# iptables -A INPUT -m icmp -p icmp --icmp-type any -m set --match-set IP4BLOCKMAIL src -j DROP #
# IPv6 #
# ip6tables -A INPUT -m tcp -p tcp -m multiport --dports 25,110,143,465,587,993,995 -m set --match-set ${BAN6_BLOCK_NAME} ${IPv6_BAN} #
# ip6tables -A INPUT -m udp -p udp -m multiport --dports 25,110,143,465,587,993,995 -m set --match-set ${BAN6_BLOCK_NAME} ${IPv6_BAN} #
# ip6tables -A INPUT -p ipv6-icmp --icmpv6-type echo-request ${BAN6_BLOCK_NAME} ${IPv6_BAN} #
# ip6tables -A INPUT -p ipv6-icmp --icmpv6-type echo-reply ${BAN6_BLOCK_NAME} ${IPv6_BAN} #
#############################################################################################################################################
#end
Ustawiamy ewentualnie zmienną IPSET - położenie tego pliku może być różne w poszczególnych wersjach systemu. Plikowi nadajemy atrybut wykonywalności. Powinien znajdować się na partycji montowanej bez parametru noexec. Rozsądnym jest uruchamiać ten plik z cron w jakimś wybranym przedziale czasowym (np. co 15 minut). Takie uruchomienie z cron spowoduje przeskanowanie loga exim i zaktualizowanie zbioru danychipset.
Pokazany wyżej skrypt bash, aktualizujący zbiór danych ipset możemy następnie wykorzystać do przyblokowania połączeń do portów poczty z zebranych z logu exim adresów IPv4.
iptables -A INPUT -i TWOJ_INTERFEJS_INTERNETOWY -m tcp -p tcp -m multiport --dports 25,110,143,465,587,993,995 \
-m set --set-match IP4BLOCKMAIL src -j REJECT --reject-with icmp-port-unreachable
iptables -A INPUT -i TWOJ_INTERFEJS_INTERNETOWY -m udp -p udp -m multiport --dports 25,110,143,465,587,993,995 \
-m set --set-match IP4BLOCKMAIL src -j REJECT --reject-with icmp-port-unreachable
iptables -A INPUT -i TWOJ_INTERFEJS_INTERNETOWY -p icmp -m icmp --icmp-type any \
-m set --set-match IP4BLOCKMAIL src -j REJECT --reject-with icmp-port-unreachable
Taka konstrukcja umożliwi blokowanie upierdliwych adresów IPv4.
Wykorzystanie ipset oraz cron do aktualizacji zbioru ipset, zapewni ciągłą aktualizację niechcianej adresacji oraz umożliwi iptableskorzystanie z tego zbioru bez konieczności restartu firewall.