Pull to refresh

Новая старая методика защиты от почтового спама на базе MTA Exim

System administration *
Sandbox
Хочу представить описание методики защиты корпоративной почты от спама, позволяющей использовать преимущества отдельных инструментов фильтрации адресов, избегая недостатков этих же методов.
Можно выделить, что эти приемы можно использовать на SMTP-прокси, закрывающем корпоративный почтовый сервер, находящийся в DMZ.

Зачастую администраторы избегают некоторых эффективных приемов фильтрации, из-за недостатков того или иного подхода. Например — фильтры DNSBL нередко дают ложные срабатывания на те узлы, которые попадают в него по ошибке — например, в составе всего блока адресов отдельного провайдера. Часто используемый способ фильтрации на основе простого определения PTR-записи тоже имеет свойство давать сбои в случаях, когда записи A и PTR — не совпадают, или просто возникли проблемы со службой DNS.

В этой статье я хочу показать, как разбивать отдельные способы фильтрации на более мелкие и оперировать фильтрацией по совокупности данных об отправляющем узле, а не только по результату одного запрещающего правила.

Данная методика существует давно, мне встречались разные реализации этой идеи разными специалистами, а эта вариация в более кратком виде была описана мною еще 5 лет назад в рассылке exim-users@exim.org (статью еще можно найти в архиве рассылки), но, несмотря на простоту реализации и наличие документации, сейчас они применяются почтовыми администраторами нечасто.

На примере почты компании «Horns'n'Hoofs» с доменом hornsnhoofs.com попробуем рассмотреть не выдуманные, а вполне работоспособные «в бою» приемы фильтрации.

Основная идея этой реализации состоит в том, что ни одна из проверок не является «критической», кроме собственного блэклиста сервера, хранимого в базе SQL. Другими словами — мы не отказываемся ни от использования DNSBL, ни от проверок на совпадение прямой и обратной DNS-записей ни от каких-либо других тестов на «спамность» хоста, но не запрещаем узлу продолжать SMTP-сессию, если он не прошел какую-то отдельную проверку (например, светится в DSNBL SpamHaus).

Каждый непройденный тест лишь добавляет некоторое количество баллов к «спамовости» письма, а принятие решения о приеме или отказе производится на основе итоговой суммы этих баллов в нескольких «контрольных точках» конфигурации. Подобный подход позволяет использовать множество инструментов для оценки почтовых отправителей и, вместе с этим, снизить уровень ложных срабатываний фильтра (т.н. false positives).
Статья подразумевает, что читатель способен установить и настроить exim в качестве принимающего сервера. Также расчитываю на то, что читатель умеет писать хотя бы несложные лукапы (lookups) для экзима.
Само собой, протокол SMTP надо знать любому грамотному админу, а принципы его работы отлично описаны в RFC 821, 2821 и 5321, переводы которых на русский язык можно легко найти в сети. Теоретическое описание многих способов защиты от спама можно найти в RFC 2505.

Итак, приступим к описанию конфигурации почтового сервера:
Сумма набранных баллов будет хранится в переменной $acl_c_spamscore. Это основная переменная в программе конфигурации, от её значения зависит всё остальное поведение MTA.

Сперва задаем ее исходное значение — 0. Например, в acl, отвечающим за проверку аргумента HELO MAIL FROM:
acl_check_sender:
     warn set acl_c_spamscore = 0
     [...]
     accept


UPD: спасибо пользователю slimlv, который заметил логическую ошибку.

Почему инициализация происходит не сразу при подключении? Очень просто — счетчик обнуляется также и при передаче команды RSET, перезапускающей SMTP-сессию. Если этого не делать, то количество очков осталось бы прежним и к нему бы суммировались новые очки за те же самые проверки, произведенные до команды RSET.
Это неудобно при отладке работы MTA, в боевой же системе не принципиально, где будет обнуляться счетчик — сразу при подключении узла (в acl_smtp_connect), или после передачи MAIL FROM, как в примере.

Ещё важна переменная $acl_c_bouncemessage, в которую складываются сообщения о результатах всех проверок. Она необходима для качественной отладки работы MTA. Вы будете сразу видеть в логе сервера, какие проверки не были пройдены и сколько очков набрано, а читающие почтовый отлуп админы на другом конце провода смогут понять, почему произошел разрыв SMTP-сессии и исправить ошибку (ага, размечтался; 95% из них, увы, только репу почешет). Впрочем, ложных срабатываний при правильно настроенной системе фильтрации так мало (у меня за последний год — всего одно), что я использую эту переменную только для собственного удобства при отладке.

