Борьба со спамом на почтовых серверах


Внимание!
В данной статье под термином "спам" следует понимать не только сам спам, но и вирусы, которые то же являются нежелательной корреспонденцией, и рассылаются в основном тем же способом что и большая часть спама.

Имена хостов и IP-адреса приведенные после слова "например:" - взяты наугад, и вовсе не обязательно указывают на спамера!

Приводимые здесь листинги довольно хорошо отлажены и рассчитаны на типичные конфигурации, но все же не стоит тупо копировать, вполне вероятно что именно в вашей конфигурации почтовой системы что то будет работать не совсем так как описано, используйте:
/dev/head
/dev/hands
/usr/share/sendmail/cf/README
и конечно же man... ;-)
Не забывайте тестировать!


Введение
От применения контентных фильтров (рассматривающих содержание тела письма) сразу было решено отказаться, по той причине, что фильтрация почты идет для большого количества разношерстных клиентов. В том числе среди них есть несколько рекламных и кадровых агентств, где контентный фильтр порубил бы большое количество честной почты как явный спам.
Кроме того, понятие спама у каждого свое, что одному - спам, то другому - не спам, на всех тут не угодишь, и одно необоснованно зарубленное честное письмо, вызывает гораздо больше нареканий чем 10 писем пропущенного спама.
Вдобавок, у контентных фильтров есть один большой недостаток, необходимо полностью принять тело письма, а доля спама (и вирусов) сейчас представляет большую часть почтового трафика, кто будет за это платить?
Да и вообще, зафильтровать ВЕСЬ спам невозможно, но вот заметно уменьшить его количество можно.
По этим причинам упор был сделан на фильтрацию не собственно спама, а его рассыльщиков.

В результате анализа источников спама было выявлено, что подавляющее большинство рассылается не совсем легальным путем, то есть, с клиентской машины непосредственно на SMTP сервер получателя, минуя SMTP сервер отправителя, что противоречит рекомендации RFC 821/2821 и общепринятым нормам, в этом направлении и пошла основная работа.

Хочу сразу оговорится, здесь приведены примеры для конфигурации FreeBSD + sendmail, однако принципы вполне переносимы на другие почтовые платформы.


1. Резольвинг и имя хоста
Подавляющее большинство спама рассылается с DSL/DialUp, это и стало первым и главным эшелоном обороны.
Типичные признаки DSL/DialUp:
1. Отсутствие в DNS записи PTR (невозможность определить имя хоста по его IP-адресу).
2. Присутствие записи PTR, но отсутствие записи A (имя хоста определено, но для этого имени невозможно определить IP-адрес).
3. Несовпадение информации в PTR а A (проход по цепочке PTR - A привел не к тому IP-адресу с которого мы начинали).
4. Типичное для DSL/DialUP имя хоста (много цифр, разделителей, типичные слова ограниченные цифрой или разделителем), например:
233-165-189.xdsl-dinamico.ctbcnetsuper.com.br
213.160.133.20.ldc.net
dsl-201-135-0-138.prod-infinitum.com.mx
ppp12-55.pppoe.mtu-net.ru
client-200.106.106.48.speedy.net.pe
ACC0D88C.ipt.aol.com

Совпадение PTR и A нужно проверять обязательно, так как система DNS позволяет писать в PTR все что угодно, хоть microsoft.com ;-)
А это значит, что информация из PTR не является гарантировано достоверной, и требует подтверждения через A, например с нами соединились с IP-адреса 84.9.30.216:

# host -t PTR 84.9.30.216
216.30.9.84.IN-ADDR.ARPA domain name pointer example.com

# host -t A example.com
example.com has address 192.0.34.166
Итак, 192.0.34.166 != 84.9.30.216 - в приеме почты отказываем.

Реально у каждого PTR и A может быть несколько значений, к тому же могут участвовать записи CNAME, но нам в данном случае это не особо интересно, так как это проблема разработчиков sendmail, и они с ней успешно справляются. Мы рассматриваем только результат работы резольвера sendmail, который нам любезно предоставлен в переменной $&{client_resolve}.

LOCAL_RULESETS

SLocal_check_relay

