Есть категория программ, которые кажутся простыми снаружи и бесконечными внутри. Exim из этой категории. Отправить письмо с его помощью можно за десять минут. Настроить его так, чтобы письма доходили до адресатов, не попадали в спам, проверялись на входе и подписывались на выходе, это уже другая история. История, в которой важна каждая деталь: порядок роутеров, условия в ACL, путь к приватному ключу и то, как именно SpamAssassin получает сообщение на анализ.
Exim обрабатывает каждое сообщение последовательно через три стадии: роутеры определяют, куда доставить письмо, транспорты определяют, как именно его доставить, и ACL определяют, принимать ли его вообще. Эти три слоя работают вместе, и понимание их взаимодействия отделяет работающую конфигурацию от той, которая работает до первого нестандартного случая.
Установка и выбор между light и heavy
На Debian и Ubuntu Exim поставляется в двух вариантах. Лёгкий вариант exim4-daemon-light подходит для простой доставки без контентной фильтрации. Тяжёлый вариант exim4-daemon-heavy включает поддержку SpamAssassin через content scanning extension, LDAP-поиски, встроенный Perl и SASL-аутентификацию. Для полноценного почтового сервера с проверкой спама нужен именно тяжёлый вариант.
apt-get install exim4-daemon-heavy spamassassin \
spf-tools-perl sasl2-bin
# Начальная конфигурация через debconf
dpkg-reconfigure exim4-config
# Проверить текущую конфигурацию
cat /etc/exim4/update-exim4.conf.conf
На Debian конфигурация хранится либо в одном файле /etc/exim4/exim4.conf.template, либо разделена по директориям в /etc/exim4/conf.d/. Разделённый вариант удобнее для сложных конфигураций: файлы из каждой поддиректории объединяются утилитой update-exim4.conf в алфавитном порядке. Собственные файлы принято именовать с префиксом 00_, чтобы они включались первыми.
# Пересобрать конфигурацию из фрагментов и перезапустить
update-exim4.conf
systemctl restart exim4
# Проверить синтаксис конфигурации без перезапуска
exim4 -bV
exim4 -C /etc/exim4/exim4.conf.template -bV
Роутеры и логика принятия решений о доставке
Роутеры в Exim обрабатываются строго по порядку. Каждый роутер проверяет условие domains, senders, condition и другие предикаты. Если условие выполнено и роутер принял адрес, обработка останавливается. Если нет, Exim переходит к следующему роутеру. Именно поэтому порядок определения роутеров критичен: более специфичные условия должны идти раньше общих.
Стандартный роутер для DNS-доставки внешней почты:
dnslookup:
debug_print = "R: dnslookup for $local_part@$domain"
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
same_domain_copy_routing = yes
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 : \
192.168.0.0/16 : 172.16.0.0/12 : 10.0.0.0/8
no_more
Директива no_more означает, что если роутер принял адрес, но доставка не удалась, следующие роутеры не проверяются. Для релеинга через smart host вместо прямого DNS-разрешения используется manualroute:
smarthost_relay:
debug_print = "R: smarthost for $local_part@$domain"
driver = manualroute
domains = ! +local_domains
transport = remote_smtp_smarthost
route_list = * smtp.relay.example.com::587
host_find_failed = defer
same_domain_copy_routing = yes
no_more
Роутер для условного релеинга конкретных доменов через выделенный хост:
domain_relay:
driver = manualroute
domains = example.org : example.net
transport = remote_smtp
route_list = example.org mx1.example.org byname \
: example.net mx1.example.net byname
no_more
Разрешение релеинга для аутентифицированных клиентов и доверенных хостов задаётся в ACL-блоке acl_check_rcpt. Список доверенных хостов, которым разрешён релеинг:
# В секции main
hostlist relay_from_hosts = 127.0.0.1 : ::1 : 10.0.0.0/8
# В acl_check_rcpt
accept hosts = +relay_from_hosts
control = submission
control = dkim_disable_verify
accept authenticated = *
control = submission
control = dkim_disable_verify
Директива dkim_disable_verify для релейных источников критически важна: без неё Exim будет проверять DKIM-подписи в письмах, пришедших от внутренних серверов, что не имеет смысла и может блокировать легитимную почту.
Генерация ключей и настройка DKIM-подписи
DKIM-подпись в Exim настраивается на уровне транспорта, а не роутера. Это означает, что одна конфигурация подписи применяется ко всем письмам, проходящим через данный транспорт. Exim проверяет наличие приватного ключа динамически через переменные, что позволяет хранить ключи для разных доменов в одной директории и подписывать письма автоматически нужным ключом.
Сгенерировать ключевую пару для домена:
mkdir -p /etc/exim4/dkim
cd /etc/exim4/dkim
# Сгенерировать приватный ключ RSA 2048 бит
openssl genpkey -algorithm RSA \
-out example.com.pem \
-pkeyopt rsa_keygen_bits:2048
# Извлечь публичный ключ
openssl rsa -in example.com.pem \
-pubout -out example.com.pub
# Установить права: читать может только exim
chown root:Debian-exim /etc/exim4/dkim/example.com.pem
chmod 640 /etc/exim4/dkim/example.com.pem
# Получить публичный ключ в формате для DNS
openssl rsa -in example.com.pem -pubout -outform DER 2>/dev/null \
| openssl base64 -A
Полученный base64-ключ добавляется в DNS-запись TXT. Селектор mail это произвольная строка, которая должна совпадать с параметром dkim_selector в конфигурации Exim:
mail._domainkey.example.com. IN TXT \
"v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
Конфигурация транспорта с динамическим выбором ключа по домену отправителя:
remote_smtp:
driver = smtp
message_size_limit = ${if > {$max_received_linelength}{998} \
{1}{0}}
dkim_domain = ${lc:$sender_address_domain}
dkim_selector = mail
dkim_private_key = ${if exists \
{/etc/exim4/dkim/${lc:$sender_address_domain}.pem} \
{/etc/exim4/dkim/${lc:$sender_address_domain}.pem} \
{0}}
dkim_canon = relaxed
dkim_sign_headers = Date:From:To:Subject:Message-ID:\
Content-Type:MIME-Version
Когда dkim_private_key возвращает 0, Exim отправит письмо без подписи, что лучше, чем отказ доставки при отсутствии ключа. Алгоритм relaxed канонизации рекомендован для большинства случаев: он устойчив к незначительным изменениям пробелов и переносов строк, которые иногда вносят промежуточные серверы.
Проверить что подпись корректна после настройки:
# Отправить тестовое письмо и проверить заголовки
echo "DKIM test" | mail -s "Test DKIM" Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
# Или через exim напрямую с verbose-выводом
exim4 -v Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. <<< "Subject: DKIM test
Test body"
Установка SpamAssassin и подключение к Exim
SpamAssassin в Exim работает через content scanning extension, доступное только в exim4-daemon-heavy. Демон spamd принимает сообщения через сокет или TCP-порт, анализирует их и возвращает оценку вместе с добавленными заголовками. Exim получает результат и принимает решение на основе настроенных порогов.
# Включить spamd в автозапуск
systemctl enable spamassassin
systemctl start spamassassin
# Проверить что spamd слушает
ss -tlnp | grep 783
Настройка SpamAssassin в /etc/spamassassin/local.cf:
# Путь к базе данных Bayes
bayes_path /var/spamassassin/bayes
# Не изменять тему письма
rewrite_subject 0
# Добавлять заголовки, но не оборачивать письмо
report_safe 0
# Минимальный балл для пометки как спам
required_score 5.0
# Использовать сетевые проверки
use_razor2 1
use_pyzor 1
mkdir -p /var/spamassassin
chown Debian-exim:Debian-exim /var/spamassassin
В конфигурации Exim подключение к SpamAssassin задаётся в секции main:
# Адрес демона spamd
spamd_address = 127.0.0.1 783
В ACL-блоке acl_check_data добавляется проверка содержимого:
# В /etc/exim4/conf.d/acl/40_exim4-config_check_data
# Не проверять исходящую почту от аутентифицированных клиентов
warn condition = ${if def:authenticated_id}
set acl_m_skipspam = yes
# Запустить SpamAssassin для входящей почты
warn condition = ${if !def:acl_m_skipspam}
spam = nobody:true
add_header = X-Spam-Score: $spam_score
add_header = X-Spam-Bar: $spam_bar
add_header = X-Spam-Report: $spam_report
# Пометить как спам при оценке выше 5
warn condition = ${if !def:acl_m_skipspam}
condition = ${if >{$spam_score_int}{50}}
add_header = X-Spam-Flag: YES
# Отклонить при очень высокой оценке (явный спам)
deny condition = ${if !def:acl_m_skipspam}
condition = ${if >{$spam_score_int}{140}}
message = Message rejected as spam (score $spam_score)
Переменная $spam_score_int содержит оценку, умноженную на 10. Порог 50 соответствует оценке 5.0, порог 140 соответствует 14.0. Это разграничение важно: умеренный спам помечается заголовком для клиентской фильтрации, откровенный спам отклоняется на уровне SMTP с кодом ошибки.
Обучение Bayes и мониторинг очереди
SpamAssassin становится значительно точнее после обучения на реальной почте конкретного сервера. Байесовский фильтр требует нескольких сотен примеров и спама, и чистых писем, прежде чем начнёт давать надёжные результаты. Обучение запускается вручную или по расписанию через cron.
# Обучить на папке со спамом
sa-learn --spam /home/user/Maildir/.Spam/cur/
# Обучить на чистых письмах
sa-learn --ham /home/user/Maildir/cur/
# Посмотреть статистику базы Bayes
sa-learn --dump magic
# Синхронизировать базу вручную
sa-learn --sync
Мониторинг состояния очереди и анализ проблемных доставок:
# Посмотреть очередь
exim4 -bp
# Количество писем в очереди
exim4 -bpc
# Запустить немедленную обработку очереди
exim4 -qf
# Посмотреть лог доставки конкретного письма
exim4 -Mvl <message_id>
# Принудительно отправить конкретное письмо
exim4 -M <message_id>
# Очистить retry-кэш для хоста с которым были проблемы
exim_tidydb -t 0d /var/spool/exim retry
exim_tidydb -t 0d /var/spool/exim wait-remote_smtp
Последняя пара команд незаменима при ситуации, когда удалённый сервер был недоступен, Exim закэшировал провальное состояние, а сервер уже восстановлен, но Exim продолжает ждать истечения таймера перед следующей попыткой. Это распространённая причина задержек доставки, которая не имеет ничего общего с реальными проблемами соединения.
Проверка SPF, DMARC и итоговая диагностика конфигурации
Полноценный почтовый сервер без SPF-проверки входящей почты и правильно опубликованной SPF-записи для исходящей работает лишь вполовину своих возможностей. SPF-проверка в Exim включается через пакет spf-tools-perl и флаг CHECK_RCPT_SPF в макросах конфигурации.
# Включить SPF-проверку через локальные макросы
cat >> /etc/exim4/exim4.conf.localmacros << 'EOF'
CHECK_RCPT_SPF = yes
DKIM_CANON = relaxed
EOF
update-exim4.conf
systemctl reload exim4
Тестирование конфигурации DKIM через внешние инструменты после запуска:
# Отправить тест на специальный адрес проверки
echo "DKIM check" | mail -s "DKIM Test" \
Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
# Проверить DKIM-запись в DNS
dig TXT mail._domainkey.example.com +short
# Проверить SPF-запись
dig TXT example.com +short | grep spf
# Полная диагностика Exim с трассировкой конкретного адреса
exim4 -bt Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
exim4 -bh 1.2.3.4 <<< "EHLO test.com"
Команда exim4 -bt показывает, через какой роутер и транспорт пройдёт письмо для данного адреса, без реальной отправки. Это самый быстрый способ убедиться, что роутеры настроены правильно и письмо не уйдёт туда, куда не планировалось. На почтовом сервере хорошая диагностика важнее хорошего дизайна конфигурации: ошибки в SMTP-инфраструктуре часто невидимы до момента, когда важное письмо тихо исчезает в очереди или возвращается с непонятной ошибкой.
Exim честен со своими пользователями: он делает ровно то, что написано в конфигурации. Это и его сила, и его ловушка. Написать конфигурацию, которая делает правильные вещи в правильном порядке, и при этом подписывает исходящие письма и фильтрует входящие без ложных срабатываний, значит понять, как думает этот MTA. После этого понимания он становится одним из самых управляемых почтовых серверов в мире.