Основная масса проверок заключена в секции acl_check_sender (фаза SMTP-сессии, наступающая после передачи «MAIL FROM:<email@address.any>»):

Начнем с начала:

acl_check_sender:
     [...]
&nbsp&nbsp&nbsp&nbsp warn  set acl_c_spamscore = 0
     drop hosts = +blacklisted_hosts
          message = Connection closed. IP [$sender_host_address] is listed in Blacklist.
     [...]
     accept


После того, как мы поимели в логе сервера запись, с какого email и ip-адреса производилась отправка письма, можно отключить (drop) узел, если он находится в локальном «черном списке» сервера (в MySQL-базе), чтобы не генерировать лишнего трафика дополнительными командами SMTP, а также DNS и DNSBL-запросами.

Сам блэклист представляет из себя таблицу в БД, состоящую из двух полей: IP varchar(15) и Timestamp int(11), где хранятся ip-адрес и время добавления его в базу в формате unix_time (в этом формате удобно проводить операции вычитания количества прошедших секунд):

Пример запроса к блэклисту очень прост: SELECT IP FROM antispam.blacklist WHERE IP='1.1.1.1' limit 1".

Работа с БД из почтового сервера реализуется тоже элементарно. Для этого в секцию основной конфигурации надо добавить директиву:

hostlist blacklisted_hosts = ${lookup mysql {SELECT IP FROM antispam.blacklist \
                                             WHERE IP='$sender_host_address' limit 1} \
                              }


Так мы формируем список из одного ip-адреса (или из нуля адресов, если запрос не вернул ничего), который в дальнейшем опрашиваем как +blacklisted_hosts в списках доступа.

Разумеется, нельзя забывать про директиву hide mysql_servers = 127.0.0.1/antispam/mta/mtapass в самом начале конфига, которая содержит параметры подключения к БД.

Наполнение таблицы blacklist ip-адресами происходит в автоматическом режиме самим MTA, что будет показано ниже.

А теперь смотрим антиспамовую «боеголовку»:

   warn !condition = ${lookup{$sender_address_domain}wildlsearch{/CONFIG_PREFIX/\
                              additional/trusted_zones}{1}{0}}
        set acl_c_spamscore = ${eval:$acl_c_spamscore+20}
        set acl_c_bouncemessage = $acl_c_bouncemessage Suspicious e-mail address;


Где trusted_zones является обычным текстовым файлом в директории additional в папке с конфигурацией экзима. Он содержит примерно следующее:

^.*\\.ru\$
^.*\\.ua\$
^.*\\.by\$
^.*\\.com\$
^.*\\.org\$
^.*\\.net\$
^.*\\.edu\$

Регулярные выражения описывают те доменные зоны, с email-адресов которых (DNS пока еще не при чём) обычно приходит корреспонденция. Пример выше содержит необходимый минимум, который при необходимости можно и нужно редактировать.

Директива warn указывает экзиму, что письмо не нужно ни принимать, ни отвергать. Необходимо лишь выполнить условие и обрабатывать сессию дальше.

Из этого получается, что узел, отсылающий письмо с нехарактерной для входящей почты доменной зоной в адресе отправителя (не путать с заголовком «From:» тела письма) получит 20 очков и двинется дальше:

#-----------------------------DNS Records verify------------------------------------
     warn !verify = reverse_host_lookup
          set acl_c_spamscore = ${eval:$acl_c_spamscore+30}
          set acl_c_bouncemessage = $acl_c_bouncemessage Reverse host lookup failed;

+30 очков — за несовпадение прямой (A) и обратной (PTR) DNS-записей.

     warn condition = ${if eq {$acl_c_reverse_zone}{}}
          set acl_c_spamscore = ${eval:$acl_c_spamscore+50}
          set acl_c_bouncemessage = $acl_c_bouncemessage No DNS PTR record found;


Ещё 50 — за отсутствие обратной (PTR) записи.
Восмидесяти очков уже вполне достаточно, чтобы посадить узел в «серый список» (greylist), как будет показано далее.

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


