Предистория.В канторе где мне довелось работать я застал такую картину: часть почтовых ящиков находилась на FreeBSD со встроенным sendmail, а часть уже успели перенести на MS Exchange внутри локалки. При этом чтобы создать рабочий ящик нужно было проделать уйму телодвижений, к тому же сработала моя личная неприязнь к sendmail. Короче было решено переходить на MTA EXIM, почту с ящиков, которые на FreeBSD раздавать пользователям с помощью courier-imap, остальную передавать на MS Exchange. За основу были взяты конфиги с нескольких статей написанных Лисом ( http://www.lissyara.su) за что ему большое спасибо.
Для проверки существоваания пользователей в домене (цель - генерить отлупы до приёма сообщения, на уровне "rcpt to:" - иначе, они могут пойти по фальшивому обратному адресу), exim будет лазить напрямую в виндовый ldap - для этого заведён специальный пользователь в домене, без права логинитсья.
Для удобства, конфиг почтаря был разбит на несколько кусков. Была реализована схема с т.н. "белыми списками". Также, с сайта http://www.iana.org/ был утянут список всех доменов верхнего уровня - он используется в проверках HELO - существует ли такой домен (проверка регулярным выражением на синтаксис - всё же не так эффективна).
Итак. Обновляем порты, ставим exim. Для выставления всех опций один раз, и чтоб в дальнейшем не мучаться - как же я его собирал, выставляем нужные опции в /etc/make.conf, у меня он получился такой:
# added by use.perl 2008-02-13 21:02:25
PERL_VER=5.8.8
PERL_VERSION=5.8.8
# default MySQL version
DEFAULT_MYSQL_VER=51
PORTSDIR?= /usr/ports
.if ${.CURDIR} == ${PORTSDIR}/mail/exim
# Поддержка MySQL
WITH_MYSQL= yes
LOG_FILE_PATH?= syslog
# Контентное сканирование
WITH_CONTENT_SCAN= yes
# Дефолтовая кодировка заголовков
WITH_DEFAULT_CHARSET?= koi8-r
# Отключаем IPv6
WITHOUT_IPV6= yes
# Подрубаем LDAP
WITH_OPENLDAP= yes
WITH_OPENLDAP_VER = 23
# Версия BDB - в чём ведёт свои БД подсказок
WITH_BDB_VER= 4
# Проверки SPF
WITH_SPF= yes
# Подержка перекодировки (для заголовков)
WITH_ICONV= yes
.endif
# SQL server
.if ${.CURDIR} == ${PORTSDIR}/databases/mysql51-server
BUILD_OPTIMIZED=yes
BUILD_STATIC=yes
WITH_CHARSET=cp1251
WITH_COLLATION=cp1251_bin
#WITH_LINUXTHREADS=yes
WITH_PROC_SCOPE_PTH=yes
.endif
# MySQL client
.if ${.CURDIR} == ${PORTSDIR}/databases/mysql51-client
BUILD_OPTIMIZED=yes
BUILD_STATIC=yes
WITH_CHARSET=cp1251
WITH_COLLATION=cp1251_bin
.endif
Соответственно - вовсе не обязательно вносить все пункты, но я решил быть дословным :). После чего, можно ставить exim:
gw$ cd /usr/ports/mail/exim
gw$ make install clean
Если у Вас не стоит MySQL, то он его потащщит и поставит (не MySQL-сервер! А клиента.) Сам сервер, если он нужен на этой машине, надо ставить самому.
gw$ cd /usr/ports/databases/mysql51-server/
gw$ make install clean
После установки, такие движения:
gw$ echo 'mysql_enable="YES"' >> /etc/rc.conf
gw$ /usr/local/etc/rc.d/mysql-server.sh start
Starting mysql.
gw# ps -axj | grep mysql
mysql 811 1 809 48 0 I con- 0:00.01 /bin/sh /usr/local/bi
mysql 1013 811 809 48 0 S con- 53:02.03 /usr/local/libexec/my
root 19681 19366 19680 19337 2 RL+ p0 0:00.00 grep mysql
Далее - рисуем конфиги. По ним комментариев немного, они и сами неплохо прокоментированы (заодно оставил все старые ошмётки, от процесса их создания - может кому-то пригодятся). Прошу тока обратить внимание на имена файлов, был уже прикол, когда боролись с глюками у одного товарисча, а оказалось что не инклюдятся файлы - и, фактически, он работал на пустом конфиге :).
/usr/local/etc/exim/configure:
# Инклюдим главные настройки
.include_if_exists /usr/local/etc/exim/includes/100.main.conf
### конфигурация ACL для входящей почты
begin acl
# Эти правила срабатывают для каждого получателя
acl_check_rcpt:
# Начало файла ACL - те, кого срубаем сразу же
.include_if_exists /usr/local/etc/exim/includes/200.acl_check_rcpt.conf
# Проверки на спам - по хостам и прочему.
.include_if_exists /usr/local/etc/exim/includes/300.acl_check_rcpt_spam_rule.conf
# Конец конфигурации - блэк-листы, задержки и прочее
.include_if_exists /usr/local/etc/exim/includes/400.acl_check_rcpt_end.conf
# Тут идут ACL проверяющие содержимое (тело) письма.
# Без них будут пропускаться все сообщения.
acl_check_data:
# Инклюдим конфигурацию проверки тела письма
.include_if_exists /usr/local/etc/exim/includes/500.acl_check_data.conf
# чё делаем с почтой
begin routers
# Инклюдим конфигурацию роутреров
.include_if_exists /usr/local/etc/exim/includes/600.routers.conf
# начинаются транспорты - как доставляем почту
begin transports
# Инклюдим транспорты
.include_if_exists /usr/local/etc/exim/includes/700.transports.conf
# Конфигурация повторов и перезаписи
.include_if_exists /usr/local/etc/exim/includes/800.retry_and_rewrite.conf
# Секция авторизации при отправке писем. Ввиду того,
# что почтовых клиентов много, и все всё делают
# по-своему, то и механизмов авторизации три...
begin authenticators
# Аутентификация юзеров.
.include_if_exists /usr/local/etc/exim/includes/900.authenticators.conf
/usr/local/etc/exim/includes/100.main.conf:
# Имя хоста. Используется в EHLO.
# Фигурирует в других пунктах, если они не заданы -
# типа qualify_domain и прочих..
# Если тут ничё не установлено (строка закомметрована)
# то используется то, что вернёт функция uname()
primary_hostname = gw.komarovka.org.ua
# Вводим данные для подключения к MySQL серверу.
# словечко `hide`, вначале, означает, что при
# вызове проверки конфига командой
# exim -bV config_file эти данные не будут отображаться.
# Если без него - то будут показаны... Формат записи:
# хост/имя_бд/пользователь/пароль
hide mysql_servers = localhost/exim/exim/exim
# Эксчейнджевый домен
MS_EXCHANGE_DOMAIN = komarovka.org.ua
# внутренний IP машины
INTERNAL_IP = 192.168.2.20
# внимание - изменённ дефолтовый разделитель списка!
ldap_default_servers = <; 192.168.2.4:3268
#; dc2.domain.org:3268
# макросы для работы с LDAP
LDAP_AD_BINDDN = exim
LDAP_AD_PASS = eximpass
LDAP_AD_BASE_DN = DC=komarovka,DC=org,DC=ua
# макрос для проверки пользователей в домене
LDAP_AD_MAIL_RCPT = \
user=LDAP_AD_BINDDN \
pass=LDAP_AD_PASS \
ldap:///LDAP_AD_BASE_DN?mail?sub?\
(&(|(objectClass=user)(objectClass=publicFolder)(objectClass=group))\
(|(proxyAddresses=${quote_ldap:${local_part}@MS_EXCHANGE_DOMAIN})\
(proxyAddresses=smtp:${quote_ldap:${local_part}@MS_EXCHANGE_DOMAIN}))\
(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
# Делаем список локальных доменов. Далее этот
# список будет фигурировать в виде +local_domains
# В данном случае домены выбираются из БД MySQL. Также
# можно их просто перечислить через двоеточие. Есть интересная
# возможность, можно указать юзер@[хост] - az@[222.222.4.5]
domainlist local_domains = ${lookup mysql{SELECT `domain` \
FROM `domain` WHERE \
`domain`='${domain}' AND \
`active`='1'}}
#domainlist local_domains = mail.short-dom.ru : relay.long-domain.ru
#domainlist local_domains = komarovka.org.ua : mail1.komarovka.org.ua
# делаем список доменов с которых разрешены релеи.
# Далее этот список будет в виде +relay_to_domains
# Можно использовать символы подстановки, типа:
# .... = *.my.domen.su : !spam.my.domen.su : first.su
# тогда пропускается всё, что похоже на *.my.domen.su, но
# от spam.my.domen.su релеится почта не будет.
#domainlist relay_to_domains = ${lookup mysql{SELECT `domain` \
# FROM `domain` WHERE \
# `domain`='${domain}' AND \
# `active`='1'}}
#domainlist relay_to_domains = long-domain.ru : short-dom.ru
#domainlist relay_to_domains = mail1.komarovka.org.ua : komarovka.org.ua
domainlist relay_to_domains = komarovka.org.ua
# Составляем список хостов с которых разрешён неавторизованый
# релей. Обычно в нём находятся локальные сети, и локалхост...
# ЛокалХост в двух видах был внесён сознательно - пару раз
# сталкивался с кривым файлом /etc/hosts - результатом было
# непонимание `localhost` но пониманием 127.0.0.1/8
hostlist relay_from_hosts = localhost : 127.0.0.0/8 : 192.168.2.2
# Вводим названия acl`ов для проверки почты. (В общем-то, это
# необязательно, если вы делаете открытый релей, или хотите
# принимать вообще всю почту с любого хоста для любых
# получателей... Тока потом не жалуйтесь что у Вас спам
# и провайдер выкатывает немеряный счёт :))
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
# Прикручиваем антивирус - при условии, что exim собран
# с его поддержкой. В качестве антивиря юзаем ClamAV,
# ибо - ПО должно быть свободным! :)
# Итак, указываем местоположение сокета clamd.
av_scanner = clamd:/var/run/clamav/clamd
# Адрес куда слать на проверку спама (SpamAssasin), но я
# это не юзаю. Не так много у меня спама...
# spamd_address = 127.0.0.1 783
# Имя домена добавляемое для локальных отправителей (реальных
# юзеров системы) т.е. почта отправляемая от root, будет от
# root@домен_указанный_здесь. Если пункт незадан, то используется
# имя хоста из `primary_hostname`. Логичней было бы написать здесь
# az.su, но мне удобней иначе:
qualify_domain = komarovka.org.ua
# Имя хоста для ситуации, обратной предыдущей, - это имя домена
# добавляемое к почте для системных юзеров, ну и вообще для почты
# пришедшей на адрес типа `root`, `az`, & etc... Если этот
# пункт незадан то используется значение полученное из
# предыдущего пункта - `qualify_domain`
qualify_recipient = komarovka.org.ua
# A это как раз кусок вышеописанного анахронизма - про почту в
# виде user@[222.222.222.222] - принимать её или нет. По дефолту
# (когда строка закомментирована) значение - false. Если захотите
# поставить true то надо будет добавить в список доменов
# комбинацию @[] - она означает `все локальные адреса`
allow_domain_literals = false
# Пользователь от которого работает exim
exim_user = mailnull
# группа в кторой работает exim
exim_group = mail
# запрещаем работу доставки под юзером root - в целях безопасности
never_users = root
# Проверяем соответствие прямой и обратной зон для всех хостов.
# Тока зачем это нужно - даже и незнаю... Спам на этом не режется...
# Зато возможны проблемы - если сервер зоны скажет `сервер файлед`
# то почту от этого хоста Вы не получите :)
#host_lookup = *
# Тоже анахронизм (на самом деле, не такой уж анахронизм, но все давно
# забили на ident и закрыли файрволлом tcp:113...) Это проверка - Ваш
# хост спрашивает у удалённого, с которого было подключение, а кто
# собстно ко мне подключился на такой-то порт? Если на удалённом хосте
# работает identd - он может ответить (а может и не ответить - как
# настроить), скажет UID пользователя от которого установлено
# соединение, тип ОС, и имя пользователя. Теперь, понимаете, почему
# у всех оно зарублено и файрволлами позакрыто? :) Это же палево :)
# Тока на мой взгляд, если на сервере всё настроено правильно -
# то вовсе это и не страшно.
# Короче - если хостс поставить * то будет проверять все. Таймаут -
# если поставить 0 то не будет ждать ответа ни от кого. По
# вышеописанным причинам - отключаем
#rfc1413_hosts = *
rfc1413_query_timeout = 0s
# По дефолту, экзим отфутболивает все `неквалифицированные` адреса,
# состоящие тока из локальной части. Для того чтобы разрешить такие письма
# определённых хостов используются эти директивы:
# для `неквалифицированных` отправителей
sender_unqualified_hosts = +relay_from_hosts
# для `неквалифицированных` получателей
recipient_unqualified_hosts = +relay_from_hosts
# Если сообщение было недоставлено, то генерится соощение
# об ошибке. Если сообщение об ошибке не удалось доставить
# то оно замораживается на указанный в этом пункте срок,
# после чего снова попытка доставить его. При очередной
# неудаче - сообщение удаляется.
ignore_bounce_errors_after = 45m
# Замороженные сообщения, находящиеся в очереди, дольше
# указанного времени удаляются и генерится сообщение
# об ошибке (при условии, что это не было недоставленное
# сообщение об ошибке :))
timeout_frozen_after = 2d
# собсно на этом штатный конфиг кончился, но
# меня-то это не устраивает... Поэтому пошли пункты,
# почёрпнутые из других источников.
# список адресов, через запятую, на которые засылаются
# сообщения о замороженных сообщениях (о замороженых
# уведомлениях о заморозке, сообщения не генерятся. - я
# надеюсь эта строка понятна :))
#freeze_tell = az@komarovka.org.ua
# Список хостов, почта от которых принимается, несмотря
# на ошибки в HELO/EHLO (тут указана моя подсеть)
helo_accept_junk_hosts = 192.168.2.0/24
# Через какое время повторять попытку доставки
# замороженного сообщения
auto_thaw = 2h
# Приветствие сервера
smtp_banner = "$primary_hostname, ESMTP EXIM $version_number"
# Максимальное число одновременных подключений по
# SMTP. Рассчитывать надо исходя из нагрузки на сервер
smtp_accept_max = 50
# максимальное число сообщений принимаемое за одно соединение
# от удалённого сервера (или пользователя). C числом 25
# я имел проблемы тока один раз - когда у меня три дня лежал
# инет и после его подъёма попёрли мессаги. Но у меня не так
# много почты - всего 30 пользователей.
smtp_accept_max_per_connection = 50
# чё-то про логи и борьбу с флудом - я так понимаю -
# максимальное число сообщений записываемых в логи
smtp_connect_backlog = 30
# максимальное число коннектов с одного хоста
smtp_accept_max_per_host = 20
# Ход ладьёй - для увеличения производительности,
# директория `spool` внутри, разбивается на
# директории - это ускоряет обработку
split_spool_directory = true
# Если у сообщения много адресатов на удалённых хостах,
# то запускатеся до указанного числа максимально число
# параллельных процессов доставки
remote_max_parallel = 15
# при генерации сообщения об ошибке прикладывать
# не всё сообщение, а кусок (от начала) указанного
# размера (иногда полезно и целиком - в таком случае
# просто закомментируйте эту строку)
return_size_limit = 50k
# размер сообщения. У меня стоит относительно большой
# размер (`относительно` - потому, что на большинстве
# хостов оно ограничено 2-5-10мб, либо стоит анлим.)
message_size_limit = 15M
# разрешаем неположенные символы в HELO (столкнулся
# с этим случайно - имя фирмы состояло из двух слов
# и какой-то раздолбай домен обозвал my_firme_name
# прям с подчёркиваниями... Виндовые клиенты при
# соединении радостно рапортовали о себе
# `vasya.my_firme_name` ну а экзим их футболил :))
helo_allow_chars = _
# Принудительная синхронизация. Если отправитель
# торопится подавать команды, не дождавшись ответа,
# то он посылается далеко и надолго :) Немного,
# спам режется.
smtp_enforce_sync = true
# Выбираем, что мы будем логировать
# + - писать в логи,
# - - Не писать в логи.
# +all_parents - все входящие?
# +connection_reject - разорваные соединения
# +incoming_interface - интерфейс (реально - IP)
# +lost_incoming_connections - потеряные входящие
# соединения
# +received_sender - отправитель
# +received_recipients - получатель
# +smtp_confirmation - подтверждения SMTP?
# +smtp_syntax_error - ошибки синтаксиса SMTP
# +smtp_protocol_error - ошибки протокола SMTP
# -queue_run - работа очереди (замороженные мессаги)
#log_selector = \
# +all \
# -incoming_port \
# -incoming_interface \
# -arguments \
# -smtp_connection \
# -lost_incoming_connection \
# -queue_run \
# -smtp_confirmation \
# -smtp_syntax_error \
# -smtp_protocol_error
log_selector = \
+all_parents \
+connection_reject \
+incoming_interface \
+lost_incoming_connection \
+received_sender \
+received_recipients \
+smtp_confirmation \
+smtp_syntax_error \
+smtp_protocol_error \
-queue_run
# +connection_reject \
# +incoming_interface \
# +lost_incoming_connection \
# +received_sender \
# +received_recipients
# Системный фильтр, копирование проходящей почты
system_filter = /usr/local/etc/exim/filters/system-filter
system_filter_pipe_transport = address_pipe
system_filter_user = mailnull
system_filter_group = mail
# Убираем собственную временную метку exim`a из логов, её ставит
# сам syslogd - нефига дублировать
syslog_timestamp = no
log_file_path = syslog : /usr/local/var/log/exim/%s-%D.log
/usr/local/etc/exim/includes/200.acl_check_rcpt.conf:
# Проверка получателей
# принимать сообщения которые пришли с локалхоста,
# не по TCP/IP
accept hosts = :
# Запрещаем письма содержащие в локальной части
# символы @; %; !; /; |. Учтите, если у вас было
# `percent_hack_domains` то % надо убрать.
# Проверяются локальные домены
deny domains = +local_domains
message = "incorrect symbol in address"
local_parts = ^[.] : ^.*[@%!/|]
delay = 30s
# Проверяем недопустимые символы для
# нелокальных получателей:
deny domains = !+local_domains
message = "incorrect symbol in address"
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
delay = 30s
# Принимаем почту для постмастеров локальных доменов без
# проверки отправителя (я закомментировал, т.к. это -
# основной источник спама на мой ящик).
# accept local_parts = postmaster
# domains = +local_domains
# Запрещщаем, если невозможно проверить отправителя
# (отсутствует в списке локальных пользователей)
# У себя я это закоментил, по причине, что некоторые
# железяки (принтеры, & etc) и программы (Касперский, DrWEB)
# умеют слать почту, в случае проблем но не умеют ставить
# нужного отправителя. Такие письма эта проверка не пускает.
require verify = sender
# Запрещщаем тех, кто не обменивается приветственными
# сообщениями (HELO/EHLO)
deny condition = ${if eq{$sender_helo_name}{}{yes}{no}}
message = "HELO/EHLO require by SMTP RFC"
delay = 30s
# Рубаем нах, тех, кто подставляет свой IP в HELO
deny hosts = !+relay_from_hosts:*
condition = ${if isip{$sender_helo_name}{yes}{no}}
message = We don't allow domain literals, many spam...
delay = 30s
# Рубаем хело с нашим именем
deny condition = ${if match_domain{$sender_helo_name} \
{$primary_hostname:+local_domains:+relay_to_domains} \
{true}{false}}
hosts = !+relay_from_hosts : *
message = Message was delivered by ratware - own
log_message = remote host used our name in HELO/EHLO.
delay = 30s
# Рубаем тех, кто в HELO пихает мой IP (2500 за месяц!)
deny condition = ${if eq{$sender_helo_name}\
{$interface_address}{yes}{no}}
hosts = !127.0.0.1 : !localhost : *
message = It is my IP!
# Рубаем мудаков с недопустимыми символами в helo (пока видел тока такие)
deny condition = ${if match{$sender_helo_name}{\N_\N}{yes}{no}}
hosts = !127.0.0.1 : !localhost : !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+20}
message = Invalid HELO
# Ограничения эксчейнджа - юзер не может начинаться/заканчиваться точкой.
deny senders = \N^\.|\.@\N
message = Invalid address
# Рубаем хосты типа *adsl*; *dialup*; *pool*;....
# Нормальные люди с таких не пишут. Если будут
# проблемы - уберёте проблемный пункт (у меня клиенты
# имеют запись типа asdl-1233.zone.su - я ADSL убрал...)
deny condition = ${if match{$sender_host_name} \
{adsl|dialup|pool|peer|dhcp} \
{yes}{no}}
message = dialup host
/usr/local/etc/exim/includes/300.acl_check_rcpt_spam_rule.conf:
# Проверка на спам - по хосту
# тут оставлено много отладки - раскомментите что надо...
# Задержка. (это такой метод борьбы со спамом,
# основанный на принципе его рассылки) На этом рубается
# почти весь спам. Единственно - метод неприменим на
# реально загруженных MTA - т.к. в результате ему
# приходится держать много открытых соединений.
# но на офисе в сотню-две человек - шикарный метод.
#
# более сложный вариант, смотрите в статье по exim и
# курьер имап. Т.к. там метод боле умный (просто правил
# больше :), то можно и на более загруженные сервера ставить)
# # Вводим переменную 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
/usr/local/etc/exim/includes/400.acl_check_rcpt_end.conf:
# Конец конфигурации проверки отправителя
# Сбрасываем спамерскую переменную, если домен в белом списке
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
# ставим задержку в 0 секунд своим хостам
warn hosts = +relay_from_hosts
set acl_c0 = 0s
# Ставим нулевую задержку хостам из белого листа
warn condition = ${if eq{$acl_m2}{1}{yes}{no}}
set acl_c0 = 0s
# пишем в логи задержку (если оно вам надо)
# 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.
warn
delay = $acl_c0
# Рубаем тех, кто в блэк-листах. Серваки перебираются
# сверху вниз, если не хост не найден на первом, то
# запрашивается второй, и т.д. Если не найден ни в одном
# из списка - то почта пропускается.
deny message = "you in blacklist - $dnslist_domain --> \
$dnslist_text; $dnslist_value"
hosts = !+relay_from_hosts
domains = !+relay_to_domains
dnslists = cbl.abuseat.org : \
dynablock.njabl.org : \
dul.dnsbl.sorbs.net : \
list.dsbl.org : \
bl.spamcorp.net : \
opm.blitzed.org : \
blackholes.wirehub.net : \
proxies.blackholes.wirehub.net : \
sbl-xbl.spamhaus.org
delay = 30s
# для фильтра - устанавливаем переменные (отладка)
# warn set acl_m4 = $sender_address
# set acl_m5 = $local_part@$domain
# проверяем пользователей из файла альясов (системные)
accept domains = +local_domains
message = "Unknown user for this domain"
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
# Принимаем сообщения от тех, кто аутентифицировался:
# Вообще, большинство конфигов в рунете - это один и тот же
# конфиг написанный Ginger, в котором этот пункт расположен
# внизу. Но при таком расположении рубятся клиенты с adsl,
# ppp, и прочие зарезанные на последующих проверках. Но это
# жа неправильно! Этом мои пользователи из дома! Потому
# я это правило расположил до проверок.
accept authenticated = *
# Разрешаем почту от доменов в списке relay_from_hosts
accept hosts = +relay_from_hosts
# приниаем почту для эксчейнджевых доменов
accept domains = +relay_to_domains
# Если неподошло ни одно правило - чувак явно ищет
# открытый релей. Пшёл прочь. :)
deny message = "Access deny - this not open relay!"
delay = 30s
Для проверки существоваания пользователей в домене (цель - генерить отлупы до приёма сообщения, на уровне "rcpt to:" - иначе, они могут пойти по фальшивому обратному адресу), exim будет лазить напрямую в виндовый ldap - для этого заведён специальный пользователь в домене, без права логинитсья.
Для удобства, конфиг почтаря был разбит на несколько кусков. Была реализована схема с т.н. "белыми списками". Также, с сайта http://www.iana.org/ был утянут список всех доменов верхнего уровня - он используется в проверках HELO - существует ли такой домен (проверка регулярным выражением на синтаксис - всё же не так эффективна).
Итак. Обновляем порты, ставим exim. Для выставления всех опций один раз, и чтоб в дальнейшем не мучаться - как же я его собирал, выставляем нужные опции в /etc/make.conf, у меня он получился такой:
# added by use.perl 2008-02-13 21:02:25
PERL_VER=5.8.8
PERL_VERSION=5.8.8
# default MySQL version
DEFAULT_MYSQL_VER=51
PORTSDIR?= /usr/ports
.if ${.CURDIR} == ${PORTSDIR}/mail/exim
# Поддержка MySQL
WITH_MYSQL= yes
LOG_FILE_PATH?= syslog
# Контентное сканирование
WITH_CONTENT_SCAN= yes
# Дефолтовая кодировка заголовков
WITH_DEFAULT_CHARSET?= koi8-r
# Отключаем IPv6
WITHOUT_IPV6= yes
# Подрубаем LDAP
WITH_OPENLDAP= yes
WITH_OPENLDAP_VER = 23
# Версия BDB - в чём ведёт свои БД подсказок
WITH_BDB_VER= 4
# Проверки SPF
WITH_SPF= yes
# Подержка перекодировки (для заголовков)
WITH_ICONV= yes
.endif
# SQL server
.if ${.CURDIR} == ${PORTSDIR}/databases/mysql51-server
BUILD_OPTIMIZED=yes
BUILD_STATIC=yes
WITH_CHARSET=cp1251
WITH_COLLATION=cp1251_bin
#WITH_LINUXTHREADS=yes
WITH_PROC_SCOPE_PTH=yes
.endif
# MySQL client
.if ${.CURDIR} == ${PORTSDIR}/databases/mysql51-client
BUILD_OPTIMIZED=yes
BUILD_STATIC=yes
WITH_CHARSET=cp1251
WITH_COLLATION=cp1251_bin
.endif
Соответственно - вовсе не обязательно вносить все пункты, но я решил быть дословным :). После чего, можно ставить exim:
gw$ cd /usr/ports/mail/exim
gw$ make install clean
Если у Вас не стоит MySQL, то он его потащщит и поставит (не MySQL-сервер! А клиента.) Сам сервер, если он нужен на этой машине, надо ставить самому.
gw$ cd /usr/ports/databases/mysql51-server/
gw$ make install clean
После установки, такие движения:
gw$ echo 'mysql_enable="YES"' >> /etc/rc.conf
gw$ /usr/local/etc/rc.d/mysql-server.sh start
Starting mysql.
gw# ps -axj | grep mysql
mysql 811 1 809 48 0 I con- 0:00.01 /bin/sh /usr/local/bi
mysql 1013 811 809 48 0 S con- 53:02.03 /usr/local/libexec/my
root 19681 19366 19680 19337 2 RL+ p0 0:00.00 grep mysql
Далее - рисуем конфиги. По ним комментариев немного, они и сами неплохо прокоментированы (заодно оставил все старые ошмётки, от процесса их создания - может кому-то пригодятся). Прошу тока обратить внимание на имена файлов, был уже прикол, когда боролись с глюками у одного товарисча, а оказалось что не инклюдятся файлы - и, фактически, он работал на пустом конфиге :).
/usr/local/etc/exim/configure:
# Инклюдим главные настройки
.include_if_exists /usr/local/etc/exim/includes/100.main.conf
### конфигурация ACL для входящей почты
begin acl
# Эти правила срабатывают для каждого получателя
acl_check_rcpt:
# Начало файла ACL - те, кого срубаем сразу же
.include_if_exists /usr/local/etc/exim/includes/200.acl_check_rcpt.conf
# Проверки на спам - по хостам и прочему.
.include_if_exists /usr/local/etc/exim/includes/300.acl_check_rcpt_spam_rule.conf
# Конец конфигурации - блэк-листы, задержки и прочее
.include_if_exists /usr/local/etc/exim/includes/400.acl_check_rcpt_end.conf
# Тут идут ACL проверяющие содержимое (тело) письма.
# Без них будут пропускаться все сообщения.
acl_check_data:
# Инклюдим конфигурацию проверки тела письма
.include_if_exists /usr/local/etc/exim/includes/500.acl_check_data.conf
# чё делаем с почтой
begin routers
# Инклюдим конфигурацию роутреров
.include_if_exists /usr/local/etc/exim/includes/600.routers.conf
# начинаются транспорты - как доставляем почту
begin transports
# Инклюдим транспорты
.include_if_exists /usr/local/etc/exim/includes/700.transports.conf
# Конфигурация повторов и перезаписи
.include_if_exists /usr/local/etc/exim/includes/800.retry_and_rewrite.conf
# Секция авторизации при отправке писем. Ввиду того,
# что почтовых клиентов много, и все всё делают
# по-своему, то и механизмов авторизации три...
begin authenticators
# Аутентификация юзеров.
.include_if_exists /usr/local/etc/exim/includes/900.authenticators.conf
/usr/local/etc/exim/includes/100.main.conf:
# Имя хоста. Используется в EHLO.
# Фигурирует в других пунктах, если они не заданы -
# типа qualify_domain и прочих..
# Если тут ничё не установлено (строка закомметрована)
# то используется то, что вернёт функция uname()
primary_hostname = gw.komarovka.org.ua
# Вводим данные для подключения к MySQL серверу.
# словечко `hide`, вначале, означает, что при
# вызове проверки конфига командой
# exim -bV config_file эти данные не будут отображаться.
# Если без него - то будут показаны... Формат записи:
# хост/имя_бд/пользователь/пароль
hide mysql_servers = localhost/exim/exim/exim
# Эксчейнджевый домен
MS_EXCHANGE_DOMAIN = komarovka.org.ua
# внутренний IP машины
INTERNAL_IP = 192.168.2.20
# внимание - изменённ дефолтовый разделитель списка!
ldap_default_servers = <; 192.168.2.4:3268
#; dc2.domain.org:3268
# макросы для работы с LDAP
LDAP_AD_BINDDN = exim
LDAP_AD_PASS = eximpass
LDAP_AD_BASE_DN = DC=komarovka,DC=org,DC=ua
# макрос для проверки пользователей в домене
LDAP_AD_MAIL_RCPT = \
user=LDAP_AD_BINDDN \
pass=LDAP_AD_PASS \
ldap:///LDAP_AD_BASE_DN?mail?sub?\
(&(|(objectClass=user)(objectClass=publicFolder)(objectClass=group))\
(|(proxyAddresses=${quote_ldap:${local_part}@MS_EXCHANGE_DOMAIN})\
(proxyAddresses=smtp:${quote_ldap:${local_part}@MS_EXCHANGE_DOMAIN}))\
(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
# Делаем список локальных доменов. Далее этот
# список будет фигурировать в виде +local_domains
# В данном случае домены выбираются из БД MySQL. Также
# можно их просто перечислить через двоеточие. Есть интересная
# возможность, можно указать юзер@[хост] - az@[222.222.4.5]
domainlist local_domains = ${lookup mysql{SELECT `domain` \
FROM `domain` WHERE \
`domain`='${domain}' AND \
`active`='1'}}
#domainlist local_domains = mail.short-dom.ru : relay.long-domain.ru
#domainlist local_domains = komarovka.org.ua : mail1.komarovka.org.ua
# делаем список доменов с которых разрешены релеи.
# Далее этот список будет в виде +relay_to_domains
# Можно использовать символы подстановки, типа:
# .... = *.my.domen.su : !spam.my.domen.su : first.su
# тогда пропускается всё, что похоже на *.my.domen.su, но
# от spam.my.domen.su релеится почта не будет.
#domainlist relay_to_domains = ${lookup mysql{SELECT `domain` \
# FROM `domain` WHERE \
# `domain`='${domain}' AND \
# `active`='1'}}
#domainlist relay_to_domains = long-domain.ru : short-dom.ru
#domainlist relay_to_domains = mail1.komarovka.org.ua : komarovka.org.ua
domainlist relay_to_domains = komarovka.org.ua
# Составляем список хостов с которых разрешён неавторизованый
# релей. Обычно в нём находятся локальные сети, и локалхост...
# ЛокалХост в двух видах был внесён сознательно - пару раз
# сталкивался с кривым файлом /etc/hosts - результатом было
# непонимание `localhost` но пониманием 127.0.0.1/8
hostlist relay_from_hosts = localhost : 127.0.0.0/8 : 192.168.2.2
# Вводим названия acl`ов для проверки почты. (В общем-то, это
# необязательно, если вы делаете открытый релей, или хотите
# принимать вообще всю почту с любого хоста для любых
# получателей... Тока потом не жалуйтесь что у Вас спам
# и провайдер выкатывает немеряный счёт :))
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
# Прикручиваем антивирус - при условии, что exim собран
# с его поддержкой. В качестве антивиря юзаем ClamAV,
# ибо - ПО должно быть свободным! :)
# Итак, указываем местоположение сокета clamd.
av_scanner = clamd:/var/run/clamav/clamd
# Адрес куда слать на проверку спама (SpamAssasin), но я
# это не юзаю. Не так много у меня спама...
# spamd_address = 127.0.0.1 783
# Имя домена добавляемое для локальных отправителей (реальных
# юзеров системы) т.е. почта отправляемая от root, будет от
# root@домен_указанный_здесь. Если пункт незадан, то используется
# имя хоста из `primary_hostname`. Логичней было бы написать здесь
# az.su, но мне удобней иначе:
qualify_domain = komarovka.org.ua
# Имя хоста для ситуации, обратной предыдущей, - это имя домена
# добавляемое к почте для системных юзеров, ну и вообще для почты
# пришедшей на адрес типа `root`, `az`, & etc... Если этот
# пункт незадан то используется значение полученное из
# предыдущего пункта - `qualify_domain`
qualify_recipient = komarovka.org.ua
# A это как раз кусок вышеописанного анахронизма - про почту в
# виде user@[222.222.222.222] - принимать её или нет. По дефолту
# (когда строка закомментирована) значение - false. Если захотите
# поставить true то надо будет добавить в список доменов
# комбинацию @[] - она означает `все локальные адреса`
allow_domain_literals = false
# Пользователь от которого работает exim
exim_user = mailnull
# группа в кторой работает exim
exim_group = mail
# запрещаем работу доставки под юзером root - в целях безопасности
never_users = root
# Проверяем соответствие прямой и обратной зон для всех хостов.
# Тока зачем это нужно - даже и незнаю... Спам на этом не режется...
# Зато возможны проблемы - если сервер зоны скажет `сервер файлед`
# то почту от этого хоста Вы не получите :)
#host_lookup = *
# Тоже анахронизм (на самом деле, не такой уж анахронизм, но все давно
# забили на ident и закрыли файрволлом tcp:113...) Это проверка - Ваш
# хост спрашивает у удалённого, с которого было подключение, а кто
# собстно ко мне подключился на такой-то порт? Если на удалённом хосте
# работает identd - он может ответить (а может и не ответить - как
# настроить), скажет UID пользователя от которого установлено
# соединение, тип ОС, и имя пользователя. Теперь, понимаете, почему
# у всех оно зарублено и файрволлами позакрыто? :) Это же палево :)
# Тока на мой взгляд, если на сервере всё настроено правильно -
# то вовсе это и не страшно.
# Короче - если хостс поставить * то будет проверять все. Таймаут -
# если поставить 0 то не будет ждать ответа ни от кого. По
# вышеописанным причинам - отключаем
#rfc1413_hosts = *
rfc1413_query_timeout = 0s
# По дефолту, экзим отфутболивает все `неквалифицированные` адреса,
# состоящие тока из локальной части. Для того чтобы разрешить такие письма
# определённых хостов используются эти директивы:
# для `неквалифицированных` отправителей
sender_unqualified_hosts = +relay_from_hosts
# для `неквалифицированных` получателей
recipient_unqualified_hosts = +relay_from_hosts
# Если сообщение было недоставлено, то генерится соощение
# об ошибке. Если сообщение об ошибке не удалось доставить
# то оно замораживается на указанный в этом пункте срок,
# после чего снова попытка доставить его. При очередной
# неудаче - сообщение удаляется.
ignore_bounce_errors_after = 45m
# Замороженные сообщения, находящиеся в очереди, дольше
# указанного времени удаляются и генерится сообщение
# об ошибке (при условии, что это не было недоставленное
# сообщение об ошибке :))
timeout_frozen_after = 2d
# собсно на этом штатный конфиг кончился, но
# меня-то это не устраивает... Поэтому пошли пункты,
# почёрпнутые из других источников.
# список адресов, через запятую, на которые засылаются
# сообщения о замороженных сообщениях (о замороженых
# уведомлениях о заморозке, сообщения не генерятся. - я
# надеюсь эта строка понятна :))
#freeze_tell = az@komarovka.org.ua
# Список хостов, почта от которых принимается, несмотря
# на ошибки в HELO/EHLO (тут указана моя подсеть)
helo_accept_junk_hosts = 192.168.2.0/24
# Через какое время повторять попытку доставки
# замороженного сообщения
auto_thaw = 2h
# Приветствие сервера
smtp_banner = "$primary_hostname, ESMTP EXIM $version_number"
# Максимальное число одновременных подключений по
# SMTP. Рассчитывать надо исходя из нагрузки на сервер
smtp_accept_max = 50
# максимальное число сообщений принимаемое за одно соединение
# от удалённого сервера (или пользователя). C числом 25
# я имел проблемы тока один раз - когда у меня три дня лежал
# инет и после его подъёма попёрли мессаги. Но у меня не так
# много почты - всего 30 пользователей.
smtp_accept_max_per_connection = 50
# чё-то про логи и борьбу с флудом - я так понимаю -
# максимальное число сообщений записываемых в логи
smtp_connect_backlog = 30
# максимальное число коннектов с одного хоста
smtp_accept_max_per_host = 20
# Ход ладьёй - для увеличения производительности,
# директория `spool` внутри, разбивается на
# директории - это ускоряет обработку
split_spool_directory = true
# Если у сообщения много адресатов на удалённых хостах,
# то запускатеся до указанного числа максимально число
# параллельных процессов доставки
remote_max_parallel = 15
# при генерации сообщения об ошибке прикладывать
# не всё сообщение, а кусок (от начала) указанного
# размера (иногда полезно и целиком - в таком случае
# просто закомментируйте эту строку)
return_size_limit = 50k
# размер сообщения. У меня стоит относительно большой
# размер (`относительно` - потому, что на большинстве
# хостов оно ограничено 2-5-10мб, либо стоит анлим.)
message_size_limit = 15M
# разрешаем неположенные символы в HELO (столкнулся
# с этим случайно - имя фирмы состояло из двух слов
# и какой-то раздолбай домен обозвал my_firme_name
# прям с подчёркиваниями... Виндовые клиенты при
# соединении радостно рапортовали о себе
# `vasya.my_firme_name` ну а экзим их футболил :))
helo_allow_chars = _
# Принудительная синхронизация. Если отправитель
# торопится подавать команды, не дождавшись ответа,
# то он посылается далеко и надолго :) Немного,
# спам режется.
smtp_enforce_sync = true
# Выбираем, что мы будем логировать
# + - писать в логи,
# - - Не писать в логи.
# +all_parents - все входящие?
# +connection_reject - разорваные соединения
# +incoming_interface - интерфейс (реально - IP)
# +lost_incoming_connections - потеряные входящие
# соединения
# +received_sender - отправитель
# +received_recipients - получатель
# +smtp_confirmation - подтверждения SMTP?
# +smtp_syntax_error - ошибки синтаксиса SMTP
# +smtp_protocol_error - ошибки протокола SMTP
# -queue_run - работа очереди (замороженные мессаги)
#log_selector = \
# +all \
# -incoming_port \
# -incoming_interface \
# -arguments \
# -smtp_connection \
# -lost_incoming_connection \
# -queue_run \
# -smtp_confirmation \
# -smtp_syntax_error \
# -smtp_protocol_error
log_selector = \
+all_parents \
+connection_reject \
+incoming_interface \
+lost_incoming_connection \
+received_sender \
+received_recipients \
+smtp_confirmation \
+smtp_syntax_error \
+smtp_protocol_error \
-queue_run
# +connection_reject \
# +incoming_interface \
# +lost_incoming_connection \
# +received_sender \
# +received_recipients
# Системный фильтр, копирование проходящей почты
system_filter = /usr/local/etc/exim/filters/system-filter
system_filter_pipe_transport = address_pipe
system_filter_user = mailnull
system_filter_group = mail
# Убираем собственную временную метку exim`a из логов, её ставит
# сам syslogd - нефига дублировать
syslog_timestamp = no
log_file_path = syslog : /usr/local/var/log/exim/%s-%D.log
/usr/local/etc/exim/includes/200.acl_check_rcpt.conf:
# Проверка получателей
# принимать сообщения которые пришли с локалхоста,
# не по TCP/IP
accept hosts = :
# Запрещаем письма содержащие в локальной части
# символы @; %; !; /; |. Учтите, если у вас было
# `percent_hack_domains` то % надо убрать.
# Проверяются локальные домены
deny domains = +local_domains
message = "incorrect symbol in address"
local_parts = ^[.] : ^.*[@%!/|]
delay = 30s
# Проверяем недопустимые символы для
# нелокальных получателей:
deny domains = !+local_domains
message = "incorrect symbol in address"
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
delay = 30s
# Принимаем почту для постмастеров локальных доменов без
# проверки отправителя (я закомментировал, т.к. это -
# основной источник спама на мой ящик).
# accept local_parts = postmaster
# domains = +local_domains
# Запрещщаем, если невозможно проверить отправителя
# (отсутствует в списке локальных пользователей)
# У себя я это закоментил, по причине, что некоторые
# железяки (принтеры, & etc) и программы (Касперский, DrWEB)
# умеют слать почту, в случае проблем но не умеют ставить
# нужного отправителя. Такие письма эта проверка не пускает.
require verify = sender
# Запрещщаем тех, кто не обменивается приветственными
# сообщениями (HELO/EHLO)
deny condition = ${if eq{$sender_helo_name}{}{yes}{no}}
message = "HELO/EHLO require by SMTP RFC"
delay = 30s
# Рубаем нах, тех, кто подставляет свой IP в HELO
deny hosts = !+relay_from_hosts:*
condition = ${if isip{$sender_helo_name}{yes}{no}}
message = We don't allow domain literals, many spam...
delay = 30s
# Рубаем хело с нашим именем
deny condition = ${if match_domain{$sender_helo_name} \
{$primary_hostname:+local_domains:+relay_to_domains} \
{true}{false}}
hosts = !+relay_from_hosts : *
message = Message was delivered by ratware - own
log_message = remote host used our name in HELO/EHLO.
delay = 30s
# Рубаем тех, кто в HELO пихает мой IP (2500 за месяц!)
deny condition = ${if eq{$sender_helo_name}\
{$interface_address}{yes}{no}}
hosts = !127.0.0.1 : !localhost : *
message = It is my IP!
# Рубаем мудаков с недопустимыми символами в helo (пока видел тока такие)
deny condition = ${if match{$sender_helo_name}{\N_\N}{yes}{no}}
hosts = !127.0.0.1 : !localhost : !+relay_from_hosts : *
set acl_m0 = ${eval:$acl_m0+20}
message = Invalid HELO
# Ограничения эксчейнджа - юзер не может начинаться/заканчиваться точкой.
deny senders = \N^\.|\.@\N
message = Invalid address
# Рубаем хосты типа *adsl*; *dialup*; *pool*;....
# Нормальные люди с таких не пишут. Если будут
# проблемы - уберёте проблемный пункт (у меня клиенты
# имеют запись типа asdl-1233.zone.su - я ADSL убрал...)
deny condition = ${if match{$sender_host_name} \
{adsl|dialup|pool|peer|dhcp} \
{yes}{no}}
message = dialup host
/usr/local/etc/exim/includes/300.acl_check_rcpt_spam_rule.conf:
# Проверка на спам - по хосту
# тут оставлено много отладки - раскомментите что надо...
# Задержка. (это такой метод борьбы со спамом,
# основанный на принципе его рассылки) На этом рубается
# почти весь спам. Единственно - метод неприменим на
# реально загруженных MTA - т.к. в результате ему
# приходится держать много открытых соединений.
# но на офисе в сотню-две человек - шикарный метод.
#
# более сложный вариант, смотрите в статье по exim и
# курьер имап. Т.к. там метод боле умный (просто правил
# больше :), то можно и на более загруженные сервера ставить)
# # Вводим переменную 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
/usr/local/etc/exim/includes/400.acl_check_rcpt_end.conf:
# Конец конфигурации проверки отправителя
# Сбрасываем спамерскую переменную, если домен в белом списке
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
# ставим задержку в 0 секунд своим хостам
warn hosts = +relay_from_hosts
set acl_c0 = 0s
# Ставим нулевую задержку хостам из белого листа
warn condition = ${if eq{$acl_m2}{1}{yes}{no}}
set acl_c0 = 0s
# пишем в логи задержку (если оно вам надо)
# 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.
warn
delay = $acl_c0
# Рубаем тех, кто в блэк-листах. Серваки перебираются
# сверху вниз, если не хост не найден на первом, то
# запрашивается второй, и т.д. Если не найден ни в одном
# из списка - то почта пропускается.
deny message = "you in blacklist - $dnslist_domain --> \
$dnslist_text; $dnslist_value"
hosts = !+relay_from_hosts
domains = !+relay_to_domains
dnslists = cbl.abuseat.org : \
dynablock.njabl.org : \
dul.dnsbl.sorbs.net : \
list.dsbl.org : \
bl.spamcorp.net : \
opm.blitzed.org : \
blackholes.wirehub.net : \
proxies.blackholes.wirehub.net : \
sbl-xbl.spamhaus.org
delay = 30s
# для фильтра - устанавливаем переменные (отладка)
# warn set acl_m4 = $sender_address
# set acl_m5 = $local_part@$domain
# проверяем пользователей из файла альясов (системные)
accept domains = +local_domains
message = "Unknown user for this domain"
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
# Принимаем сообщения от тех, кто аутентифицировался:
# Вообще, большинство конфигов в рунете - это один и тот же
# конфиг написанный Ginger, в котором этот пункт расположен
# внизу. Но при таком расположении рубятся клиенты с adsl,
# ppp, и прочие зарезанные на последующих проверках. Но это
# жа неправильно! Этом мои пользователи из дома! Потому
# я это правило расположил до проверок.
accept authenticated = *
# Разрешаем почту от доменов в списке relay_from_hosts
accept hosts = +relay_from_hosts
# приниаем почту для эксчейнджевых доменов
accept domains = +relay_to_domains
# Если неподошло ни одно правило - чувак явно ищет
# открытый релей. Пшёл прочь. :)
deny message = "Access deny - this not open relay!"
delay = 30s



Информация
Теги
Прямой эфир
Лучшие новости
Кто на сайте
Заголовок