R$*			$: < $&{client_resolve} >
R<TEMP>			$#error $@ 4.7.1 $: "450 Relaying temporarily denied. IP name lookup failed for " $&{client_addr}
R<FAIL>			$#error $@ 5.7.1 $: "550 Relaying denied. IP name lookup failed for " $&{client_addr}
R<FORGED>		$#error $@ 5.7.1 $: "550 Relaying denied. IP name forged (PTR and A records mismatch) for " $&{client_addr}
После отсечения таким образом DSL/DialUp хостов подпадающих под пункты 1, 2, 3 - переходим к пункту 4 (этот листинг для отображения правильной компоновки включает в себя предыдущий, все это нужно располагать в конце semdmail.mc).

LOCAL_CONFIG

Knondsl1		regex -a@MATCH (^|[0-9.-])(mail|mailrelay|mta|mx|relay|smtp)[0-9.-]
Knondsl2		regex -a@MATCH \.(hotmail\.com|rax\.ru|ip\.net\.ua)$

Kdsl1			regex -a@MATCH ([0-9].*){5,}
Kdsl2			regex -a@MATCH (^|[0-9.-])([axv]dsl|as|bgp|broadband|cable|[ck]lient|dhcp|dial|dialin|dialup|dialer|dip|dsl|dslam|dup|dyn|dynamic|host|ip|isdn|modem|nas|node|pool|ppp|pppo[ae]|sirius.*ukrtel.*|user|users|vpn)[0-9.-]
Kdsl3			regex -a@MATCH [0-9a-f]{8,}
Kdsl4			regex -a@MATCH (^|\.)[0-9]*[.-]
Kdsl5			regex -a@MATCH (-.*){3,}
Kdsl6			regex -a@MATCH \.(ipt\.aol\.com|internetdsl\.tpnet\.pl|rr\.com|pppool\.de|adelphia\.net|osnanet\.de|dedicado\.com\.uy)$


LOCAL_RULESETS

SLocal_check_relay

R$*			$: < $&{client_resolve} >
R<TEMP>			$#error $@ 4.7.1 $: "450 Relaying temporarily denied. IP name lookup failed for " $&{client_addr}
R<FAIL>			$#error $@ 5.7.1 $: "550 Relaying denied. IP name lookup failed for " $&{client_addr}
R<FORGED>		$#error $@ 5.7.1 $: "550 Relaying denied. IP name forged (PTR and A records mismatch) for " $&{client_addr}

R$*			$: $(nondsl1 $&{client_name} $)
R@MATCH			$@ OK
R$*			$: $(nondsl2 $&{client_name} $)
R@MATCH			$@ OK

R$*			$: $(dsl1 $&{client_name} $)
R@MATCH			$#error $@ 5.7.1 $: "554 DSL or DialUp sender " $&{client_name} " [" $&{client_addr} "] (1), please use Provider SMTP"
R$*			$: $(dsl2 $&{client_name} $)
R@MATCH			$#error $@ 5.7.1 $: "554 DSL or DialUp sender " $&{client_name} " [" $&{client_addr} "] (2), please use Provider SMTP"
R$*			$: $(dsl3 $&{client_name} $)
R@MATCH			$#error $@ 5.7.1 $: "554 DSL or DialUp sender " $&{client_name} " [" $&{client_addr} "] (3), please use Provider SMTP"
R$*			$: $(dsl4 $&{client_name} $)
R@MATCH			$#error $@ 5.7.1 $: "554 DSL or DialUp sender " $&{client_name} " [" $&{client_addr} "] (4), please use Provider SMTP"
R$*			$: $(dsl5 $&{client_name} $)
R@MATCH			$#error $@ 5.7.1 $: "554 DSL or DialUp sender " $&{client_name} " [" $&{client_addr} "] (5), please use Provider SMTP"
R$*			$: $(dsl6 $&{client_name} $)
R@MATCH			$#error $@ 5.7.1 $: "554 DSL or DialUp sender " $&{client_name} " [" $&{client_addr} "] (6), please use Provider SMTP"
Обратите внимание!
Правая и левая часть выражений должна быть разделена не пробелами а ТАБУЛЯЦИЕЙ!
Хосты вашей сети должны проходить все эти проверки!
Иначе нужно добавить свой домен в список Knondsl2 или в самом начале SLocal_check_relay вставить для них обход всех проверок.