Можно заметить, что здесь появилась переменная $acl_c_reverse_zone, которая содержит результат (значение DNS PTR узла) вот такой проверки:

          set acl_c_reverse_zone = ${escape:${lookup dnsdb{ptr=$sender_host_address}}}

Её значение можно устанавливать при подключении узла (в acl_smtp_connect, что более правильно) или при проверке аргумента HELO в acl_check_helo там, где
инициализуется $acl_c_spamscore. Разница небольшая. Можно вообще везде использовать конструкцию:

     warn condition = ${if eq {${escape:${lookup dnsdb{ptr=$sender_host_address}}}}\
                                                      {сравниваемоезначение}}


Но следует учесть, что при каждом таком лукапе будет генерироваться DNS-запрос. При большом потоке почты (спама) это будет создавать лишние нагрузки. При малом же потоке Вы вряд ли вообще почувствуете разницу.

#-----------------------------Dynamic IP pools processing---------------------------
     warn condition = ${lookup {$acl_c_reverse_zone}wildlsearch{CONFIG_PREFIX/\
                                additional/dynamic_pools}{1}{0}\
                       }
          set acl_c_spamscore = ${eval:$acl_c_spamscore+70}
          set acl_c_bouncemessage = $acl_c_bouncemessage Suspected PTR DNS record \
                                    points to dynamic IP pool;


+70 очков отправляющий узел получит, если его DNS-запись указывает на какой-либо динамический пул адресов, т.к. такие пулы — питательная среда для вирусов и, как следствие — хорошая почва для ботнетов.

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


Файл dynamic_pools по структуре похож на файл trusted_zones и содержит регулярные выражения для проверки DNS-записей для модемщиков, adslщиков и других узлов с динамически выделяемыми ip:

^.*([0-9]+).([0-9]+).([0-9]+).([0-9]+).*
^.*host.([0-9]+).*
^.*dynamic.*
^.*dial.*
^.*ppp.*
^.*pptp.*
^.*broadband.*
^.*dhcp.*

Сюда можно добавить и свои правила, это лишь необходимый минимум.

#---------------------------Geographical DNS zone processing------------------------
     warn !condition = ${lookup {$sender_host_name}wildlsearch{/CONFIG_PREFIX/\
                                 additional/trusted_zones}{1}{0}}
          set acl_c_spamscore  = ${eval:$acl_c_spamscore+20}
          set acl_c_bouncemessage = $acl_c_bouncemessage Untrusted domain zone;


Тот же trusted_zones, использованный ранее для проверки emailов, теперь применяется для фильтрации по реальным DNS-записям.

+20 очков, если почта идет с Китайской, Мексиканской, Корейской и прочих, не перечисленных в списке доменных зон.

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

#-------------------Huge DSL & DialUp ISP's DNS zone processing---------------------
     warn condition = ${lookup {$sender_host_name}wildlsearch{CONFIG_PREFIX/\
                                additional/spamvertised_isp}{1}{0}}
          set acl_c_spamscore = ${eval:$acl_c_spamscore+40}
          set acl_c_bouncemessage = $acl_c_bouncemessage Spamvertised ISP DNS zone;


+40 баллов отдельным крупным провайдерам, не заботящимся о фильтрации исходящего трафика, генерируемого ботнетами.

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


В файле spamvertised_isp перечислены многие крупные провайдеры, разрешающие своим модемщикам совершать исходящие подключения на 25 порт (вычисляются по логам):

^.*comcast\\.net
^.*pppoe\\.mtu-net\\.ru
^.*qwerty\\.ru
^.*ono\\.com
^.*virtua\\.com\\.br


Можно дописать и свои записи. Даже нужно.

#----------------------------Handler for impossible HELO's-------------------------
     warn condition = ${if or {\
                                  {match{$sender_helo_name}{localhost}}\
                                  {match{$sender_helo_name}{mail.hornsnhoofs.com}}\
                                  {match{$sender_helo_name}{^127\\.0\\.0\\.([0-9]+)}}\
                              }{1}{0}\
                       }
          set acl_c_spamscore = ${eval:$acl_c_spamscore+60}
          set acl_c_bouncemessage = $acl_c_bouncemessage HELO $sender_helo_name is forged;


+60 очков спамерам, указывающим в качестве аргумента HELO наш почтовый сервер (принимающий).

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

