Фильтры regex в sendmail
Часто для борьбы со спамом используют procmail, просто поиском находя подозрительные сочетания букв. Однако, если Вы хотите проверять не только почту, которая ложится в почтовые ящики, но и проходящую транзитом, все становится не так просто. Хотя и существует способ задействовать procmail и для транзитной почты, см. Milter, его здесь рассматривать не будем.
Рассмотрим здесь поиск регулярных выражений в заголовках сообщений.
Вам, вероятно, понадобится перекомпилировать sendmail с поддержкой regexp
Проверить, есть ли такая поддержка, можно командой
#echo a|sendmail -d|grep REGEX
и найдя строку Сompiled with, которая должна содержать MAP_REGEX
Если строки нет, добавьте в файл /devtools/OS/Linux строку
APPENDDEF(`conf_sendmail_ENVDEF', `-DMAP_REGEX')
После этого перекомпилируйте файлы
#./Build
#./Build install
Теперь Вы можете внести фильтры в .mc файл в директории /cf/cf
Вот пример файла sendmail.mc
Как оно работает?
Каждая строчка заголовка, которая начинается с Subject: попадает на правило HSubject:
Оттуда на рулесет CheckSubject
Первая строка вызывает поиск регулярного выражения Kspamsubj
Если выражения найдены, то к строке subject дописывается слово CONTAINSSHIT
Вторая строка рулесета проверяет, кончается ли строка subject словом CONTAINSSHIT
И если содержит, то обработка правил рулесета прекращается и отправителю выдается ошибка 550 5.7.1 Subject denied
Если не содержит, то третья строка безусловно заканчивает проверку строки subject
Замечания
1. обязательно необходимо использовать (табуляцию) а не пробелы в описании рулесета
2. по умолчанию regex не делает разницы между большими и малыми буквами
3. ответ, что сообщение не принято, выдается только после того как сервер принял все сообщение целиком
О вызове проверок во время входящей SMTP-сессии
см. также
При начале SMTP сессии от удаленного компютера может быть вызван рулесет SCheck_relay
После команды MAIL FROM может быть вызван рулесет SCheck_mail
После каждой команды RCPT TO
может быть вызван рулесет SLocal_check_rcpt
Рулесеты, связаные с отдельными полями заголовка, а также SCheck_eoh
(вызывается после последнего поля заголовка)
обрабатываются только после получения всего письма, поэтому по возможности
лучше вынести проверки в SCheck_mail и SLocal_check_rcpt
Пример
...
LOCAL_RULESETS
SLocal_check_rcpt
R$*(tab)$: $&{verify}
#присваивает переменной значение &{verify} , которое есть OK
#если TLS сертификат клиента разрешен
ROK(tab)$# OK
#Такому человеку разрешен релеинг(то есть отменяются все дальнейшие проверки)
...
Полный список доступных переменных можно посмотреть например
здесь Например, ${client_addr},${mail_addr},${msg_size},${rcpt_addr}
и даже такие экзотические как ${load_avg} Отладка В любом месте обработки рулесетов можно заставить sendmail записать
в maillog значение переменной. Для этого необходимо включить логгинг:
...
Ksyslog syslog
LOCAL_RULESETS
SLocal_check_rcpt
R$*(tab)$: $&{verify}
ROK(tab)$# OK
R$*(tab)$: $&{rcpt_addr}
R$*(tab)$: $(syslog RCPT: $1 $)
# Здесь вместо RCPT: можно поставить любой текст, он попадает в лог
# а $1 есть первый параметр, он тоже запишется в лог
...
Сохранение переменной Допустим, мы хотим проверить было ли в заголовке поле To:
Примечание: поле To: в заголовке письма и
&{Rcpt_addr} могут
различаться, значение To: видно в почтовом клиенте как адрес получателя, а
&{Rcpt_addr} используется для доставки почты; Они могут быть различны,
например, если письмо отправляестя списку рассылки, или, при спаме.
Отсутствие поля To: в заголовке может также служить признаком спама
Необходимо предварительно включить макрос сохранения переменных
...
HTo: $>To
...
Kstorage macro
Ksyslog syslog
LOCAL_RULESETS
STo
R$*(tab)$: $(storage {TO} $@ $1 $) $1
#Сохраняем нашу переменную под именем &{TO}
R$*(tab)$@ OK
#На этом заканчиваем проверку поля To:
...
Scheck_eoh
R$*(tab)$: < $&{TO} >
#Загружаем переменную из &{TO}
R$*(tab)$: $(syslog $&{TO} $1 $) $1
R$*(tab)$: $(storage {TO} $) $1
#Сохраняем в &{TO} пустоту (для проверки следующих писем)
R< $+ >(tab)$@ OK
#Если наша переменная не пуста, заканчиваем проверку с ОК
R$*(tab)$#error $@ 4.7.1 $: 450 letter to nobody
#Иначе, выдаем временную ошибку
Сравнение переменных
При включении соответствующего arith map нам становятся
доступны простейшие арифметические операции такие как
+, -, *, /, =, а так же сравнение. Результат первых пяти операций -
целое число, а сравнения - FALSE или TRUE
пример применения - будем откладывать принятие больших писем
при высокой загрузке сервера
...
MAILER(smtp)dnl
MAILER(local)dnl
Kcomp arith
Ksyslog syslog
...
Kmyself regex -aOK 127.0.0.1
LOCAL_RULESETS
Scheck_mail
R$*(tab)$: $&{client_addr}
#загружаем адрес подключившегося клиента
R$*(tab)$: $(myself $1 $)
#Если локальный пользователь
R$*OK(tab)$@ OK
#То может передавать сообщения без ограничений
R$*(tab)$: $(comp l $@ 3 $@ $&{load_avg} $) $1
#Сравниваем (функция l(маленькая L)) максимально допустимую среднюю загрузку
#(здесь 3) и текущую $&{load_avg}
#Результат присваиваем текущей переменной $1
RFALSE $*(tab)$@ OK
#Если текущая загрузка меньше предельно допустимой то заканчиваем проверку
R$*(tab)$: $&{msg_size}
#Загружаем размер сообщения
R$*(tab)$: $(syslog size: $1 load: $&{load_avg} $)
#Syslog (для отладки)
R$*(tab)$: $(comp l $@ $&{msg_size} $@ 100000 $) $1
#Аналогично, сравниваем размер сообщения с
#предельно допустимым при высокой нагрузке,
#то есть 100000байт
RFALSE $*(tab)$#error $: "450 4.7.1 Too busy (${load_avg}) to process big messages"
#Если сообщение больше, даем временный отлуп
R$*(tab)$@ OK
#Иначе, все ОК
...
Примечание: данный метод не является 100% - надежным, поскольку мы не можем
знать наверняка размер сообщения во время команды MAIL FROM,
однако, обычно клиент сообщает серверу размер сообщения в виде
MAIL FROM:<a@b.com> SIZE=12345
Еще пример - разрешить отправку для авторизованых пользователей
только если они отправляют с адреса соответсвующего SMTP AUTH логину
и только при шифровании не менее 128 бит
...
LOCAL_CONFIG
...
Kislogin regex -aOK login
Kstorage macro
Ksyslog syslog
Kcomp arith
LOCAL_RULESETS
...
SLocal_check_rcpt
R$*(tab)$: $&t
#Заменяем переменную AU (временем) чтобы она была непустая
R$*(tab)$: $(storage {AU} $@ $1 $) $1
R$*(tab)$: $&{auth_authen}
#Загружаем имя под которым залогинился пользователь (используя SMTP AUTH)
R$*(tab)$: $(storage {AU} $@ $1 $) $1
#и сохраняем его как AU
R$*(tab)$: $&{mail_addr}
#Загружаем отправителя
R$+@$+(tab)$: $1@
#Оставляем только то что было до собаки и приписываем собаку
R$=AU@(tab)$: $1
#Ищем в переменной(то есть левой стороне отправителя с собакой)
#имя под которым он аутентифицировался (переменная AU),
#если найдено, результату присваивается перменная AU (в ней нету собачки)
R$+@(tab)$: NOK
#А теперь ищем собачку и если она есть присваиваем кодовое слово NOK
R$*(tab)$: $(storage {SEQA} $@ $1 $) $1
#Сохраняем результат как переменную SEQA
R$*(tab)$: $&{cipher_bits}
#Загружаем уровень шифрования TLS
R$*(tab)$: $(comp l $@ $&{cipher_bits} $@ 127 $) $1
#Сравниваем его с числом 127 (это для минимум 128-битного шифрования)
R$*FALSE$*(tab)$: $&{auth_type}!$&{SEQA}
#Если уровень шифрования не меньше 127 (FALSE)
#Составляем нашу переменную из типа Аутентификации (слово login или пусто)
#восклицательного знака и результата сравнения отправителя и логина (SEQA)
Rlogin!NOK$*(tab)$#error $@ 4.7.1 $: 450 FROM must match auth user $&{auth_authen}
#Отлуп, если уровень шифрования ок, метод login,
#но имя отправителя не соответствует логину
Rlogin$*(tab)$@ OK
#иначе - ОК, разрешен RELAY
R$*(tab)$: $(syslog WHATCOMPARED: $1 $)
# Это - для отладки
R$*(tab)$: $&{auth_type}
#Опять загружаем метод аутентификации
R$*(tab)$: $(islogin $1 $)
#Ищеи в нем слово login и если есть дописываем ОК
#(см. Kislogin regex выше)
R$*OK(tab)$#error $@ 4.7.1 $: 450 You MUST use minimum 128 bit encription
#И если использован логин то даем отлуп
#(Если же шифрование было включено, то отлуп был дан выше)
но сендмейл стал проверять поля письма еще до анализа всеми мильтерами
и получается что перед spamassassin это еще не спам, а после заголовки уже не проверяются.
Что посоветуете?
Да, действительно, regex-фильтры обрабатываются до мильтеров, но в данном случае, если стоит задача просто отфутболивать спам - письма, можно воспользоваться опцией spamass-milter -r NN, тогда сам мильтер discard-ает письма с уровнем спама больше NN и пишет отлуп отправителю еще в этой SMTP сессии Ответить
Как известно, значения хедеров можно переносить на другие строки, т.е. можно написать
X-Spam-Report: Spam detection software, running on the system "relay.host", ...
а можно написать
X-Spam-Report: Spam detection software,
running on the system "relay.host", ...
и так далее
Как я выяснил, regexp'ом можно обработать только ту часть хедера, которая уместилась в первой строке. Это конечно беда, но не фатальная. Кстати, можно ли её преодолеть? В общем я у себя настроил работу по наличию слова spam в хедере X-Spam-Report, ибо слово spam в этом хедере всегда успевает уместиться на первой строке.
Однако заметил следующую проблему: в некоторых случаях спам, у которого X-Spam-Report: Spam ... всё-равно проходит.
Погонял телнетом, повставлял разные усечённые варианты прошедшего хедера X-Spam-Report и увидел, что не срабатывать regexp начинает скорее всего при преодолении некоторого критического размера содержимого рассматриваемого хедера.
Можно ли это преодолеть?
Есть конечно предположение и о том, что негативно сказывается само содержимое хедера, но это менее вероятно, т.к. regexp'ом расматривается (почему-то) только первая строка содержимого.
Если я не сильно нафлужу тут, то приведу полный вариант хедера X-Spam-Report, при котором не срабатывает мой шаблон
KRuleXSpamReport regex -aRORORO -n SPAM
X-Spam-Report: Spam detection software,
running on the system "assassin2.aaanet.ru", has
identified this incoming email as possible spam. The original
message
has been attached to this so you can view it (if it isn't spam) or
block
similar future email. If you have any questions, see
postmaster@aaanet.ru for details.
Content preview: =f7=e5=f0=ed=e5=eb=e0, =e8 =f2=fb
=ef=e5=f7=e0=eb=fc=ed=e0=ff =f1=e8=e4=e5=eb=e0
=c1=f3=f5=e3=e0=eb=f2=e5=e5=f0=f3 =e2 =ef=ee=ee=ec=ee=f9=fc! =d1
=ed=e0=f1=f2=f3=ef=eb=e5=e5=ed=e8=e5=ec 2004 =e3=ee=ee=e4=e0 =e2
=ed=e0=eb=ee=e3=ee=ee=e2=ee=ec
=e7=e0=ea=ee=ed=ee=ee=e4=e0=f2=e5=eb=fc=f1=f2=f2=e2=e5 =e8 =cf=c1=d3
=ef=f0=f0=ee=e8=e7=ee=ee=f8=eb=e8 =ed=e5=ea=ee=f2=ee=ee=f0=fb=e5
=e8=e7=ec=e5=ed=e5=e5=ed=e8=ff, =ee=f1=f1=ee=e1=ee =e2=e0=e6=ed=fb=e5
=e8 =f1=ef=ee=f0=f0=ed=fb=e5 =e8=e7 =ea=ee=f2=ee=f0=fb=f5:
=e3=ee=f0=e4=ee=fe =ea=f0=e0=f1=ee=e9 вєВ =cd=c4=d1; вєВ
=cd=e0=eb=ee=ee=e3 =ed=e0 =ef=f0=e8=e1=fb=eb=fc; [...]
Content analysis details: (6.9 points, 5.0 required)
pts rule name description
---- ----------------------
--------------------------------------------------
0.1 HTML_FONTCOLOR_UNKNOWN BODY: HTML font color is unknown to us
0.8 HTML_30_40 BODY: Message is 30% to 40% HTML
0.0 HTML_MESSAGE BODY: HTML included in message
0.2 HTML_FONT_FACE_BAD BODY: HTML font face is not a word
0.7 HTML_TABLE_THICK_BORD BODY: HTML table has thick border
0.1 MIME_HTML_ONLY BODY: Message only has text/html MIME
parts
2.2 RCVD_IN_BL_SPAMCOP_NET RBL: Received via a relay in
bl.spamcop.net
[Blocked - see <http://www.spamcop.net/bl.shtml?199.72.44.111>]
2.5 RCVD_IN_DYNABLOCK RBL: Sent directly from dynamic IP
address
[211.207.138.244 listed in dnsbl.sorbs.net]
0.1 RCVD_IN_SORBS RBL: SORBS: sender is listed in SORBS
[211.207.138.244 listed in dnsbl.sorbs.net]
0.1 RCVD_IN_RFCI RBL: Sent via a relay in
ipwhois.rfc-ignorant.org
[Inaccurate or missing WHOIS data]
RE: неправильная работа regexp на длинных хедерах▼▲
И это - правда! Баг в sendmail-e, не обрабатываются поля длиннее 256 бит. Кстати, на новых версиях sendmail такие письма отвергаются с catadd:string too long Ответить
У меня sendmail отвергает почту, содержащуюю шаблон X-Spam-Flag: spam|yes
И вот в такую почту попал отправитель, который присылает мне не спам, но на
его сервере к его письмам добавляется заголовок X-Spam-Flag: ...
Отключать проверку этого флага не захотелось. Попробовал заставить
пропускать мне письма с этим флагом от отправителя, методом внесения этого
отправителя в access.db . Пробовал по разному:
email_отправителя OK
From:email_отправителя OK
домен_отправителя OK
@домен_отправителя OK
IP_адрес_его_smtp_сервера OK
И босполезно.
Почему ж так происходит? REGEXP в sendmail'е имеют такой высокий
приоритет, что дальнейшая обработка письма завершается сразу после
применения REGEXP'а?
Просто был у меня пример: почта блокируется по RBL-базам. После внесения в
access.db конкретного IP-адреса (ip OK), который попадал в RBL, почта с
этого IP начала приходить ко мне нормально.
Т.е. я так понимаю: письмо должно отвергнуться из-за RBL, но принята из-за
моего дополнительного особого access.db-разрешения принимать её с указанного
IP не взирая на RBL.
А в случае с REGEXP такое же номер не прокатывает... А почему? Ответить
SCheckSpamField
R$*(tab)$: $(SpamField $1 $)
R$RORORO(tab)$@ OK
R$*(tab)$: < $&{mail_addr} >
R< $={spam_whitelist} >(tab)$@ OK
R$*(tab)$#error $@ 4.7.1 $: 450 spam-like mail temporaty not accepted from $&{mail_addr}
то есть если в регексе НЕТУ строки YES то на второй строчке сразу ОК,
а затем присваиваем (анализируемой переменной) значение адреса отправителя
и проверяем в файле-списке spam_whitelist Ответить
В своей борьбе со спамом я даже написал статью на http://prof.pi2.ru/.
Кроме всего, я заметил, что многие спамеры ставят в качестве hostname
мой IP-номер (IP-номер того сервера, который принимает спам-почту).
Или часто в качестве hostname используется IP-номер отправителя
или произвольный IP-номер. Можно ли как-то фильтровать спам,
основываясь на строке, которую оправитель сообщает при установке
SMTP-соединения (я так понимаю, это строчка HELO)?
Если надо - могу прислать поясняющие примеры строк "Received:".
У меня это работает (первое EHLO попадает в поле Recieved:),
но только потому что добавляется поле Recieved четыре раза и
проверяется правилом четыре раза, два раза при передаче Касперскому
антивирусу и один - при обработке virtusertable, и при втором проходе
проверкой уже попадается аргумент первого EHLO от удаленного хоста...
А если почта приходит и сразу падает в ящик или уходит через релэй,
то у меня не получалось, чтобы сработало....
Думаю, Вы знаете, как пишутся правила для FireWall:
указывается действие (deny, allow, ...)
и набор условий (IP-номер:порт источника и назначения,
протокол TCP/UDP/ICMP, сетевой интерфейс и вход/выход);
если пакет попадает под все указанные условия,
то к нему применяется действие, на чем просмотр
списка правил для данного пакета заканчивается.
Аналогично действует Squid при фильтрации доступа к WWW.
Хотелось бы иметь аналогичный интерфейс для фильтрации писем,
где можно было бы указать:
адрес получателя;
наличие этого адреса в строке "To:" или "Cc:";
другие адреса в строках "To:" и "Cc:";
адрес отправителя в строках "From_",
"Return-Path", "From:" и "Reply-To:;
hostname и IP-номер машины-отправителя;
результаты резолвинга этих параметров через DNS;
другие заголовки (как это сделано в Вашем regex);
тип и содержимое частей multipart-письма.
И по совокупности этих данных принимать решение о приеме письма
или об отлупе отправителю. Желательно делать это
не тогда, когда письмо принято целиком, а по мере приема
(например, hostname можно проверить при установлении SMTP-соединения).
Почему-то мне кажется, что этот путь требует изменений
в исходном коде SendMail...
Понравилось. Настроил. Заработало. Но правильно заработало не всё.
Я взял простой пример со страницы regex-sendmail.html с простым запретом
писем с темой по шаблону get|dvd|sell|cool . После этого у
меня sendmail действительно начал не принимать письма для доставки
( 550 5.7.1 Subject denied ). Я было возрадовался.
Однако.
Пишу простое письмо с темой test. Sendmail, как и положено, пишет мне
про успешный приём письма для доставки ( Message accepted for delivery ),
однако в локальный ящик письмо не доставляет! Смотрю maillog, а там пишут:
Sep 19 23:52:04 sha mail.local: lockmailbox /var/mail/rodin failed; error code 75
Sep 19 23:52:04 sha sm-mta[53414]: h8JJpDub053299: to=<lana@sha.rnd.su>, delay=00:00:47, xdelay=00:00:41, mailer=local, pri=31669, dsn=4.0.0, stat=Deferred: local mailer (/usr/libexec/mail.local) exited with EX_TEMPFAIL
Письма застревают в очереди для доставки и в ящики не доставляются... :-(
После того, как возвращаю на место сохранённый sendmail.cf (без проверки
шаблонов), все застрявшие в очереди письма быстренько раскладываются по
ящикам.
Не знаете, в чём проблема? Может я в чём-то ошибся при настройке?
Свой mc-файл привожу полностью в конце письма. Может опытным взглядом Вы
сразу в описанной ситуации что-то нехорошее заметите и поможете мне
разобраться в проблеме.
Спасибо.
divert(-1)
#
# Copyright (c) 1998 Sendmail, Inc. All rights reserved.
# Copyright (c) 1983 Eric P. Allman. All rights reserved.
# Copyright (c) 1988, 1993
# The Regents of the University of California. All rights reserved.
#
# By using this file, you agree to the terms and conditions set
# forth in the LICENSE file which can be found at the top level of
# the sendmail distribution.
#
#
#
# This is a generic configuration file for 4.4 BSD-based systems,
# including 4.4-Lite, BSDi, NetBSD, and FreeBSD.
# It has support for local and SMTP mail only. If you want to
# customize it, copy it to a name appropriate for your environment
# and do the modifications there.
#
# скопирил из generic-bsd4.4.mc и доделываю