Описанные в этом разделе проверки, ранее были реализованы в milter-regex, однако перекочевали в sendmail из за желания сэкономить (раз в 10) трафик проверок по RBL, а так же, невозможности в milter-regex корректно обработать результат резольвинга.
Вы можете использовать SLocal_check_rcpt вместо SLocal_check_relay, это позволит видеть в логах E-Mail адреса отправителя и получателя, однако учтите, что проверки по RBL и milter-фильтрам будут происходить раньше, соответственно значительно (в 10...30 раз) увеличивать ненужный трафик.

Политические аспекты:
Админов честных почтовиков не проходящих резольвинг - можно смело посылать править свои зоны. Я сам когда то нарвался на подобный отлуп с sms.umc.com.ua ;-) Да и думаю что многие фильтры используют резольвинг если не так жестко, то по крайней мере как один из критериев "спамности".
Некоторые крупные почтовики могут слишком походить на DSL/DialUp, и наоборот, некоторые DSL/DialUp не очень то на себя похожи, здесь нет "серой зоны", используйте Knondsl2 и Kdsl6 в случае ошибок.


2. RBL (dnsbl)
Это службы ведущие "черные списки" открытых релаев и рассыльщиков спама, некоторые (как например dul.ru) содержат списки IP-адресов модемных пулов части Российских (и не только) провайдеров.
Некоторые RBL имеют несколько баз содержащих разные категории Интернет-сброда.
В любом случае, не стоит принимать почту от IP-адреса имеющегося хотя бы в одной RBL.
Однако помните, что у каждой RBL своя политика занесения и удаления хостов, у кого то она может быть слишком жесткой, у кого то слишком мягкой. Часть этих систем бесплатная, часть платная.
Читайте правила самой RBL и отзывы о ее использовании.
Некоторые могут быть отвергнуты по причине отсутствия срабатываний, для проверки всегда помещайте новую RBL в начало списка.
Здесь приведены те системы, которые удовлетворяют меня на данный момент. Итак, вносим в sendmail.mc, в конец того места где у вас определены FEATURE:

FEATURE(`dnsbl', `list.dsbl.org', `"Yours IP " $&{client_addr} " are BlackListed by http://dsbl.org"')dnl
FEATURE(`dnsbl', `bl.spamcop.net', `"Spam blocked see: http://spamcop.net/bl.shtml?"$&{client_addr}')dnl
FEATURE(`dnsbl', `dul.ru', `"Yours IP " $&{client_addr} " are BlackListed by http://www.dul.ru"')dnl
Зона dul.ru по моим наблюдениям меняется редко, если у вас довольно большой почтовый трафик - ее имеет смысл "засекондарить", то есть, прописать в настройках своего bind "вторичный DNS", это сэкономит трафик и уменьшит время проверки. Например на момент написания статьи размер файла зоны составлял 630кБ, это трафик всего пары тысяч запросов, последний раз она менялась 3 месяца назад.
Такая статичность зоны dul.ru определяется ее назначением, и она в этом смысле почти уникальна.
Но не стоит это делать с крупными динамичными RBL, во первых вы проиграете по трафику многократно, во вторых многие из них не разрешают трансфер зоны.
Пример для named.conf:

acl local-host {
    127.0.0.0/8;
};

zone "dul.ru" {
	type slave;
	file "m/slaves/dul.ru";
	masters {
	    194.87.0.31;
	};
	allow-query {
	    local-host;
	};
	allow-transfer {
	    local-host;
	};
};
Политические аспекты:
В RBL (dul.ru - отдельный вопрос) просто так не попадают, для этого как правило нужно быть довольно мерзкой сволочью или законченным разгильдяем.
По поводу отлупов на основе информации из RBL - лучшее решение - отправлять просителя к администрации этой RBL, и не утруждать себя допросами как и за что он туда попал.
Обратите внимание, некоторые RBL могут блокировать не только IP-адреса с которых непосредственно пришел спам, но и всю сеть или AS, или IP-адреса тех кто предоставляет почтовый или Веб сервис известным спамерам. Так же возможна блокировка IP-адресов с которых замечена рассылка вирусов. Всегда читайте политику RBL и отзывы о ней перед ее использованием.


3. access_db и virtusertable
В access_db комментировать вроде бы нечего, все прекрасно знают что это такое, не забывайте использовать.
Лично я туда вношу "честных" спамеров (которые не подделывают адрес отправителя, в США например с некоторых пор это УГОЛОВНОЕ преступление). Если спамит конкретный пользователь какого то провайдера или публичного почтовика - вношу E-Mail, если спамит администрация домена - весь домен, например там у меня в том числе прописаны adamant.ua, adamant.net, colocall.net - народ знает своих "героев" ;-)