#------------------------------Handler for wrong HELO's----------------------------
     warn !condition = ${if or {\
                                  {match{$sender_helo_name}{^.+\\.((?i)[a-z]+)\$}}\
                                  [...]
                               }{1}{0}\
                        }
          set acl_c_spamscore = ${eval:$acl_c_spamscore+20}
          set acl_c_bouncemessage = $acl_c_bouncemessage HELO name is not \
                                    Fully Qualified Domain Name;


+20 спамовых баллов, если HELO не является FQDN.

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

#--------------------------Handler for forged HELO arguments-----------------------
     warn !condition = ${if or {\
                                  {eq{$sender_helo_name}{$sender_host_name}}\
                               }\
                        }
          set acl_c_spamscore = ${eval:$acl_c_spamscore+20}
          set acl_c_bouncemessage = $acl_c_bouncemessage HELO not equals Hostname;


Ещё 20, если аргумент HELO не совпадает с основной DNS-записью (A) для отправляющего узла.

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

#------------------------Handler for suspicious HELO arguments---------------------
     warn !condition = ${lookup {$sender_helo_name}wildlsearch{/CONFIG_PREFIX/\
                                 additional/trusted_zones}{1}{0}}
          set acl_c_spamscore = ${eval:$acl_c_spamscore+20}
          set acl_c_bouncemessage = $acl_c_bouncemessage Suspicious HELO argument;



Старый добрый trusted_zones теперь используется для проверки аргумента HELO.
+20 очков китайцам, корейцам и прочим мексиканским японцам.
#----------------------------------------------------------------------------------


И наконец — правильная реализация опроса систем DNSBL:

Ни в коем случе нельзя отключать узел, который светится только в одной такой системе. Высока вероятность того, что он попал туда случайно.

Не стоит совсем отказываться от использования DNSBL — это мощный инструмент фильтрации.

Только при одновременном попадании в две и более таких системы можно объявлять узел рассыльщиком спама.

#-------------------------------DNSBL processing section---------------------------
     warn dnslists = sbl.spamhaus.org
          set acl_c_spamscore = ${eval:$acl_c_spamscore+60}
          set acl_c_bouncemessage = $acl_c_bouncemessage Listed in DNSBL $dnslist_domain;

     warn dnslists = bl.spamcop.net
          set acl_c_spamscore = ${eval:$acl_c_spamscore+60}
          set acl_c_bouncemessage = $acl_c_bouncemessage Listed in DNSBL $dnslist_domain;

     warn dnslists = dnsbl.sorbs.net
          set acl_c_spamscore = ${eval:$acl_c_spamscore+60}
          set acl_c_bouncemessage = $acl_c_bouncemessage Listed in DNSBL $dnslist_domain;

     warn dnslists = dul.ru
          set acl_c_spamscore = ${eval:$acl_c_spamscore+60}
          set acl_c_bouncemessage = $acl_c_bouncemessage Listed in DNSBL $dnslist_domain;


+60 очков за попадание в любой из «черных списков DNS». При одновременном попадании в два списка узел получит 120 очков, чего вполне достаточно, чтобы прекратить прием почты от него, но недостаточно для автоматического добавления в локальный блэклист. Если админ отправляющего почтового сервера успеет оперативно отписаться хотя бы от одного DNSBL, то передача почты пострадает минимально.

Опрос DNSBL в сочетании с остальными проверками — отсеивает спамеров очень хорошо и зачастую именно эти очки становятся решающими для помещения ip-адреса в локальный блэклист на неделю.

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

     warn !verify = sender/callout=3m,defer_ok
          set acl_c_spamscore = ${eval:$acl_c_spamscore+60}
          set acl_c_bouncemessage = $acl_c_bouncemessage Cannot complete sender verify;


Здесь производится callout (проверка существования ящика отправителя). Серверу дано максимум 3 минуты на проверку (иначе есть риск, что «хороший» отправитель отвалится, не дождавшись окончания проверки, он ведь висит подключенным), недоступность удаленного узла считается за успешное прохождение проверки. Т.е. 60 очков узел получит, если отправлял почту с отсутствующего (чаще всего — поддельного) email-адреса.

Именно поэтому не стоит делать легитимные рассылки с адресов вроде www@webserver.example.org. Callout — распространенный способ фильтрации, и поддерживается многими современными реализациями MTA.

     accept condition = ${if >{$acl_c_spamscore}{145}}

