Бронированный Exim

В настоящей статье пойдет речь о небезызвестном агенте пересылки почты Exim, а именно о его настройке в части реализации достаточно мощной системы защиты от спама, блокирования подозрительных хостов, возможно рассылающих спам, защиты от подбора паролей и других полезных радостей положительно влияющих на общую защищенность почтового сервера. Материал решил оформить отдельно, т.к. редактирование (дополнение) ранее выпущенной статьи о настройке Exim будет не наглядным. Планируется периодическое дополнение и обновление данного материала, т.к. нет предела совершенству, со временем появляются новые идеи по усилению системы защиты почтового сервера. Многие приведенные в данной статье настройки Exim подходят без адаптации к конфигурации почтового сервера, описанной в статье о настройке почтовой системы. Описание системы защиты почтового сервера я начну с теоретической части, после чего перейду к технической с приведением конфигурационных директив Exim.

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

  1. использование антивирусных программных средств;
  2. использование средств анализа сообщений (Rspamd, SpamAssassin и т.п.);
  3. запрет пересылки через сервер сообщений неизвестными хостами;
  4. никому нельзя доверять или выявление рассылки спама локальными пользователями;
  5. нельзя быть полностью в чем-то уверенным или использование в работе почтового сервера технологии, так называемых серых списков, на основе бальной системы;
    • проверка хостов, направляющих сообщения на наш сервер, на предмет соответствия требованиям RFC;
    • использование технологий, направленных на борьбу с рассылкой спама (SPF, DNSBL и т.п.);
    • использование спам-ловушек;
  6. блокирование попыток подбора паролей к почтовым ящикам пользователей;
  7. использование шифрованных соединений (везде где это возможно);
  8. использование криптографических алгоритмов с сильной криптографической стойкостью;
  9. использование актуальных версий программного обеспечения на почтовом сервере;
  10. постоянный мониторинг (контроль) работы почтового сервера.

По п.п. 1,2 думаю итак все понятно, поскольку зараженные вирусами компьютеры пользователей могут рассылать спам, как и в рассылаемом спаме могут содержаться ссылки на вредоносный код. Пункт 3, думаю, понятен и не нуждается в комментарии. Пункт 4 гласит, что необходимо предусмотреть защиту от негодяев, намеревающихся использовать учетную запись на сервере для рассылки спама. Пункт 5 гласит, что все проверки должны быть нацелены на выявление признаков рассылки спама, а не на констатацию фактов, поскольку полностью быть уверенным, что с хоста ведется только рассылка спама нельзя. Принцип под номером 6 гласит о том, что взлом учетной записи на сервере необходимо максимально усложнить. Пункты 7 и 8 логичны, поскольку в настоящее время передавать по открытым каналам связи чувствительную информацию совсем небезопасно. Также логичен пункт 9, поскольку в программном обеспечении часто находят уязвимости. Ну а следить за работой сервера, хотя бы периодически :) обязанность каждого администратора, о чем и гласит пункт 10 списка.