По поводу virtusertable есть одно замечание, если конечно вы его используете...
Не оставляйте дыр вирусам и спамерам в виде "открытого домена" в котором есть получатель "по умолчанию". Вирусы часто шлют себя на случайные адреса в существующих доменах, вроде 06b62866.55510507@example.com или используя словарь человеческих имен, например fred@example.com в достаточно обоснованной надежде что какой ни будь Фред там наверняка найдется.
Кроме того, спамеры часто используют в качестве адреса отправителя случайные имена пользователей в открытом домене, например mndfuy5jhf@example.com это затрудняет их блокировку и делает бесполезной проверку существования адреса отправителя, что вызовет у некоторой части админов вполне понятный зуд в руках на тему блокировки всего вашего домена, так понравившегося спамерам. Я сам по этой причине заблокировал пару публичных почтовиков.
Пример правильного ведения virtusertable:

fred@example.com	example-fred
bob@example.com		example-bob
postmaster@example.com	example-postmaster
@example.com		error:5.1.1:550 User unknown
Кстати, это же замечание касается всяких "smart host" и "UUCP relay", но тут трудно что либо поделать, MTA получателя может не знать списка пользователей :-(
Хотя с появлением SPF эту дыру можно закрыть.


4. milter-sender и подобные
Смысл этих фильтров - удостоверится в существовании E-Mail адреса отправителя, что бы лишить спамера возможности влить вам спам от имени несуществующих E-Mail адресов. Согласно RFC 821/2821 адрес отправителя предъявляемый в команде "MAIL FROM:" SMTP-сессии обязан существовать, что бы на него можно было доставить сообщение о доставке/недоставке/ошибке (Delivery notification), да и просто странно выглядело бы письмо, если на него невозможно ответить стандартным нажатием кнопки почтового клиента.
Пока это достаточно эффективный способ борьбы со спамом, так как далеко не все спамерские и вирусные SMTP движки учитывают такую особенность, да и далеко не все почтовые сервера утруждают себя такой проверкой, что дает спамерам возможность расслабится.
Когда подключившийся к вам хост передает адрес отправителя (команда "MAIL FROM:" SMTP-сессии), фильтр соединяется с первичным (имеющим максимальный приоритет по MX) SMTP сервером осуществляющим прием почты для предъявленного домена, и пытается передать письмо для предъявленного E-Mail адреса, если удаленный сервер ответил согласием - мы принимаем письмо, если отказом - мы так же отказываем.
В любом случае передачи тела "проверочного" письма не происходит, "проверочная" SMTP-сессия фильтра с удаленным сервером проходит только до стадии "RCPT TO:".
Результаты проверок кешируються, что позволяет экономить трафик.
Например нам пытаются передать письмо от имени gfjsghfdh@yandex.ru,

# host -t MX yandex.ru
yandex.ru mail is handled (pri=0) by mx1.yandex.ru
yandex.ru mail is handled (pri=10) by mx2.yandex.ru
yandex.ru mail is handled (pri=20) by mx3.yandex.ru
самый высокий приоритет (pri=0) имеет mx1.yandex.ru, сервер с наивысшем приоритетом как правило обладает списком пользователей, остальные могут быть резервными и не чего не знать о пользователях проверяемого домена, с ним мы и соединяемся:

# telnet mx1.yandex.ru 25
Trying 213.180.200.7...
Connected to mx1.yandex.ru.
Escape character is '^]'.
220 Yandex ESMTP (NO UCE)(NO UBE) server ready at Fri, 10 Sep 2004 04:27:06 +0400
HELO people.dn.ua
250 mx7.yandex.ru Hello people.dn.ua
MAIL FROM:<>
250 2.1.0 Sender syntax Ok;
RCPT TO:<gfjsghfdh@yandex.ru>
550 5.1.1 <gfjsghfdh@yandex.ru> user unknown
QUIT
221 2.0.0 mx7.yandex.ru Out
Connection closed by foreign host.
Итак, mx1.yandex.ru нам сказал что пользователя gfjsghfdh@yandex.ru не существует, значит адрес отправителя фальшивый, или этот пользователь удален/заблокирован, письмо с таким адресом отправителя мы не принимаем.
Так же эта проверка полезна тем, что даже если адрес существует (спамеры обычно используют существующие адреса отправителей), администратор почтового сервера того домена, от имени которого производится рассылка спама, может заметить ненормально высокую активность проверок, и принять меры к блокированию E-Mail адреса, что позволит получателям производящим проверки отклонить спамерские письма.
Некоторые крупные почтовики уже используют автоматическую систему препятствия спамерским рассылкам, например я периодически замечаю в логах такое:

Milter: from=<sashka@yahoo.com>, reject=421 4.7.1 MX 1 'mx1.mail.yahoo.com.' [64.157.4.78] for <sashka@yahoo.com> rejected address saying "VS14-RT5 Mailbox bounce arrival rate exceeds system limit (#4.2.2)"
Дело в том, что при проверке в качестве адреса отправителя используется "bounce" адрес <>, что позволяет отличить обычную почту от проверок и уведомлений доставки, благодаря чему подобное поведение почтового сервера не препятствует прохождению обычной почты. Если ваш адрес кто то использует для рассылки спама, эти действия можно частично пресечь с помощью milter-regex:

reject "MAIL FROM: <> disabled for this recipient"
envfrom /<>/ei and envrcpt /<fred@example.com>/ei
Но это годится только как аварийная мера, вы не сможете отправлять почту на сервера производящие проверку!
Кроме того, без вреда для себя, вы можете использовать жесткую политику SPF, вроде "v=spf1 mx -all".

Рекомендации по настройке milter-sender:
MilterSocketTimeout=86400
Иначе очень большие но очень медленно вливаемые письма вы не когда не получите, будет происходить:
Milter (milter-sender): write(L) returned -1, expected 5: Broken pipe
Milter (milter-sender): to error state
с обрывом SMTP-сессии. У нас хватает уникумов, пытающихся отливать стометровые письма с диалапа ;-)
Это вообще касается всех milter-ов, проверяйте какие там таймауты по умолчанию!