Тут маленькая хитрость — узлы с более чем 145 очками — верные кандидаты в локальный блэклист. Незачем их больше проверять, передаем их в следующий acl и там крепко баним.

     accept delay = ${eval:$acl_c_spamscore/2}s


Кто не набрал 145 очков — проходит пыточку задержкой сессии: принимающий MTA имитирует плохой коннект и «подвисает» на количество секунд, равное половине количества «спамовых очков». Скажем, узел, набравший 60 очков будет «подвешен» на 30 секунд.

Обычно у рассыльщиков спама нет такого количества времени на ожидание ответа и они отваливаются секунд через 15-20.

Вот мы и дошли до последнего acl, проверяющего правильность параметров SMTP-сессии (срабатывает после RCPT TO:). В нем и происходит «взвес» суммы баллов и решение дальнейшей судьбы письма:

acl_check_rcpt:
[...]
#------Spamtraps check-------
     warn condition = ${lookup {$local_part@$domain}lsearch{/CONFIG_PREFIX/\
                        additional/spamtraps}{1}{0}}
          set acl_c_spamscore = ${eval:$acl_c_spamscore+50}
          set acl_c_bouncemessage = $acl_c_bouncemessage Spamtrap hit;


+50 очков за попадание в спам-ловушку.

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


Список адресов-ловушек находится в файле spamtraps, по emailу на строку:

spamtrap@hornsnhoofs.com
honeypot@hornsnhoofs.com


И так далее.

Использование собственных спам-ловушек — хороший способ прикрыться от крупных рассыльщиков, роющих интернеты ботами на предмет emailов. Потом эти адреса передаются, продаются и просто раздаются в составе баз для рассылок. То есть — распространяются спамерами между собой.

Сложность возникает в первоначальном распространении таких адресов, чтобы именно они попали к спамерам. Например, для этого можно использовать такой механизм, как wpoison на основном сайте компании. Главное — не забыть про запрет их индексации поисковиками (через robots.txt или META CONTENT=«NOINDEX, NOFOLLOW»).

Зачастую старые домены уже имеют заброшенные или даже никогда не существовавшие адреса, на которые, тем не менее, идет один спам. Это идеальные кандидаты для использования в качестве spamtraps. Главное — чтобы таким ящиком не пользовались продолжительное время, а еще лучше, чтобы им не пользовались никогда.

Парадоксально, но факт — многие из ныне используемых мной адресов-ловушек никогда и нигде не существовали, кроме как в спамерских базах. Как они там появились, неизвестно, но спам на них идет регулярно. Изучите логи своего почтовика за продолжительный период, наверняка там найдётся что-то похожее.

#---------------------------Blacklist Processing Section------------------------
     drop !senders = :
          !condition = ${if <{$acl_c_spamscore}{150}}
          message = Connection closed. Spamscore threshold (150 points) reached. \
                    Spamscore is $acl_c_spamscore! \
                    Warning: IP [$sender_host_address] added to Blacklist. \
                    Details: $acl_c_bouncemessage
          condition = ${lookup mysql \
                          {\
                               insert into antispam.blacklist (IP,Timestamp)\
                               values ('${sender_host_address}',${tod_epoch});\
                          }\
                       }


Те, кто смог набрать 150 очков и больше — объявляются чемпионами! Их ip-адреса помещаются в локальный блэклист и слава о них не смолкает неделю.
Узлы, находящиеся в нем, дропаются сразу после передачи команды «MAIL FROM:».

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


Как уже было сказано — срок жизни записи в моем блэклисте — неделя (604800 секунд). Базы чистятся кроном каждый час:

#!/bin/bash
echo "delete from blacklist where Timestamp < `echo "\`date +%s\`-604800" | bc`;" | /usr/local/bin/mysql -u mta -pmtapass antispam
echo "optimize table blacklist" | /usr/local/bin/mysql -u mta -pmtapass antispam

     deny condition = ${if >{$acl_c_spamscore}{100}}
          condition = ${if ={$acl_c_validrcpt}{1}}
          message = Message rejected. Spamscore threshold (100 points) reached. \
                    Spamscore is $acl_c_spamscore! Details: acl_c_bouncemessage


