Регулярно натыкаюсь на статьи про прикручивание к почтовикам антиспамовских систем (например spamassassin и подобных). Каждый раз, смотря на эти связки и кучу проблем которые они приносят, «пожимаю плечами» и искренно не понимаю зачем все это? Спам можно очень эффективно рубить непосредственно силами почтовика, без привлечения сторонних программ, некоторые из которых еще и требуют длительного обучения (насколько я знаю, но могу и ошибаться — не использую я их).
Метод отлова спама, который я опишу в этой статье, дает эффективность, примерно 97%. Он опробован на 10-ке серверов, и работает уже как минимум лет семь.
Все примеры конфигурации буду приводить для почтового сервера exim, собранного с поддержкой mysql. Но перевести их на тот-же postfix не составит особого труда.
Пользователи храняться в БД. Дабы не загромождать статью структура БД в приложенном файле (ссылка). Структура взята от ранних версий postfixadmin. Дабы было удобно админить пользователей.
Для начала инициализируем несколько переменных, которые будут использоваться в наших проверках почты. Названия переменных достаточно «говорящие».
Указываем почтовику правила по которым будет проверяться почта
Заголовки и текст письма непосредственно.
Ну а теперь начнем проверять нашу почту. Начинаем с заголовков.
Теперь перейдем к проверке тела письма.
В exim есть механизм system filter. Вот туда и добавляем
Т.е. мы в начало темы письма вставляем сточку "(*** SPAM ***)", по которой клиенты пользователей уже отсортировуют спам.
Как видите набор правил не велик, но позволяет эффективно отфильтровывать спам, при этом не ставя никаких дополнительных систем. Конечно есть вероятность ложных срабатываний, но после первого же письма от нашего пользователя к тому, которого случайно включили в спам, и он попадает в whitelist.
Повторюсь, по моим наблюдениям, такие достаточно простые правила не пропускают где-то 97% спама.
Метод отлова спама, который я опишу в этой статье, дает эффективность, примерно 97%. Он опробован на 10-ке серверов, и работает уже как минимум лет семь.
Все примеры конфигурации буду приводить для почтового сервера exim, собранного с поддержкой mysql. Но перевести их на тот-же postfix не составит особого труда.
Пользователи храняться в БД. Дабы не загромождать статью структура БД в приложенном файле (ссылка). Структура взята от ранних версий postfixadmin. Дабы было удобно админить пользователей.
Для начала инициализируем несколько переменных, которые будут использоваться в наших проверках почты. Названия переменных достаточно «говорящие».
domainlist local_domains = ${lookup mysql{SELECT `domain` FROM `domain` WHERE `domain`='${domain}' AND `active`='1'}} domainlist relay_to_domains = ${lookup mysql{SELECT `domain` FROM `domain` WHERE `domain`='${domain}' AND `active`='1'}}
Указываем почтовику правила по которым будет проверяться почта
acl_smtp_rcpt = acl_check_rcpt acl_smtp_data = acl_check_data
Заголовки и текст письма непосредственно.
Ну а теперь начнем проверять нашу почту. Начинаем с заголовков.
acl_check_rcpt: # принимать сообщения которые пришли с локалхоста, не по TCP/IP accept hosts = : # Запрещаем письма содержащие в локальной части символы @; %; !; /; |. deny message = "Incorrect symbol in address" domains = +local_domains local_parts = ^[.] : ^.*[@%!/|] # Проверяем недопустимые символы для нелокальных получателей: deny message = "Incorrect symbol in address" domains = !+local_domains local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./ # Принимаем почту для постмастеров локальных доменов без проверки отправителя accept local_parts = postmaster domains = +local_domains # Запрещщаем, если невозможно проверить отправителя (отсутствует в списке локальных пользователей) require verify = sender # Запрещщаем тех, кто не обменивается приветственными сообщениями (HELO/EHLO) deny message = "HELO/EHLO require by SMTP RFC" condition = ${if eq{$sender_helo_name}{}{yes}{no}} # Принимаем сообщения от тех, кто аутентифицировался accept authenticated = * # Отрубаем тех, кто подставляет свой IP в HELO deny message = "Your IP in HELO - access denied!" # все хосты кроме тех, что в relay_from_hosts hosts = * : !+relay_from_hosts condition = ${if eq{$sender_helo_name}{$sender_host_address} \ {true}{false}} # Рубаем хосты типа *adsl*; *dialup*; *pool*;.... deny message = "your hostname is bad (adsl, poll, ppp & etc)." condition = ${if match{$sender_host_name} \ {adsl|dialup|pool|peer|dhcp} \ {yes}{no}} # Рубаем тех, кто в блэк-листах. deny message = "you in blacklist - $dnslist_domain" hosts = !+relay_from_hosts dnslists = dul.dnsbl.sorbs.net : \ sbl-xbl.spamhaus.org # Начинаем подсчет очков спама для письма. Вес каждого параметра подбирался экспериментально. 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)" # наличие в теме слова Vigra, ранее было очень актуально, сейчас данное правило уже устарело # 200 очков warn condition = ${if match{$h_subject} \ {viagra} \ {yes}{no}} set acl_m0 = ${eval:$acl_m0+200} logwrite = "STAGE0: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name - VIAGRA!!!!" # Проверяем соответствие HELO и обратной записи DNS - на правильном почтовике они должны совпадать # 30 очков 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 zone not match with HELO" # Проверяем наличие обратной зоны для хоста # 30 очков 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" # Проверяем число точек и дефисов в доменном имени. Если больше 4-х уже такой домен вызывает подозрение # 40 очков 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 = "STAGE3: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name - more dots or defice in name" # Проверяем общую длину адреса отправителя. Больше 30 символов вызывает подозрение # 10 очков warn condition = ${if >{${strlen:$sender_address}}{30}{yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+10} logwrite = STAGE4: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with HELO=$sender_helo_name \ - many big sender address [$sender_address] # Очки за диалап в имени хоста. В данном правиле используется файлик со списком масок. Честно, за давностью лет, не помню где его взял # Его Вы можете найти в архиве с БД, ссылка на которую приведена в начале статьи # 60 очков warn condition = ${lookup{$sender_host_name} \ wildlsearch{/usr/local/etc/exim/dialup_hosts} \ {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 \ host=$sender_helo_name - dialup, ppp & etc..." # Очки за диалап в HELO # 60 очков warn condition = ${lookup{$sender_helo_name} \ wildlsearch{/usr/local/etc/exim/dialup_hosts} \ {yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+60} logwrite = "STAGE6: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name - dialup, ppp & etc..." # Проверяем есть ли зона первого уровня из HELO # 150 очков 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 warn set acl_m2 = 0 # Проверяем, не было ли письма на этот адрес от наших пользователей. # Тут же заполняем whitelist если письмо от нашего локального отправителя 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 = STAGE7: $sender_address ==> $local_part@$domain; \ setting acl_m2 = $acl_m2; WHITELIST for this addresses # Проверка наличия домена в whitelist 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 = STAGE8: $sender_address ==> $local_part@$domain; \ setting acl_m2 = $acl_m2; WHITELIST for ALL domains # Сбрасываем переменную с очками спама если домен в whitelist или наши пользователи общались с данным респондентом 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 # Проверка получателя в локальных доменах. Если не проходит, то проверяется следующий ACL accept domains = +local_domains endpass message = "In my mailserver not stored this user" verify = recipient # Проверяем получателя в релейных доменах accept domains = +relay_to_domains endpass message = "main server not know relay to this address" verify = recipient # Разрешаем почту от доменов в списке relay_from_hosts accept hosts = +relay_from_hosts # Если неподошло ни одно правило - явно ищут открытый релей deny message = "This is not open relay"
Теперь перейдем к проверке тела письма.
acl_check_data: deny message = contains $found_extension file (blacklisted). demime = com:vbs:bat:pif:scr:exe deny message = This message contains a MIME error ($demime_reason) demime = * condition = ${if >{$demime_errorlevel}{2}{1}{0}} deny message = This message contains NUL characters log_message = NUL characters! condition = ${if >{$body_zerocount}{0}{1}{0}} deny message = Incorrect headers syntax hosts = !+relay_from_hosts:* !verify = header_syntax # Здесь можем отклонить почту по подсчитанным ранее очкам спама. При сумме очков более 99, считаем, что это спам. # Но, как показала практика, после раскоментирования следующих строчек, юзеры начинают очень нервничать и # переживать - а работает ли вообще у нас почта, и где любимый спам по утрам :) А потому далее мы используем эти # очки немного по другому #deny message = Possible SPAM message # log_message = Possible SPAM message # condition = ${if >{$acl_m0}{99}{yes}{no}} # Пропускаем остальное accept
В exim есть механизм system filter. Вот туда и добавляем
if $acl_m0 matches ^\\d+ then logwrite "FILTER: debug - digit in variable acl_m0 = $acl_m0 (after first if)" if $acl_m0 is above 99 then headers add "X-Spam-Description: if spam count >= 100 - this is spam" headers add "X-Spam-Count: $acl_m0" headers add "Old-Subject: $h_subject:" headers remove "Subject" headers add "Subject: (*** SPAM ***) $h_old-subject:" headers add "X-Spam: YES" logwrite "EXIM FILTER: Spam count = $acl_m0 ; Added SPAM header" endif endif
Т.е. мы в начало темы письма вставляем сточку "(*** SPAM ***)", по которой клиенты пользователей уже отсортировуют спам.
Как видите набор правил не велик, но позволяет эффективно отфильтровывать спам, при этом не ставя никаких дополнительных систем. Конечно есть вероятность ложных срабатываний, но после первого же письма от нашего пользователя к тому, которого случайно включили в спам, и он попадает в whitelist.
Повторюсь, по моим наблюдениям, такие достаточно простые правила не пропускают где-то 97% спама.