CacheAcceptTTL=900
CacheRejectTTL=3480
Кеширование положительных и отрицательных результатов проверки.
Положительные желательно кешировать недолго, в противном случае если какой то адрес был заблокирован - вы нескоро об этом узнаете, а его могут заблокировать уже во время рассылки спама. По умолчанию там 604800 (неделя!), пропустили вы одно письмо, положительный результат прокешировался, через час адрес блокируют, а вы еще целую неделю принимаете спам с этим адресом отправителя :-(
Отрицательное можно подольше, в данном случае выбран интервал чуть меньше часа, это связано с тем, что многие почтовики имеют интервал повторных попыток доставки 30 или 60 минут, и если отказ был временным (код 4xx) через час письмо все таки пройдет. Но имейте ввиду, слишком большое значение создаст проблемы честным но криворуким отправителям, которые получили у вас отлуп, исправили свои баги, и теперь вынуждены долго ждать истечения TTL.

CacheGreyListTTL=0
GreyListBlockTime=0
GreyListRejectCount=0
Пока отключено во избежании багов (GreyList в milter-sender только появился) и недостатке времени и настроения разбираться в тонкостях его работы.

Есть еще spamilter, выполняет ту же проверку плюс проверку по SPF, но что то этот полуфабрикат мне не понравился, сыроват еще для применения, да и жестких SPF я кроме как у себя еще не где не видел, и RFC по SPF еще в черновике - смысл?
Подождем.

Дополнено:

У разработчиков milter-sender случился приступ жадности, этот фильтр стал платным, в придачу был удален из дерева портов FreeBSD.
Конфигурация spamilter близкая к тому что делал milter-sender (проверка SPF отключена):
UserName                = nobody
PolicyUrl               = http://www.somedomain.com/policy.html
Dbpath                  = /var/db/spamilter
Conn                    = local:/var/run/spamilter/spamilter.sock
DnsBlChk                = 0
SmtpSndrChk             = 1
SmtpSndrChkAction       = Reject        # valid options are 'Tag' or 'Reject'
MtaHostChk              = 0
MtaHostIpfw             = 0
MtaHostIpfwNominate     = 0
MtaHostIpChk            = 0
MtaSpfChk               = 0
MsExtChk                = 0
MsExtChkAction          = Reject        # valid options are 'Tag' or 'Reject'
PopAuthChk              = 0
MtaSpfChk               = 0