Тут все просто. Набрал больше 100 очков — получил «550 Message rejected.». При этом в лог записывается количество набраных очков и отчеты о заваленных проверках. Эта же информация уходит вместе с почтовым отлупом на ту сторону провода. Вдруг кому пригодится для отладки.

Важно передавать отлуп, не разрывая сессию (не доводя письмо до приема в acl и начала обработки роутерами и транспортами), в противном случае события могут развернуться не очень хорошо для Вас: в худшем случае Ваш сервер смогут использовать как рассыльщика «спам-отдачи».

Вкратце про пересылку спама в виде отдачи: если написать письмо со спамом на несуществующий email-адрес nosuchaddress@hornsnhoofs.com, указывая в «MAIL FROM:» адрес vasyapupkin@gmail.com, и письмо будет принято почтовым сервером, а уже после приема почтовый роутер определит, что адрес назначения не существует, то сервер обернет письмо со спамом в новый конвертик, добавит строки своего отлупа и вернет его «отправителю» — ничего не подозревающему Васе Пупкину на Gmail.com.

Далее корпорация бабла быстро добавит Ваш сервер в «черный список» и блокирует прием почты с его ip. Теперь Вы пишете оправдательные письма в техсаппорт, а мерзенькие спамеры хихикают.

Впрочем, это очень краткое описание явления и существуют другие способы защититься от него, но тактика передачи отлупа, не прекращая сессию — самая верная.

Ещё можно добавлять эту инфу в служебные заголовки письма, для более удобной отладки. Впрочем это весьма опционально.
     accept condition = ${if <{$acl_c_spamscore}{70}}
            condition = ${if ={$acl_c_validrcpt}{1}}

Узлы, набравшие менее 70 спамовых очков, считаются легитимными, мы принимаем от них письмо, если адрес получателя ($acl_c_validrcpt) существует.

Не буду описывать, как эта переменная получает значение, т.к. это зависит от способа хранения данных о почтовых пользователях. Скажу, что значение true (или просто 1) у меня она получает после проверки почтового адреса получателя в Active Directory посредством LDAP-поиска. Это можно делать для любой базы данных с пользователями, лишь бы она поддерживалась экзимом.

Самое интересное происходит с узлами, набравшими от 70 до 100 очков. Их мы не можем отнести ни к легитимным хостам, ни к рассыльщикам спама. Поэтому заворачиваем их в грейлист на 29 минут (интервал выбран с расчетом на второй прогон почтовой очереди MTA-отправителем):

#--------------------------Greylist Processing Section--------------------------
     defer condition = ${if ={$acl_c_validrcpt}{1}}
           condition = ${lookup mysql \
                           {\
                               select Source from antispam.greylist where \
                               Source='$sender_host_address' \
                               and Timestamp > ${eval:$tod_epoch-1740} limit 1\
                           }{1}{0}\
                        }
           message = Message deferred. Try again later. You was been already greylisted.

     accept condition = ${lookup {$acl_c_reverse_zone}wildlsearch{CONFIG_PREFIX/\
                          additional/dynamic_pools}{1}{0}}
            condition = ${if ={$acl_c_validrcpt}{1}}
            condition = ${lookup mysql \
                           {\
                               select Source from antispam.greylist where \
                               Source='$sender_host_address' \
                               and grey_hash = '${md5:${lc:$sender_address\
                                                      $local_part@$domain}}' \
                               and Timestamp < ${eval:$tod_epoch-1740} limit 1 \
                           }{1}{0}\
                         }
            condition = ${lookup mysql \
                           {\
                               delete from antispam.greylist where \
                               Source='$sender_host_address' \
                               and grey_hash = '${md5:${lc:$sender_address\
                                                      $local_part@$domain}}' \
                           }\
                         }

     accept !condition = ${lookup {$acl_c_reverse_zone}wildlsearch{CONFIG_PREFIX/\
                          additional/dynamic_pools}{1}{0}}
            condition = ${if ={$acl_c_validrcpt}{1}}
            condition = ${lookup mysql \
                           {\
                               select Source from antispam.greylist where \
                               Source='$sender_host_address' \ 
                               and grey_hash = '${md5:${lc:$sender_address\
                                                      $local_part@$domain}}' \
                               and Timestamp < ${eval:$tod_epoch-1740} limit 1 \
                           }{1}{0}\
                         }
            condition = ${lookup mysql \
                           {\
                               insert into antispam.whitelist (IP, Timestamp) \
                               values ('$sender_host_address',$tod_epoch) \
                           }{1}{1}\
                         }
            condition = ${lookup mysql \
                           {\
                               delete from antispam.greylist where \
                               Source='$sender_host_address' \ 
                               and grey_hash = '${md5:${lc:$sender_address\
                                                       $local_part@$domain}}' \
                           }\
                         }

     defer condition = ${if ={$acl_c_validrcpt}{1}}
           condition = ${if >={$acl_c_spamscore}{70}}
           condition = ${lookup mysql \
                           {\
                               insert into antispam.greylist (Source,grey_hash,Timestamp) \
                               values ('$sender_host_address',\
                               '${md5:${lc:$sender_address$local_part@$domain}}',\
                               ${tod_epoch}); \
                           }\
                         }
           message = Message deferred. Spamscore is $acl_c_spamscore! Try again later. \
                     Greylisting in progress. Details: $acl_c_bouncemessage