Следует отметить, что технология серых списков реализована мной следующим образом. Определение хоста (желающего отправить почту на наш сервер) как предполагаемого спамера происходит на основе баллов. Это значит, что практически все проверки в ACL не являются запрещающими, а накидывающими некоторое количество баллов при попадании под какое-нибудь правило. Хосты, набравшие сравнительно большое количество баллов, благополучно размещаются в локальном черном списке. Хосты, набравшие такое количество баллов, что их нельзя отнести ни к легитимным хостам, ни к рассылающим спам, заворачиваются в серый список на 29 минут. По прошествии 29 минут (большинство серверов настроены так, что повтор отправки писем осуществляется через 30 минут), если хост повторил передачу, то от него принимается письмо и отправитель заносится в белый список, точнее хэш сумма от отправителя и получателя. От остальных хостов почта принимается в обычном порядке. Чисткой устаревших записей в списках занимается скрипт по крону (его код здесь не будет приведен). Также отмечу, что с целью упрощения отладки правил в ACL и решения проблем, в случае их возникновения на этапе приема письма, в данной конфигурации я сохраняю информацию о проведенных проверках в ACL, которые не прошла отправляющая сторона (см. переменную acl_c_spamlog). Система баллов подобрана мной так, чтобы нарушители при провале важных на мой взгляд проверок попадали либо в серый список, либо в черный, за малозначимые косяки никаких санкций не применяется (при желании можно разработать другую систему баллов, установить новые пороги для попадания в серый и черный списки). Стоит отметить, что называемые мной здесь списки по сути являются обычными таблицами в базе данных, о чем подробно написано на первой странице статьи о настройке почтовой системы.

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

  1. ######################################################################
  2. #                    Определение макросов                            #
  3. ######################################################################
  4.  
  5. # Внешний IP адрес, на котором осуществляется прием почты
  6. EXT_IP = ***.***.***.***
  7.  
  8. # Директория, в которой расположены дополнительная конфигурация
  9. EXTRA_PREFIX = /usr/local/etc/exim/extra
  10.  
  11. # SQL запрос для включения в список хостов, с которых осуществлялись
  12. # попытки подбора паролей к аккаунтам пользователей.
  13. INSERT_CRACK_IP = \
  14.   ${lookup pgsql{\
  15.     INSERT INTO "crackhosts_tb"("ip", "description") VALUES (\
  16.     '${quote_pgsql:$sender_host_address}',\
  17.     'Cracking a user ${quote_pgsql:$acl_c_af_id}')\
  18.    }{yes}{yes}}
  19.  
  20.  
  21. ####################################################################
  22. #                    Основные параметры                            #
  23. ####################################################################
  24.  
  25. # Задаем новые списки. На эти списки можно ссылаться далее в
  26. # конфигурационном файле, используя следующий синтаксис
  27. # +blacklist, +badhosts, и +crackhosts.
  28.  
  29. # "Черный" список IP адресов хостов, которые попали сюда
  30. # за нарушения множества правил RFC, т.е. провалили проверки в ACL
  31. hostlist blacklist = ${lookup pgsql{SELECT "ip" FROM "blacklist_tb" \
  32.            WHERE "ip" = '${quote_pgsql:$sender_host_address}'}}
  33.  
  34. # Список IP адресов хостов, с которых выявлены факты рассылки спама,
  35. # однако с точки зрения RFC настроены правильно. Например, это 
  36. # могут быть легитимные почтовые серверы, которые были хакнуты.
  37. # Выявляются самостоятельно администратором почтового сервера и
  38. # собственноручно добавляются в данный список по его усмотрению.
  39. hostlist badhosts = ${lookup pgsql{SELECT "ip" FROM "badhosts_tb" \
  40.            WHERE "ip" = '${quote_pgsql:$sender_host_address}'}}
  41.  
  42. # Список хостов, которые очень активно осуществляли подбор паролей
  43. # к учетным записям пользователей
  44. hostlist crackhosts = ${lookup pgsql{SELECT "ip" FROM "crackhosts_tb" \
  45.            WHERE "ip" = '${quote_pgsql:$sender_host_address}'}}
  46.  
  47. # Объявляем ACL, которые необходимы для реализации поставленной задачи.
  48. # Назначение каждой ACL будет понятно исходя из заданных в ней правил фильтрации.
  49. # Дополнительно рекомендую почитать документацию Exim по данным ACL, чтобы
  50. # понимать на каком этапе происходит срабатывание правил.
  51. acl_not_smtp     = acl_check_not_smtp
  52. acl_smtp_auth    = acl_check_auth
  53. acl_smtp_connect = acl_check_connect
  54. acl_smtp_mail    = acl_check_mail
  55. acl_smtp_rcpt    = acl_check_rcpt
  56. acl_smtp_predata = acl_check_predata
  57. acl_smtp_dkim    = acl_check_dkim
  58. acl_smtp_data    = acl_check_data
  59. acl_smtp_notquit = acl_check_notquit
  60. acl_smtp_quit    = acl_check_quit
  61.  
  62. # Указываем параметры для взаимодействия с антивирусным ПО. Я использую ClamAV.
  63. # Смотрите документацию, чтобы узнать, как подключить другие антивирусы.
  64. av_scanner = clamd:/var/run/clamav/clamd.sock
  65.  
  66. # Данная опция задает параметры подключения к средству анализа сообщений
  67. Rspamd spamd_address = 127.0.0.1 11333 retry=3s variant=rspamd
  68.  
  69. # Указываем опции для библиотеки OpenSSL, которые будут использоваться
  70. # при установке защищенного соединения с хостами. Задается как список,
  71. # разделенные пробелами, где каждый элемент может быть добавлен "+added"
  72. # или исключен "-substracted" из текущего набора опций.
  73. # Внимание: для тех, у кого библиотека OpenSSL версии >= 1.0.0, желательно
  74. # в целях повышения надежности задать опцию "+no_compression" ("CRIME" attack)
  75. # (см. https://lists.exim.org/lurker/message/20121009.173420.ed5bd052.en.html)
  76. # Также отключаем устаревшие протоколы шифрования
  77. openssl_options = +no_sslv2 +no_sslv3 \
  78.                   +no_compression
  79.  
  80. # Разрешаем только стойкие шифры (ciphers) для входящих SSL/TLS
  81. # соединений (Exim должен быть скомпилирован с поддержкой OpenSSL. Для тех,
  82. # кто использует GnuTLS параметр задается по-другому, см. документацию).
  83. # Дополнительную информацию по шифрам можно получить по команде man ciphers.
  84. tls_require_ciphers = HIGH:MEDIUM:!LOW:!ADH:!RC4:!MD5:!EXP:!aNULL:!eNULL:!NULL
  85.  
  86. # В данной переменной задается шаблон для заголовка "Received", который
  87. # добавляется Exim в каждое сообщение. Данный шаблон раскрывается
  88. # при каждом приеме сообщения. Если параметр установлен как пустой,
  89. # тогда заголовок "Received" не будет добавляться в сообщения.
  90. # P.S. шаблон практически идентичный стандартному, за исключением того,
  91. # что в нем отсутствуют сведения о версии Exim.
  92. received_header_text = Received: \
  93.   ${if def:sender_rcvhost {from $sender_rcvhost\n\t}\
  94.   {${if def:sender_ident \
  95.   {from ${quote_local_part:$sender_ident} }}\
  96.   ${if def:sender_helo_name {(helo=$sender_helo_name)\n\t}}}}\
  97.   by $primary_hostname \
  98.   ${if def:received_protocol {with $received_protocol}} \
  99.   ${if def:tls_in_cipher {($tls_in_cipher)\n\t}}\
  100.   ${if def:sender_address \
  101.   {(envelope-from <$sender_address>)\n\t}}\
  102.   id $message_exim_id\
  103.   ${if def:received_for {\n\tfor $received_for}}
  104.  
  105. #####################################################################
  106. #                        Параметры ACL                              #
  107. #####################################################################
  108.  
  109.  
  110. begin acl
  111.  
  112. acl_check_not_smtp:
  113.   # Защищаемся от рассылки спама непосредственного с самого сервера,
  114.   # для чего ограничиваем количество отправляемых писем. В данном случае
  115.   # каждому пользователю допустимо слать не более 30 сообщений в час.
  116.   deny message   = Your ratelimit of outgoing mail is very high ($sender_rate / $sender_rate_period)
  117.        ratelimit = 30/1h/leaky/$local_part@$domain
  118.  
  119.   accept
  120.  
  121. acl_check_auth:
  122.   # Блокируем команду AUTH для хостов, которые пойманы на попытках взлома.
  123.   # Далее, в ACL acl_check_notquit, acl_check_quit будет видно, как
  124.   # осуществляется наполнение данного списка.
  125.   deny message = Your host is suspected of breaking
  126.        hosts   = +crackhosts
  127.  
  128.   # Сохраняем использованные удаленным хостом данные авторизации.
  129.   # Данная переменная используется в служебных целях для выявления
  130.   # попыток подбора пароля к учетным записям удаленным хостом.
  131.   # Отмечу, что я использую метод аутентификации PLAIN, причем
  132.   # он разрешен только через шифрованное соединение.
  133.   warn set acl_c_af_id = ${if match{$smtp_command_argument}\
  134.                             {\N(?i)^(?:login|plain) (.+)$\N}\
  135.                          {$1}}
  136.  
  137.   # Блокируем попытки авторизации (использования команды AUTH в
  138.   # рамках SMTP сессии) с частотой более 3 раз в 1 минуту
  139.   deny message     = The number of attempts of authorization is exceeded
  140.        hosts       = !+relay_from_hosts
  141.        ratelimit   = 3/1m/per_cmd/leaky/auth_${sender_host_address}
  142.        delay       = 5s
  143.        log_message = Ratelimit: $sender_rate/$sender_rate_period \
  144.                        (max - $sender_rate_limit)
  145.  
  146.   accept
  147.  
  148. acl_check_connect:
  149.   # Сохраняем в переменную содержимое обратной записи (PTR) для IP подключенного клиента
  150.   # В дальнейшем данная переменная используется для проверки соответствия хоста RFC
  151.   # Модификатор defer_never указан для того, чтобы Exim не делал повторных DNS запросов, иначе
  152.   # в логах постоянно будет маячить ошибка Warning: ACL "warn" statement skipped: condition test deferred:
  153.   # failed to expand ACL string "${lookup dnsdb{ptr=$sender_host_address}{$value}}": lookup of "ptr=***.***.***.***" gave DEFER:
  154.  
  155.   warn set acl_c_reverse_address = ${lookup dnsdb{defer_never,ptr=$sender_host_address}{$value}}
  156.  
  157.   accept
  158.  
  159. acl_check_mail:
  160.   # Инициализируем переменную, в которой сохраняется общее количество спам баллов
  161.   # Обратите внимание, что используется переменная типа acl_c, поскольку необходимо
  162.   # сохранять значение в течение SMTP-сессии
  163.   warn set acl_c_spamscore = 0
  164.  
  165.   # Блокируем хосты из локального черного списка
  166.   deny message = Your IP address in local blacklist.
  167.        hosts   = +blacklist
  168.  
  169.   # Как было сказано ранее, у меня есть список хостов, с которых
  170.   # была выявлена рассылка спама, однако данные хосты настроены по RFC.
  171.   # Такие хосты пройдут все проверки в ACL. Для того, чтобы ограничить
  172.   # рассылку спама с данных хостов, накидываем им пограничное количество
  173.   # баллов, чтобы они однозначно попали в серый список, а в случае
  174.   # провала какой-либо проверки в ACL попали в черный список.
  175.   warn hosts = +badhosts
  176.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 75}
  177.        set acl_c_spamlog = $acl_c_spamlog From your IP address spam goes to my host. \
  178.                               Please contact with postmaster if you consider that it not so.
  179.  
  180.   # Проверки HELO/EHLO
  181.   #---------------------------------------------------------
  182.  
  183.   # Накидываем сверху баллов за неверный HELO/EHLO
  184.   warn !authenticated = *
  185.        hosts = !+relay_from_hosts
  186.        condition = ${if and{\
  187.                            {!match{$sender_helo_name}{\N(?i)^([a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?\.)+[a-z]{2,6}$\N}}\
  188.                            {!eqi{$sender_helo_name}{[$sender_host_address]}}\
  189.                         }\
  190.                     }
  191.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 25}
  192.        set acl_c_spamlog = $acl_c_spamlog Bad HELO/EHLO;
  193.  
  194.   # Накидываем баллы за использование в HELO/EHLO любых данных,
  195.   # принадлежащих нашему серверу
  196.   warn !authenticated = *
  197.        hosts = !+relay_from_hosts
  198.        set acl_m_islocal = ${lookup pgsql{SELECT "domainname" FROM "domains_tb" \
  199.                               WHERE "domainname" = '${quote_pgsql:$sender_helo_name}'}{yes}{no}}
  200.        condition = ${if or{\
  201.                            {eq{$sender_helo_name}{EXT_IP}}\
  202.                            {eq{$sender_helo_name}{[EXT_IP]}}\
  203.                            {eqi{$sender_helo_name}{$primary_hostname}}\
  204.                            {eq{$acl_m_islocal}{yes}}\
  205.                           }\
  206.                     }
  207.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 50}
  208.        set acl_c_spamlog = $acl_c_spamlog Your HELO is one of local domain name;
  209.  
  210.   # Проверки DNS
  211.   #---------------------------------------------------------
  212.  
  213.   # Добавляем баллов за то, что нет обратного адреса для данного IP в DNS
  214.   warn !authenticated = *
  215.        hosts = !+relay_from_hosts
  216.        condition = ${if eq{$acl_c_reverse_address}{}}
  217.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 50}
  218.        set acl_c_spamlog = $acl_c_spamlog PTR == NULL;
  219.  
  220.   # Добавляем еще баллов за то, что обратная DNS запись не совпадает с прямой.
  221.   warn !authenticated = *
  222.        hosts = !+relay_from_hosts
  223.        condition = ${if !eqi{$acl_c_reverse_address}{$sender_helo_name}}
  224.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 25}
  225.        set acl_c_spamlog = $acl_c_spamlog PTR != HELO;
  226.  
  227.   # Добавляем баллов за то, что IP хоста из диапазона динамических адресов
  228.   # содержимое файла dynamic_pools можно увидеть в статье о настройке Exim, указанной в начале данного материала
  229.   warn !authenticated = *
  230.        hosts = !+relay_from_hosts
  231.        condition = ${lookup{$acl_c_reverse_address}wildlsearch{EXTRA_PREFIX/dynamic_pools}{yes}{no}}
  232.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 50}
  233.        set acl_c_spamlog = $acl_c_spamlog PTR in dynamic pools;
  234.  
  235.   # Проверки SPF записей (Exim должен быть скомпилирован с опцией SPF)
  236.   #---------------------------------------------------------
  237.   # Накидываем баллы за попытку отправить почту с сервера, не указанного в SPF
  238.   warn !authenticated = *
  239.        hosts = !+relay_from_hosts
  240.        spf = fail : softfail
  241.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 50}
  242.        set acl_c_spamlog = $acl_c_spamlog SPF fail;
  243.  
  244.   # За отсутствие записи SPF накидываем достаточное для попадания
  245.   # в серый список количество баллов
  246.   warn !authenticated = *
  247.        hosts = !+relay_from_hosts
  248.        spf = none
  249.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 25}
  250.        set acl_c_spamlog = $acl_c_spamlog SPF none;
  251.  
  252.   # Накидываем немного баллов за некорректно оформленную SPF запись
  253.   # или при возникновении ошибки во время её получения
  254.   warn !authenticated = *
  255.        hosts = !+relay_from_hosts
  256.        spf = permerror : temperror : neutral
  257.        set acl_c_spamscore = ${eval:$acl_c_spamscore + 25}
  258.        set acl_c_spamlog = $acl_c_spamlog SPF syntax error or not received;
  259.  
  260.   # Проверка IP в черных списках. За каждое срабатывание правила
  261.   # накидываем еще немного баллов. P.S. Периодически стоит проверять
  262.   # работоспособность данных сайтов, поскольку иногда они перестают
  263.   # работать и необходимо искать другие.
  264.   #---------------------------------------------------------
  265.   warn !authenticated = *
  266.        hosts          = !+relay_from_hosts
  267.        dnslists       = zen.spamhaus.org
  268.        add_header     = X-Warning: $sender_host_address is in a black list at $dnslist_domain
  269.        set acl_c_spamscore = ${eval:$acl_c_spamscore+25}
  270.        set acl_c_spamlog = $acl_c_spamlog Blacklist zen.spamhaus.org;
  271.  
  272.   warn !authenticated = *
  273.        hosts          = !+relay_from_hosts
  274.        dnslists       = dnsbl.sorbs.net
  275.        add_header     = X-Warning: $sender_host_address is in a black list at $dnslist_domain
  276.        set acl_c_spamscore = ${eval:$acl_c_spamscore+25}
  277.        set acl_c_spamlog = $acl_c_spamlog Blacklist $dnslist_domain;
  278.  
  279.   warn !authenticated = *
  280.        hosts          = !+relay_from_hosts
  281.        dnslists       = bl.spamcop.net:cbl.abuseat.org
  282.        add_header     = X-Warning: $sender_host_address is in a black list at $dnslist_domain
  283.        set acl_c_spamscore = ${eval:$acl_c_spamscore+25}
  284.        set acl_c_spamlog = $acl_c_spamlog Blacklist $dnslist_domain
  285.  
  286.   # Проверяем авторизованного пользователя на предмет подмены адреса отправителя
  287.   #---------------------------------------------------------
  288.   deny message = Address ($sender_address) does not match with authenticated data ($authenticated_id). Check your email program settings.
  289.        authenticated = *
  290.        condition = ${if !eq{$sender_address}{$authenticated_id}{yes}{no}}
  291.  
  292.   accept
  293.  
  294. # Данная ACL используется для каждой команды RCPT при получении писем.
  295. # Данную ACL я решил выложить полностью, т.к. важен порядок правил
  296. acl_check_rcpt:
  297.   # Принять, если отправитель - локальный хост (т.е. не через TCP/IP).
  298.   accept hosts = :
  299.          control = dkim_disable_verify
  300.  
  301.   ###################################################################
  302.   # Следующая секция ACL проверяет локальную часть адреса на предмет
  303.   # содержания символов [@%!/|.(точка)] в правильных местах.
  304.   #
  305.   # Символы кроме точек часто находятся не на своих местах, такое часто
  306.   # делают люди, которые надеются обойти ограничения. Поэтому, несмотря
  307.   # на то, что они допустимы в локальных частях, эти правила блокируют 
  308.   # такие попытки.
  309.   #
  310.   # Пустые компоненты адреса (случай, когда в адресе стоят две точки
  311.   # подряд) запрещены в RFC 2822, но Exim позволяет обойти такое
  312.   # ограничение, потому что они встретились (х/з как тут перевести:
  313.   # ....but Exim allows them because they have been encountered).
  314.   # (Предполагается, что адрес имеет вид
  315.   # "firstinitial.secondinitial.familyname", но что делать тем кто не имеет
  316.   # "secondinitial"). Однако, локальная часть адреса, начинающаяся с
  317.   # точки или содержащая /../ может доставить неприятности, если
  318.   # используется как часть файла (например, для списка рассылки).
  319.   # Такое же замечание справедливо и для локальных частей,
  320.   # которые содержат наклонные черты. Символ переадресации
  321.   # вывода (<, |, >) может также доставить проблемы, если локальная
  322.   # часть легкомысленно включена в командную строку оболочки.
  323.   #
  324.   # В связи с этим для проверки используется два правила. Первое
  325.   # используется для писем направленных для локальных доменов.
  326.   # Строка "domains = +local_domains" реализовывает сказанное:
  327.   # только локальные домены. Правило блокирует локальные части,
  328.   # начинающиеся с точки или содержащие символы @ % ! / или |.
  329.   # Если у вас есть локальные учетки имеющие в названии данные
  330.   # символы, то вам необходимо модифицировать данное правило.
  331.  
  332.   deny    message       = Restricted characters in address
  333.           domains       = +local_domains
  334.           local_parts   = ^[.] : ^.*[@%!/|]
  335.  
  336.   # Второе правило применяется для остальных доменов и оно
  337.   # не такое строгое как предыдущее.
  338.   # Строка "domains = !+local_domains" указывает для каких доменов
  339.   # применять правило. Данное правило позволяет локальным
  340.   # пользователям отправлять письма во внешний мир, где адресаты
  341.   # могут иметь косую или вертикальную черту в локальной части.
  342.   # Так же правило блокирует адреса, локальная часть которых
  343.   # начинается с точки, косой или вертикальной черты, но допускает
  344.   # их использование в любом другом месте локальной части.
  345.   # Локальная часть такого вида - /../ запрещена. Использование
  346.   # символов @ % и ! запрещено, как и в предыдущем правиле.
  347.   # Это сделано, чтобы локальные пользователи (или вирусы на их
  348.   # компьютерах) не могли каким-либо образом осуществить
  349.   # атаку на удаленный хост.
  350.  
  351.   deny    message       = Restricted characters in address
  352.           domains       = !+local_domains
  353.           local_parts   = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
  354.   ###################################################################
  355.  
  356.   # Добавляем баллов за то, что адрес отправителя совпадает с адресом получателя
  357.   warn condition = ${if eqi{$sender_address}{$local_part@$domain}{yes}{no}}
  358.        set acl_c_spamscore = ${eval:$acl_c_spamscore+25}
  359.        set acl_c_spamlog = $acl_c_spamlog Sender == recipient;
  360.  
  361.   # Добавляем баллов за отправку письма на адрес-ловушку
  362.   # P.S. В качестве адресов ловушек используются давно забытые заброшенные
  363.   # адреса или специально созданные. То есть это обычный почтовый ящик.
  364.   warn local_parts = spam : spamtrap
  365.        domains     = +local_domains
  366.        set acl_c_spamscore = ${eval:$acl_c_spamscore+50}
  367.        set acl_c_spamlog = $acl_c_spamlog Spamtrap;
  368.  
  369.   # Заканчиваем проверки в данной ACL и отправляем на следующую ACL,ку клиентов,
  370.   # набравших слишком много баллов для добавления их в локальный черный список
  371.   # (в список blacklist). Предполагается, что более менее нормально настроенный
  372.   # почтовый сервер не сможет набрать такое количество баллов, следовательно
  373.   # скорее всего к нам подключился спамер.
  374.   accept condition = ${if >={$acl_c_spamscore}{100}{yes}{no}}
  375.  
  376.   # Принимать письма для пользователя postmaster для любого локального
  377.   # домена независимо от источника и без проверки отправителя.
  378.   warn local_parts   = postmaster
  379.        domains       = +local_domains
  380.        set acl_c_spamscore = 0
  381.  
  382.   # Не даем использовать локальными хостами наш сервер для рассылки спама.
  383.   # Правило не дает принимать письма с локальных хостов (с которых мы разрешили пересылку),
  384.   # если не удалось проверить отправителя.
  385.   deny !authenticated = *
  386.        hosts = +relay_from_hosts
  387.        !verify = sender
  388.  
  389.   # Добавляем баллов за невозможность проверки существования адреса отправителя
  390.   # со всех других неизвестных нам хостов
  391.   warn hosts = !+relay_from_hosts
  392.        !verify = sender/callout=3m,defer_ok
  393.        set acl_c_spamscore = ${eval:$acl_c_spamscore+25}
  394.        set acl_c_spamlog = $acl_c_spamlog Callout error;
  395.  
  396.   # Проверять получателя во входящих письмах. Эта правило будет
  397.   # проводить проверку локальной части для локальных доменов, а
  398.   # а для удаленных проверку доменной части. Единственным способом
  399.   # проверять локальную часть для удаленных доменов использовать
  400.   # механизм обратных вызовов (добавить /callout), но сначала
  401.   # прочитайте в документации про этот механизм. 
  402.   require verify = recipient
  403.  
  404.   # Ограничиваем количество отправляемых всеми пользователями писем.
  405.   # Для хостов (с которых мы разрешили пересылку) разрешаем отправлять
  406.   # не более 30 писем в час. Авторизованным на нашем сервере
  407.   # пользователям разрешаем отправлять 70 сообщений в час.
  408.   deny message = Your ratelimit of outgoing mail is very high ($sender_rate / $sender_rate_period)
  409.        !authenticated = *
  410.        hosts = +relay_from_hosts
  411.        ratelimit = 30/1h/leaky/$local_part@$domain
  412.  
  413.   deny message = Your ratelimit of outgoing mail is very high ($sender_rate / $sender_rate_period)
  414.        authenticated = *
  415.        ratelimit = 70/1h/leaky/$authenticated_id
  416.  
  417.   # Принимать письма, которые приходят с хостов, для которых этот хост
  418.   # является релеем. Подразумевается, что эти хосты скорее всего MUA,
  419.   # так что здесь установлен модификатор control=submission, который
  420.   # заставляет Exim работать в режиме передачи. Это позволит подправить
  421.   # некоторые ошибки в письме, например, нет заголовка Date. Если этот
  422.   # хост является релеем для других MTA, то вам может понадобиться
  423.   # отключить эту плюшку. Если вы хотите пересылать письма с MTA
  424.   # и в "режиме передачи" с MUA, то вы должны разделить это правило
  425.   # на два и обрабатывать такие письма отдельно. 
  426.   accept  hosts         = +relay_from_hosts
  427.           control       = submission
  428.           control       = dkim_disable_verify
  429.  
  430.   # Принимать сообщение, если оно отправлено клиентом, прошедшим
  431.   # аутентификацию.
  432.   accept  authenticated = *
  433.           control = submission
  434.           control = dkim_disable_verify 
  435.  
  436.   # Запрещаем пересылать письма через наш сервер неизвестным хостам.
  437.   require message = relay not permitted
  438.           domains = +local_domains : +relay_to_domains
  439.  
  440.   accept
  441.  
  442. acl_check_predata:
  443.   # Запрещаем письма, отправленные нескольким адресатам от "пустого" отправителя.
  444.   deny message = Sorry, sender address <> disallowed for many rcpt commands
  445.        senders = :
  446.        condition = ${if >{$rcpt_count}{1}{yes}{no}}
  447.  
  448.  
  449.   # Можно писать в лог дополнительную ифномарцию о сообщениях, которые
  450.   # набрали немного спам баллов, чтобы добавить еще какие-нибудь проверки,
  451.   # если это сообщение все же окажется спамом.
  452.   # warn condition = ${if <{$acl_c_spamscore}{50}{yes}{no}}
  453.   #      condition = ${if >{$acl_c_spamscore}{0}{yes}{no}}
  454.   #      logwrite  = Debug: $acl_c_spamlog
  455.  
  456.   # Принимаем сообщение от почтового сервера, который набрал мало спам баллов
  457.   accept condition = ${if <{$acl_c_spamscore}{50}{yes}{no}}
  458.  
  459.   # Помучаем хост небольшой задержкой (многие хосты, рассылающие спам,
  460.   # не выдерживают такого испытания временем)
  461.   warn delay = 20s
  462.  
  463.   # Блочим хосты с большим количеством баллов и добавляем их
  464.   # в локальный черный список.
  465.   #---------------------------------------------------------
  466.   deny message = Sorry, your spam score very high. Reasons: $acl_c_spamlog
  467.        condition = ${if >={$acl_c_spamscore}{100}{yes}{no}}
  468.        condition = ${lookup pgsql{\
  469.                    DELETE FROM "blacklist_tb" WHERE "ip" = '$sender_host_address';\
  470.                    INSERT INTO "blacklist_tb" VALUES ('$sender_host_address', DEFAULT, \
  471.                      'Reason: ${quote_pgsql:$acl_c_spamlog}')}{yes}{yes}}
  472.  
  473.   # Реализация серого списка. Сюда попадают хосты, набравшие недостаточное
  474.   # количество баллов для попадания в локальный черный список, но
  475.   # превысившие максимальный порог для свободного прохождения письма.
  476.   # Эти хосты нельзя отнести ни к легитимным ни к спам хостам, поэтому лучше
  477.   # еще помучить их серым списком.
  478.   #---------------------------------------------------------
  479.   # Принимаем сообщение от хоста, который уже прощел проверку "серым списком"
  480.   accept condition = ${lookup pgsql{\
  481.                      SELECT "ip" FROM "whitelist_tb" WHERE "ip" = '$sender_host_address' \
  482.                         AND "addrhash" = md5('$sender_address')\
  483.                      }{yes}{no}}
  484.  
  485.   # Сообщаем отправителю, что пока он сидит в сером списке и ему еще надо подождать
  486.   defer message = Message deferred. Your address already exists in Greylist. Try again later. Reasons: $acl_c_spamlog
  487.         condition =  ${lookup pgsql{\
  488.                       SELECT "ip" FROM "greylist_tb" WHERE "ip" = '$sender_host_address' \
  489.                         AND "addrhash" = md5('$sender_address$local_part@$domain') \
  490.                         AND "ctime" + 1740 > date_part('epoch'::text, now())\
  491.                       }{yes}{no}}
  492.         delay = ${eval:$acl_c_spamscore/3}s
  493.  
  494.   # Принимаем сообщение, если отправитель выждал необходимое время
  495.   accept condition = ${lookup pgsql{\
  496.                      SELECT "ip" FROM "greylist_tb" WHERE "ip" = '$sender_host_address' \
  497.                        AND "addrhash" = md5('$sender_address$local_part@$domain') \
  498.                        AND "ctime" + 1740 <= date_part('epoch'::text, now())\
  499.                      }{yes}{no}}
  500.          condition = ${lookup pgsql{\
  501.                      DELETE FROM "greylist_tb" WHERE "ip" = '$sender_host_address' \
  502.                        AND "addrhash" = md5('$sender_address$local_part@$domain'); \
  503.                      INSERT INTO "whitelist_tb" VALUES('$sender_host_address', \
  504.                        md5('$sender_address'), DEFAULT)\
  505.                      }{yes}{yes}}
  506.  
  507.   # Добавляем отправителя в серый список
  508.   defer message = Message deferred. Your address added to Greylist. Try again later. Reasons: $acl_c_spamlog
  509.         condition = ${lookup pgsql{\
  510.                     INSERT INTO "greylist_tb" VALUES('$sender_host_address',\
  511.                       md5('$sender_address$local_part@$domain'), DEFAULT);\
  512.                     }{yes}{yes}}
  513.         delay = ${eval:$acl_c_spamscore/3}s
  514.  
  515.   deny
  516.  
  517. # Этот ACL используется после того, как получено тело письма. В этом ACL
  518. # вы можете проверять тело письма или его заголовки, в частности здесь
  519. # можно отправить тело письма на проверку антивирусом или спам сканером.
  520. # Примеры некоторых тестов приведены ниже и закомментированы.
  521. # Без этих тестов данная ACL принимает все сообщения. Если вы хотите
  522. # использовать данные тесты, то Exim должен быть собран с
  523. # соответствующими опциями (WITH_CONTENT_SCAN=yes in Local/Makefile).
  524. acl_check_data:
  525.  
  526.   # Запрещаем прием и отправку сообщений, в которых имеются строки
  527.   # с длиной более 1000 символов. По RFC любая строка в сообщении
  528.   # не должна превышать 1000 символов с учетом символов перевода строки (CRLF).
  529.   deny    message    = maximum allowed line length is 998 octets, \
  530.                        got $max_received_linelength
  531.           condition  = ${if > {$max_received_linelength}{998}}
  532.  
  533.   # Блочим письма с вирусами.
  534.   deny message = This message contains a virus ($malware_name).
  535.        malware = *
  536.  
  537.   # Пропускаем письмо через Rspamd. После проведенных проверок
  538.   # в определенных переменных появится информация о количестве набранных баллов.
  539.   warn spam = nobody:true
  540.  
  541.   # Блочить письма с неверным синтаксисом заголовков. При очень большом потоке
  542.   # писем лучше отключить эту проверку.
  543.   deny message = Invalid header syntax
  544.        !verify = header_syntax
  545.  
  546.   # Иногда средства анализа сообщений (например SpamAssassin) присваивают
  547.   # переменным отрицательные значения, что плохо сказывается при использовании
  548.   # такого значения в вычислениях. Такое бывает, когда письмо проходит успешно
  549.   # все проверки SpamAssassin.
  550.   warn set acl_m_spam_score_int = $spam_score_int
  551.        condition = ${if match{$spam_score_int}{\N^-\N}}
  552.        set acl_m_spam_score_int = 0
  553.  
  554.   # Если в письме не найдены заголовки DKIM, тогда необходимо объявить
  555.   # переменную, поскольку она используется далее в вычислениях
  556.   warn condition = ${if !def:acl_m_dkim_score}
  557.        set acl_m_dkim_score = 0
  558.  
  559.   # Добавляем информацию о проверках SPF в заголовки письма
  560.   warn condition = ${if def:spf_received}
  561.        add_header = :at_start:$spf_received
  562.  
  563.   # Подсчитываем общее количество спам баллов по результатам проверок
  564.   # Rspamd и DKIM. Полученный результат можно использовать в почтовой программе
  565.   # указав фильтр Sieve, который будет перемещать спам в соответствующую папку.
  566.   # Письма набравшие 50 очков и более можно смело относить к спаму.
  567.   warn add_header = X-Spamscore: $acl_c_spamscore\n\
  568.                     X-Rspamd: ${eval:($acl_m_spam_score_int)+($acl_m_dkim_score)}\n\
  569.                     X-Rspamd-action: $spam_action
  570.  
  571.   accept
  572.  
  573. # Здесь осуществляются проверки DKIM заголовков в письме. В зависимости от результата,
  574. # переменной $acl_m_dkim_score присваивается конкретное количество баллов, которое
  575. # в дальнейшем используется при подведении итогов в acl_check_data.
  576. # P.S. Я решил не использовать проверки DKIM для определения относимости хоста к спамерам,
  577. # так как данная проверка скорее позволяет определять относимость именно сообщения к спаму.
  578. acl_check_dkim:
  579.   # При отсутствии DKIM записей начинаем немного сомневаться в письме
  580.   warn dkim_status = none
  581.        add_header = :at_start:Authentication-Results: dkim=$dkim_verify_status (address=$sender_address domain=$dkim_cur_signer)
  582.        set acl_m_dkim_score = 10
  583.  
  584.   # В случае сбоя при проверке DKIM записей начинаем сомневаться в письме
  585.   warn dkim_status = fail
  586.        add_header = :at_start:Authentication-Results: dkim=$dkim_verify_status (address=$sender_address domain=$dkim_cur_signer); $dkim_verify_reason.
  587.        set acl_m_dkim_score = 15
  588.  
  589.   # В случае определения не соответствия DKIM записей начинаем сильно сомневаться в письме
  590.   warn dkim_status = invalid
  591.        add_header = :at_start:Authentication-Results: dkim=$dkim_verify_status (address=$sender_address domain=$dkim_cur_signer); $dkim_verify_reason.
  592.        set acl_m_dkim_score = 20
  593.  
  594.   # В случае коректности DKIM записей в письме не сомневаемся
  595.   warn dkim_status = pass
  596.        add_header = :at_start:Authentication-Results: dkim=$dkim_verify_status, header.i=@$dkim_cur_signer
  597.        set acl_m_dkim_score = 0
  598.  
  599.   accept
  600.  
  601. # Данная ACL срабатывает после завершения соединения с хостом (без команды QUIT,
  602. # например при обрыве соединения)
  603. acl_check_notquit:
  604.   # Если хост во время нескольких подключений безуспешно пытался авторизоваться
  605.   # под конкретной учетной записью, тогда можно судить о том, что с данного IP
  606.   # ведется подбор паролей к учеткам на сервере. В данном случае в список взломщиков
  607.   # попадают хосты, которые безуспещно авторизовывались под конкретной учетной
  608.   # записью более 5 раз за 1 минуту. Также здесь учитываются данные $acl_c_af_id.
  609.   # P.S. О безуспешной попытке авторизации в Exim можно судить по тому, что переменная
  610.   # $sender_host_authenticated не инициализирована, а $authentication_failed установлена в "1"
  611.  
  612.   warn !authenticated = *
  613.        hosts          = !+relay_from_hosts
  614.        condition      = ${if and{\
  615.                            {!def:sender_host_authenticated}\
  616.                            {={$authentication_failed}{1}}\
  617.                         }}
  618.        condition      = ${if def:acl_c_af_id}
  619.        set acl_c_afh  = ${sg{\
  620.                            ${nhash_1024_1024:$acl_c_af_id}}{/}{_}\
  621.                         }
  622.        ratelimit      = 5/1m/per_cmd/strict/auth_${sender_host_address}_${acl_c_afh}
  623.        condition      = INSERT_CRACK_IP
  624.        log_message    = Bruteforce attack from $sender_host_address ($sender_helo_name)
  625.  
  626.   accept
  627.  
  628. # Данная ACL срабатывает после завершения соединения с хостом (по команде QUIT)
  629. acl_check_quit:
  630.   # Если хост во время нескольких подключений безуспешно пытался авторизоваться
  631.   # под конкретной учетной записью, тогда можно судить о том, что с данного IP
  632.   # ведется подбор паролей к учеткам на сервере. В данном случае в список взломщиков
  633.   # попадают хосты, которые безуспещно авторизовывались под конкретной учетной
  634.   # записью более 5 раз за 1 минуту. Также здесь учитываются данные $acl_c_af_id.
  635.   # P.S. О безуспешной попытке авторизации в Exim можно судить по тому, что переменная
  636.   # $sender_host_authenticated не инициализирована, а $authentication_failed установлена в "1"
  637.  
  638.   warn !authenticated = *
  639.        hosts          = !+relay_from_hosts
  640.        condition      = ${if and{\
  641.                            {!def:sender_host_authenticated}\
  642.                            {={$authentication_failed}{1}}\
  643.                         }}
  644.        condition      = ${if def:acl_c_af_id}
  645.        set acl_c_afh  = ${sg{\
  646.                            ${nhash_1024_1024:$acl_c_af_id}}{/}{_}\
  647.                         }
  648.        ratelimit      = 5/1m/per_cmd/strict/auth_${sender_host_address}_${acl_c_afh}
  649.        condition      = INSERT_CRACK_IP
  650.        log_message    = Bruteforce attack from $sender_host_address ($sender_helo_name)
  651.  
  652.   # You do not need to have a final accept, but if you do, you can use
  653.   # a message modifier to specify custom text that is sent as part of
  654.   # the 221 response to QUIT.
  655.   accept
  656.  
  657. ######################################################################
  658. #                   Параметры аутентификации                         #
  659. ######################################################################
  660.  
  661. begin authenticators
  662.  
  663. # PLAIN метод. Клиент отправляет идентификатор сессии (который тут
  664. # не используется), логин и пароль. После, доступ к логину и паролю
  665. # можно получить через переменные $auth2 и $auth3 и проверить
  666. # их корректность.
  667.  
  668. # Аутентификация пользователя проводится средствами Dovecot.
  669. # Правило server_advertise_condition = ${if def:tls_cipher } запрещает
  670. # пользователям проходить аутентификаю, если соединение не защищено.
  671.  
  672. PLAIN:
  673.   driver                     = dovecot
  674.   public_name                = PLAIN
  675.   server_set_id              = $auth1
  676.   server_socket              = /var/run/dovecot/auth-client
  677.   server_advertise_condition = ${if def:tls_cipher }

Данные правила позволяют фильтровать большое количество спама. В связке с средством анализа сообщений Rspamd я уже практически и забыл что такое спам. Изредка конечно проскакивают сообщения с рекламой, но после скармливания их Rspamd, снова наступает мир и покой.

Добавить комментарий

Filtered text

CAPTCHA
Этот вопрос предназначен для предотвращения автоматизированной обработки форм.
Fill in the blank.