# Проверка на спам - по хосту
# тут оставлено много отладки - раскомментите что надо...
# # Вводим переменную acl_m0 - в ней будет счётчик,
# # сколько очков спамерских насчиталось...
warn set acl_m0 = 0
# logwrite = "ACL m0 set default as $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name (domain in e-mail = $sender_address_domain)"
# Проверяем соответствие HELO и обратной записи DNS для севера:
warn condition = ${if !eq{$sender_helo_name}{$sender_host_name}{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+30}
# logwrite = "STAGE1: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name - reverse sone not match with HELO"
# Смотрим, нашлась ли обратная запись для этого хоста
warn condition = ${if eq{$host_lookup_failed}{1}{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+30}
# logwrite = "STAGE2: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name - no reverse zone for host"
# Удалено, т.к. пееркрывается проверками на реальность домена первого уровня
# Cчитаем длинну имени хоста (из HELO). если меньше 5 - добавляем
# - таких хостов не может существовать
# warn condition = ${if <{${strlen:$sender_helo_name}}{5}{yes}{no}}
# hosts = !+relay_from_hosts : *
# set acl_m0 = ${eval:$acl_m0+60}
# logwrite = "STAGE3: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name - lenth helo chars small 5"
# Считем число точек или дефисов в доменном имени. (больше 4-х - в топку)
warn condition = ${if match{$sender_host_name} \
{\N((?>\w+[\.|\-]){4,})\N}{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+40}
# logwrite = "STAGE4: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name - more dots in name"
# Смотрим, есть ли вобще точки в HELO - дохера уродов с ``friends'' (удалено,
# т.к. перекрывется проверками на реальность домена первого уровня)
# warn condition = ${if !match{$sender_helo_name}{\N\w\.\w\N}{yes}{no}}
# hosts = !+relay_from_hosts : *
# set acl_m0 = ${eval:$acl_m0+60}
# logwrite = "STAGE5: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name - helo not contain dots"
# Проверяем длинну обратного почтовго адреса - пследнее время сцуки повадились
# слать с безумными обратными адресами типа Fulbrightbackstage@absacargo.com,
# damsel'stailpipe`s@abbeywindows.co.uk и т.п.
warn condition = ${if <{${strlen:$sender_address}}{25}{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+10}
# logwrite = STAGE6: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with HELO=$sender_helo_name \
#- many big sender address [$sender_address]
# Добавляем очков за всякие dialup хосты
warn condition = ${lookup{$sender_host_name} \
wildlsearch{/usr/local/etc/exim/db/dialup_hosts} \
{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+60}
# logwrite = "STAGE9: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#host=$sender_helo_name - dialup, ppp & etc..."
warn condition = ${lookup{$sender_helo_name} \
wildlsearch{/usr/local/etc/exim/db/dialup_hosts} \
{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+60}
# logwrite = "STAGE10: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name - dialup, ppp & etc..."
#recipients_count
# Проверяем счётчик сообщений в сессии - нормальные пользователи редко шлют
# сообщения с большим числом получателей, а для крупных почтовых сервисов
# всё сбросится по белому листу серверов, что находится дальше
warn condition = ${if >{$recipients_count}{4}{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+($recipients_count*20)}
# logwrite = STAGE11: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name; counter = $recipients_count
# Проверяем существование зоны из HELO (на этом правиле огребают очков все
# уродцы с HELO типа 'friends' или 'localhost.localdomain')
warn condition = ${if !eq{${lookup mysql{SELECT 1 FROM \
`list_top_level_domains` WHERE `zone` = \
LCASE(CONCAT('.', SUBSTRING_INDEX( \
'${quote_mysql:$sender_helo_name}', \
'.', -1)))}}}{1}{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+150}
# logwrite = non-existent domain in HELO - \
#'$sender_helo_name' setting acl_m0 = $acl_m0
# Добавляем очки, если spf не соответствует (спасибо dikens3 за наводку)
warn spf = fail
hosts = !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+60}
# logwrite = "SPF status: spf_result = $spf_result; \
#spf_smtp_comment = $spf_smtp_comment"
# logwrite = "STAGE11-SPF: ACL m0 set = $acl_m0 for \
#host=$sender_host_name [$sender_host_address] with \
#HELO=$sender_helo_name - SPF check failed: \
#$sender_host_address is not allowed to send mail from $sender_address_domain"
# Вводим acl_m2 с нулевым значением
warn set acl_m2 = 0
# Проверяем - не было ли писем НА домен отправителя от наших юзеров за
# последние два месяца (оптимальное, на мой взгляд, число)
# Если были - то потом, в фильтре, обнулим количество насчитанных очков.
warn condition = ${if eq{${lookup mysql{SELECT 1 FROM `sended_list` \
WHERE `user_to` = \
LCASE('${quote_mysql:$sender_address}') \
AND `user_from` \
= LCASE('${quote_mysql:$local_part@$domain}') \
AND `last_mail_timestamp` < `last_mail_timestamp` \
+ (60*24*60*60) LIMIT 1}}}{1}{yes}{no}}
condition = ${lookup mysql{INSERT IGNORE INTO `domain_whitelist` \
(`domainname`, `domain_ip`, `added_timestamp`, \
`last_mail_timestamp`, `mail_count`) VALUES \
(LCASE('${quote_mysql:$sender_address_domain}'), \
'${quote_mysql:$sender_host_address}', \
UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '1') \
ON DUPLICATE KEY UPDATE \
`last_mail_timestamp` = UNIX_TIMESTAMP(), \
`mail_count` = `mail_count` + 1}}
hosts = !+relay_from_hosts : *
set acl_m2 = 1
# logwrite = STAGE12: $sender_address ==> $local_part@$domain; \
#setting acl_m2 = $acl_m2; WHITELIST for this addresses
# Переменная изменилась лишь если это были те же самые получатели,что и общались
# при внесении данных в транспорте. Для остальных этот домен так и остался
# без изменений. Соответственно надо сделать его и для остальных белым.
warn condition = ${if eq{${lookup mysql{SELECT 1 \
FROM `domain_whitelist` \
WHERE `domain_ip` = \
'${quote_mysql:$sender_host_address}' \
LIMIT 1}}}{1}{yes}{no}}
hosts = !+relay_from_hosts : *
set acl_m2 = 1
# logwrite = STAGE13: $sender_address ==> $local_part@$domain; \
#setting acl_m2 = $acl_m2; WHITELIST for ALL domains
# warn logwrite = Variables: \
#sender_address = $sender_address; sender_host_name = $sender_host_name; \
#sender_helo_name = $sender_helo_name; sender_ident = $sender_ident; \
#host_lookup_failed = $host_lookup_failed; \
#return_path = $return_path; recipients_count = $recipients_count; rcpt_count = \
#$rcpt_count; rcpt_defer_count = $rcpt_defer_count; rcpt_fail_count = \
#$rcpt_fail_count; received_count = $received_count; received_for = \
#$received_for
# Конец конфигурации проверки отправителя
# Сбрасываем спамерскую переменную, если домен в белом списке
warn condition = ${if eq{$acl_m2}{1}{yes}{no}}
logwrite = Resetting acl_m0 $acl_m0 --> 0, host in whitelist \
($sender_address ==> $local_part@$domain)
set acl_m0 = 0
# Задержка. Режется довольно много не-MTA - спамерских скриптиков.
# warn
# set acl_c0 = 5s
warn
#
# condition = ${if !eq{$acl_m0}{0}{yes}{no}}
# condition = ${if <{$acl_m0}{150}{yes}{no}}
set acl_c0 = 15s
warn
# Вычисляем задержку на основании насчитанных за спам очков:
condition = ${if !eq{$acl_m0}{0}{yes}{no}}
condition = ${if >{$acl_m0}{150}{yes}{no}}
set acl_c0 = ${eval:$acl_m0/10}s
warn
# ставим задержку в 0 секунд своим хостам
hosts = +relay_from_hosts
set acl_c0 = 0s
warn
# Ставим нулевую задержку хостам из белого листа
condition = ${if eq{$acl_m2}{1}{yes}{no}}
set acl_c0 = 0s
warn
# пишем в логи задержку (если оно вам надо)
# logwrite = Delay $acl_c0 (spam counter = $acl_m0; \
#white host = $acl_m2) for $sender_host_name \
#[$sender_host_address] with HELO=$sender_helo_name. Mail \
#from $sender_address to $local_part@$domain.
delay = $acl_c0
# Рубаем тех, кто в блэк-листах. Серваки перебираются
# сверху вниз, если не хост не найден на первом, то
# запрашивается второй, и т.д. Если не найден ни в одном
# из списка - то почта пропускается.
deny message = "you in blacklist - $dnslist_domain --> \
$dnslist_text; $dnslist_value"
hosts = !+relay_from_hosts
dnslists = cbl.abuseat.org : \
dynablock.njabl.org : \
dul.dnsbl.sorbs.net : \
list.dsbl.org : \
sbl-xbl.spamhaus.org
delay = 30s
# для фильтра - устанавливаем переменные (отладка)
# warn set acl_m4 = $sender_address
# set acl_m5 = $local_part@$domain
# проверяем пользователей из файла альясов (системные)
accept domains = +local_domains
verify = recipient
# Проверяем пользователей в эксчейндже
deny domains = +relay_to_domains
message = "Unknown user for this domain"
# изначальное условие проверки юзера было такое. Однако, при письме на
# альяс, ldap возвращал адрес получателя, а не альяс (вполне логично :))
# пришлось использовать match вместо eq (a логичней вообще оставить
# проверку на ротерах а тут verify = recipient)
# condition = ${if !eq{${lookup ldap {LDAP_AD_MAIL_RCPT}}}\
# {${local_part}@MS_EXCHANGE_DOMAIN}{yes}{no}}
condition = ${if !match{${lookup ldap {LDAP_AD_MAIL_RCPT}}}\
{@MS_EXCHANGE_DOMAIN}{yes}{no}}
# accept domains = +relay_to_domains
# endpass
# message = "No route to host... $acl_verify_message"
# verify = recipient
# Разрешаем почту от доменов в списке relay_from_hosts
accept hosts = +relay_from_hosts
# приниаем почту для эксчейнджевых доменов
accept domains = +relay_to_domains
# Если неподошло ни одно правило - чувак явно ищет
# открытый релей. Пшёл прочь. :)
deny message = "Access deny - this not open relay!"
delay = 30s
# Проверяем тело письма
# Рубаем по расширениям
deny message = contains $found_extension file (blacklisted).
demime = com:vbs:bat:pif:scr:exe
# проверяем MIME
deny message = This message contains a MIME error ($demime_reason)
demime = *
condition = ${if >{$demime_errorlevel}{2}{1}{0}}
# Проверяем письмо на вирусы
# deny malware = *
warn malware = *
logwrite = VIRUS from host $sender_host_name [$sender_host_address]. \
Mail from $sender_address to $local_part@$domain.
set acl_m1 = 1
logwrite = "In e-mail found VIRUS - $malware_name"
# Сообщения с NUL-символами
deny message = This message contains NUL characters
log_message = NUL characters!
condition = ${if >{$body_zerocount}{0}{1}{0}}
# Синтаксис заголовков
# 2007-02-15: закомменчено - очень много сообщений нормальных рубится...
# 2007-02-28: добавлено условие про очки - стало нормально
# 2007-03-29: условие про очки убрано - всё пучком, я неправильно использовал,
# оно возвращает истину если заголовки корректны...
deny message = Incorrect headers syntax
hosts = !+relay_from_hosts:*
# condition = ${if >{$acl_m0}{190}{yes}{no}}
!verify = header_syntax
# проверяем скрытые копии (немногие юзеры пользуются этим, пусть лучше
# отдельными письмами пишут) у тех, у кого уже есть спамерские очки
deny message = Administrative denied 'blind' ('hidden') copy messages
condition = ${if >{$acl_m0}{110}{yes}{no}}
hosts = !+relay_from_hosts:*
!verify = not_blind
# Вводим ACL для заполнения таблицы что и куда ходило
# warn condition = ${lookup mysql{INSERT INTO `mail_history` (`to`, `from`, \
# `message_size`,`sending_timestamp`) VALUES \
# ('$recipients', '$sender_address', \
# '$message_size', UNIX_TIMESTAMP())}}
# отладка - выводим пеерменную - надо найти про отправителя конверта.
# warn logwrite = return_path = $return_path; sender_address \
#= $sender_address; sender_address_local_part@sender_address_domain = \
#$sender_address_local_part@$sender_address_domain; reply_address = $reply_address
# проверяем нету ли в поле From адреса с которым переписываемся (web интерфейсы
# любят подставлять себя - поэтому тут и изгаляемся.)
warn condition = ${if eq{${lookup mysql{SELECT 1 FROM `sended_list` \
WHERE `user_to` = \
LCASE(SUBSTR('${quote_mysql:$reply_address}', \
POSITION('<' IN '${quote_mysql:$reply_address}') +1, \
POSITION('>' IN '${quote_mysql:$reply_address}') \
- POSITION('<' IN '${quote_mysql:$reply_address}') -1) \
) AND `user_from` \
= LCASE('${quote_mysql:$local_part@$domain}') AND \
`last_mail_timestamp` < `last_mail_timestamp` \
+ (60*24*60*60) LIMIT 1}}}{1}{yes}{no}}
condition = ${lookup mysql{INSERT IGNORE INTO `domain_whitelist` \
(`domainname`, `domain_ip`, `added_timestamp`, \
`last_mail_timestamp`, `mail_count`) VALUES \
(LCASE('${quote_mysql:$sender_address_domain}'), \
'${quote_mysql:$sender_host_address}', \
UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '1') \
ON DUPLICATE KEY UPDATE \
`last_mail_timestamp` = UNIX_TIMESTAMP(), \
`mail_count` = `mail_count` + 1}}
hosts = !+relay_from_hosts : *
set acl_m2 = 1
# Если есть необходимость - тут проверки на спам
# Пропускаем остальное
accept
/usr/local/etc/exim/includes/600.routers.conf:
# Конфигурация роутеров
# роутер для преобразования адресов из внешних во внутренние
conversion_router:
driver = redirect
# data = $local_part@MS_EXCHANGE_DOMAIN
# data = $local_part
data = ${lookup ldap {LDAP_AD_MAIL_RCPT}}
user = mailnull
group = mail
domains = +relay_to_domains
# умный роутер - шлём почту на внутренний эксчейндж (192.168.205.2)
exchange_router:
driver = "manualroute"
domains = MS_EXCHANGE_DOMAIN
# domains = +relay_to_domains
# data = $local_part
transport = remote_smtp
route_list = * 192.168.205.2
no_more
# Поиск маршрута к хосту в DNS. Если маршрут не найден в DNS -
# то это `унроутабле аддресс`. Не проверяются локальные
# домены, 0.0.0.0 и 127.0.0.0/8
dnslookup:
driver = dnslookup
domains = !+local_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
# системные альясы
system_aliases:
driver = redirect
data = ${lookup{$local_part}lsearch{/etc/aliases}}
user = mailnull
group = mail
file_transport = address_file
pipe_transport = address_pipe
allow_fail
allow_defer
# локальные пользователи
localuser:
driver = accept
check_local_user
transport = local_delivery
cannot_route_message = Unknown user
/usr/local/etc/exim/includes/700.transports.conf:
# Конфигурация транспортов
# Доставка на удалённые хосты - по SMTP
remote_smtp:
driver = smtp
# headers_add = "X-Descriptions: powered by www.lissyara.su"
# следующая строка - это внесение в таблицу отправленных писем - грамотней не
# придумал,поэтому на неё идут варнинги в логах, однако работает :)
hosts_avoid_esmtp = ${lookup mysql{INSERT IGNORE INTO `sended_list` \
(`user_from`, `user_to`, `added_timestamp`, \
`last_mail_timestamp`, `mail_count`) VALUES \
(LCASE('${quote_mysql:$sender_address}'), \
LCASE('${quote_mysql:$local_part@$domain}'), \
UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '1') ON DUPLICATE \
KEY UPDATE `last_mail_timestamp` = UNIX_TIMESTAMP(), \
`mail_count` = `mail_count` + 1}}
# локальная доставка
local_delivery:
driver = appendfile
file = /var/mail/$local_part
delivery_date_add
envelope_to_add
return_path_add
group = mail
user = $local_part
mode = 0660
no_mode_fail_narrower
# Имя программы
address_pipe:
driver = pipe
return_output
address_file:
driver = appendfile
delivery_date_add
envelope_to_add
return_path_add
# Транспорт для автоответов
address_reply:
driver = autoreply
# В никуда
null_transport:
driver = appendfile
file = /dev/null
# Конфигурация повторов и перезаписи адресов
# Начинаются повторы недоставленных писем.
begin retry
# Этот кусок я не трогал. Думаю разработчики лучше знают,
# какие тут должны быть цифирьки. Если же вы это знаете
# лучше их - меняйте. Хотя... А какого, если Вы такой
# умный, читаете этот мануал? Может ну, их, цифирьки, а? :)
# Address or Domain Error Retries
# ----------------- ----- -------
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
# преобразование адресов. Переписываем домен эксчейнджана внешний
begin rewrite
# основное правило перезаписи - внутренний на внешний домены
# однако, при отсылке внутри тоже пеерзаписывает, после
# обработки эксчейнджевым роутером.... Посему - извращаемся
*@MS_EXCHANGE_DOMAIN "${if eq{$interface_address}{INTERNAL_IP}\
{$1@long-domain.ru}fail}"
#*@MS_EXCHANGE_DOMAIN "${if or !}"
#*@MS_EXCHANGE_DOMAIN $1@long-domain.ru
*@short-dom.ru $1@long-domain.ru
logfile /var/log/exim/system-filter.log
# проверяем, нет ли вирусов
if $acl_m1 contains "1"
then
# копируем письма. с вирусами нам не нужны.
deliver viruses@eliron.ru
#no_more
else
# Спам
#logwrite "EXIM FILTER: debug - digit in variable acl_m0 = $acl_m0 (before)"
# Проверяем содержимое переменной про спам (содержит ли цифры)
if $acl_m0 matches ^\\d+
then
#logwrite "FILTER: debug - digit in variable acl_m0 = $acl_m0 (after first if)"
# Строим новую тему письма - если спам
# Проверяем содержимое переменной со счётчиком спамерских очков.
# На данный момент считаем - что если 60 и более - это спам.
# Добавляем заголовки с объяснением происходящего
headers add "X-Spam-Description: if spam count > 60 - this is spam"
headers add "X-Spam-Count: $acl_m0"
# рихтуем хеадеры
if $acl_m0 is above 59
then
# headers add "Old-Subject: $h_subject:"
# headers remove "Subject"
# headers add "Subject: (*** SPAM ***) $h_old-subject:"
headers add "X-Spam: YES"
# Старый заголовок оставляем, на всякий случай
#headers remove "Old-Subject"
#logwrite "EXIM FILTER: Spam count = $acl_m0 ; Added SPAM header"
endif
# Закрытие - содержит цифры
endif
#logwrite "EXIM FILTER: interface_address = $interface_address"
# перезапись заголовков, которые не окучены штатно - exim`ом
if $interface_address is INTERNAL_IP
then
headers add \
"Old-Disposition-Notification-To: $h_Disposition-Notification-To:"
headers add "Old-Return-Receipt-To: $h_Return-Receipt-To:"
headers remove "Disposition-Notification-To"
headers remove "Return-Receipt-To"
headers add "Disposition-Notification-To: <$sender_address>"
headers add "Return-Receipt-To: <$sender_address>"
logwrite "EXIM FILTER: heders rewritten in filter"
endif
# закрываем проверку на вирусы в письме
endif
Теперь, нужно создать БД в MySQL, согласно приложенному дампу:
Дамп таблиц для exim`a - БД для exim`a - списки доменов верхнего уровня, таблицы для ведения т.н. "Белых списков" - доменов ск оторыми юзеры переписываются.
Также, создаём такой скриптик:
more /usr/local/scripts/work/delete_from_exim_whitelist.sh
#!/bin/sh
# Дропаем домены в белом списке, которые старше 60 дней. Если переписка всё
# ещё идёт, то в таблице отправленных останутся записи - там дропается
# по дате последней отправки
/usr/local/bin/mysql --user=exim_user --password=exim_pass --database=exim_db \
--execute="DELETE FROM \`domain_whitelist\` WHERE \`added_timestamp\` \
< (UNIX_TIMESTAMP() - 60*24*3600)"
# Дропаем из списка отправленных записи которые не обновлялись более 60 дней.
/usr/local/bin/mysql --user=exim_user --password=exim_pass --database=exim_db \
--execute="DELETE FROM \`sended_list\` WHERE \`last_mail_timestamp\` \
< (UNIX_TIMESTAMP() - 60*24*3600)"
# Дропаем рикошеты. Чтоб не мешались.
/usr/local/bin/mysql --user=exim_user --password=exim_pass --database=exim_db \
--execute="DELETE FROM \`sended_list\` WHERE \`user_from\` = ''"
И пихаем его в крон - чтобы выполнялся раз в сутки - или чаще - как угодно. После чего можно всё запускать. Должно работать. Про часть касающуюся эксчейнджа - ничё сказать не могу, не настраивал, тока знаю, что через коннектор настроена отправка через фряху, почты идущей наружу.