#-----------------------------------------------------------------------------

Директива defer указывает экзиму, что cледует вернуть ошибку 4xx, заставляя удаленный сервер оставить письмо в почтовой очереди и вернуться к нему позже, при её повторной обработке.

Логика работы всей секции аналогична любому другому модулю для грейлистинга для любого почтовика: SMTP-ответ с кодом временной ошибки 451 в течение указанного промежутка времени, затем разблокировка по таймауту и внесение в «белый список», если в новом письме указан тот же получатель.

За исключением маленького «но», наглядно демонстрирующего гибкость экзима: узлы с ip-адресами из динамических пулов проходят грейлистинг каждый раз заново.

Для хранения данных, обеспечивающих работоспособность грейлистинга, в БД существует таблица greylist, состоящая из трех полей: Source varchar(15), grey_hash varchar(32), и Timestamp int(11) . Поле Source содержит ip-адрес отправителя, grey_hash — хеш md5 от адресов отправителя и получателя, а Timestamp — время добавления записи в формате unix_time. Помимо неё, грейлистингу требуется и таблица whitelist, позволяющая складывать туда адреса узлов, прошедших грейлистинг. Формат таблицы whitelist полностью повторяет таковой для таблицы blacklist, описанной выше.
Таблицы также очищаются кроном: в greylist записи хранятся сутки, в whitelist — месяц.

     deny message = Message rejected. No such user here. Relaying denied. $acl_c_support
          set acl_c_spamscore = ${eval:$acl_c_spamscore+5}
          set acl_c_bouncemessage = $acl_c_bouncemessage RCPT Fail;
          delay = ${eval:$acl_c_spamscore/2}s



Последнее правило фильтрации, по рекомендациям лучших собаководов — запрещающее. Его работа в чем-то похожа на последнее правило в «закрытом» файрволле deny ip from any to any, только затрагивает исключительно SMTP-сессию.

Дополнительно — за каждый неправильный адрес в «RCPT TO:» (протокол SMTP позволяет указывать несколько получателей для одного письма) узел получает по 5 спам-очков дополнительно, вплоть до достижения порога безусловной блокировки.

Ах, да — пыточка задержкой сессии после каждого «RCPT TO:» тоже имеет место быть.

Использование хотя бы этих конструкций в конфигурации почтового сервера срежет около 90-95% спама без увеличения количества ложных срабатываний.

Творческое самостоятельное использование этой методики (подсчета и взвешивания спам-коэффициентов) позволяет избавится от спама почти полностью (за исключением, пожалуй, рассылок с публичных почтовых систем типа gmail.com или mail.ru, но там за этим, как правило, следят и аккаунт блокируют) без использования систем контекстной фильтрации.

А если контекстный фильтр также планируется использовать, то таким способом можно разгрузить текстовый анализатор за счет сильного уменьшения количества входных данных.

В статье я не касался таких технологий, как SPF и DKIM, но при желании можно использовать их, как «обеляющие функции» (уменьшающие количество спам-баллов), работающие аналогично тем, которые были рассмотрены. Если у сообщества возникнет интерес к этим системам, то постараюсь рассмотреть их отдельно.
Tags:
Hubs:
Total votes 33: ↑33 and ↓0 +33
Views 27K
Comments Comments 24