Exim и Dovecot без SQL в Debian 12 Bookworm
Содержание
Введение
Эта статья является обновлённой версией статьи Exim и Dovecot без SQL, написанной ранее для Debian 7 Wheezy. В эту статью также были добавлены наработки из статьи Exim и Dovecot quota-status.
Среди системных администраторов и инженеров распространено мнение, что чем система проще, тем она надёжнее. Конечно, не стоит воспринимать это утверждение буквально. Но, как говорится, умному - достаточно. Чем меньше компонентов в системе, чем проще взаимосвязи между компонентами, тем меньше вероятность того, что она сломается и тем выше вероятность того, что её будет просто починить.
Именно поэтому большинство системных администраторов Unix любят использовать текстовые файлы, которые легко редактируются при помощи любого текстового редактора (файлы XML к таковым обычно не относятся). Здесь я опишу настройку почтовой системы на основе Dovecot и Exim без использования базы данных. Вместо этого все данные будут помещаться в текстовых файлах.
Описание настройки основано на моих предыдущих заметках Установка и настройка Dovecot и Настройка Exim, практически повторяя их. Прошу прощения за самоплагиат - я считаю, что так будет удобнее.
Настройка Dovecot
Начнём с настройки Dovecot, поскольку для настройки Exim необходимо иметь уже настроенный Dovecot.
Установка Dovecot
Установим пакеты Dovecot, содержащие поддержку серверов POP3 и IMAP:
# apt-get install dovecot-core dovecot-imapd dovecot-pop3d
Подготовка системы
Создадим группу и пользователя vmail
, от имени которого будет работать Dovecot и дадим этому пользователю доступ к каталогу, в котором будет храниться почта пользователей почтовой системы. На серверах для размещения почтовых ящиков, как и другой часто меняющейся информации, обычно используется раздел /var, который заранее делается достаточно большим. Я размещу данные в каталоге /srv
:
# groupadd -g 120 -r vmail
# useradd -g 120 -r -u 120 vmail
# mkdir /srv/vmail
# chown vmail:vmail /srv/vmail
# chmod u=rwx,g=rx,o= /srv/vmail
Базовая настройка Dovecot
Я буду использовать защищённые версии протоколов IMAP и POP3, поэтому настрою в файле /etc/dovecot/conf.d/10-auth.conf
механизмы PLAIN и LOGIN, чтобы хранить пароли в базе данных в хэшированном виде:
disable_plaintext_auth = no
auth_default_realm = domain.tld
auth_mechanisms = plain login
#!include auth-system.conf.ext
!include auth-passwdfile.conf.ext
Настроим использование учётных данных из файла, подобного /etc/passwd
, прописав в файле /etc/dovecot/conf.d/auth-passwdfile.conf.ext
следующие секции:
passdb {
driver = passwd-file
args = scheme=CRYPT username_format=%u /etc/dovecot/users
}
userdb {
driver = passwd-file
args = username_format=%u /etc/dovecot/users
# Поля по умолчанию, которые могут быть заменены значениями из файла passwd
default_fields = uid=vmail gid=vmail userdb_home=/srv/vmail/%Ld/%Ln userdb_location=maildir:/srv/vmail/%Ld/%Ln userdb_quota_rule=*:storage=1G
# Поля, значения которых заменяют значения из файла passwd
#override_fields = home=/srv/vmail/%Ld/%Ln
}
Создадим в каталоге /etc/dovecot
файл users
и проставим права доступа:
# cd /etc/dovecot
# touch users
# chown root:dovecot users
# chmod u=rw,g=r,o= users
В файле /etc/dovecot/users
могут быть следующие поля:
user:{plain}password:uid:gid:gecos:home:shell:extra_fields
Назначение полей:
user
- почтовый ящик (в данном случае - вместе с доменом),password
- пароль (можно явным образом указывать алгоритм хэширования пароля),uid
- системный идентификатор владельца файлов почты,gid
- системный идентификатор группы владельца файлов почты,gecos
- справочная информация о почтовом ящике (игнорируется),home
- путь к каталогу почты,shell
- интерпретатор (игнорируется),extra_fields
- дополнительные настройки (квота, например).
Любое из полей может быть не определено в файле, если в настройках Dovecot указаны значения этих полей по умолчанию. При указании дополнительных полей, используемых в секции userdb
необходимо перед именем поля указывать префикс userdb_
, как в примере выше, в настройках default_fields
. Имеется возможность зафиксировать часть настроек почтового ящика при помощи настройки override_fields, так что значения из файла будут игнорироваться.
Подробнее о формате файла и других настройках можно прочитать на официальной wiki-странице Dovecot: Passwd-file
Изменим форматирование отметок времени, вписав в файл /etc/dovecot/conf.d/10-logging.conf
следующую настройку:
log_timestamp = "%Y-%m-%d %H:%M:%S "
На время отладки также можно включить другие опции из этого файла:
auth_verbose = yes
auth_verbose_passwords = yes
auth_debug = yes
mail_debug = yes
В файле /etc/dovecot/conf.d/10-mail.conf
настроим путь к почтовым ящикам и пользователя, от имени которого Dovecot будет работать с ящиками:
mail_home = /srv/vmail/%Ld/%Ln
mail_location = maildir:/srv/vmail/%Ld/%Ln
mail_uid = vmail
mail_gid = vmail
first_valid_uid = 120
last_valid_uid = 120
first_valid_gid = 120
last_valid_gid = 120
Сейчас настроим сервис, при помощи которого Exim будет проверять учётные данные почтовых клиентов. Для этого отредактируем файл /etc/dovecot/conf.d/10-master.conf
и впишем в него настройки сервиса:
service auth {
unix_listener auth-userdb {
mode = 0660
user = Debian-exim
#group =
}
}
Начальная настройка сервера окончена.
Настройка плагина acl
Плагин acl
позволяет пользователям предоставлять друг другу доступ к папкам в своих почтовых ящиках. Это может быть полезно для корпоративных пользователей. Например, для директора и его заместителя. Или для директора и его секретаря. Или для сотрудников из одного отдела, которые подменяют друг друга на время обеда или отпуска. Эта возможность, естественно, доступна только при использовании протокола IMAP.
В файле /etc/dovecot/conf.d/10-mail.conf
включаем использование плагина:
mail_plugins = acl
В файле /etc/dovecot/conf.d/20-imap.conf
включаем использование плагина в IMAP-сервере:
protocol imap {
mail_plugins = $mail_plugins imap_acl
}
В файле /etc/dovecot/conf.d/10-mail.conf
прописываем следующие настройки:
namespace inbox {
separator = /
}
namespace {
type = shared
separator = /
prefix = shared/%%u/
location = maildir:%%h:INDEX=%h/shared/%%u
list = children
}
Эти настройки описывают два пространства имён: в первом хранится личная почта пользователя, а во втором будут отображаться каталоги других пользователей, к которым этот пользователь имеет доступ.
Поясню смысл настроек location
для пространства имён общих каталогов:
maildir:%%h
- означает место расположения чужого почтового ящика в формате Maildir,%%h
- полный путь к Maildir-каталогу чужого ящика,INDEX=%h/shared/%%u
- задаёт каталог, в который как бы монтируются каталоги чужой почты, к которым её владелец дал нам доступ,%h
- путь к Maildir-каталогу нашего ящика,%%u
- имя другого пользователя в видеbox@domain.tld
.
В файл /etc/dovecot/conf.d/90-acl.conf
прописываем настройки плагина:
plugin {
acl = vfile
}
plugin {
acl_shared_dict = file:/srv/vmail/%Ld/shared-mailboxes.db
}
Значение vfile
предписывает создавать внутри почтового ящика файл dovecot-acl
, в котором и будут прописываться права доступа к нему со стороны других пользователей.
Значение acl_shared_dict
указывает путь к файлу словаря, который позволит пользователям узнавать, к каким каталогам в чужих почтовых ящиках у них имеется доступ. В данном случае для каждого домена будет создан отдельный файл словаря, расположенный в каталоге домена, на одном уровне с ящиками.
Заодно опишем в файле /etc/dovecot/conf.d/15-mailboxes.conf
назначение различных каталогов внутри пространства имён, в котором хранится личная почта пользователя:
namespace inbox {
mailbox Drafts {
special_use = \Drafts
}
mailbox Junk {
special_use = \Junk
}
mailbox Trash {
special_use = \Trash
}
mailbox Sent {
special_use = \Sent
}
}
Назначение каталогов:
Drafts
- каталог черновиков,Junk
- каталог для спама,Trash
- каталог для удалённых писем,Sent
- каталог для отправленных писем.
Современные почтовые программы смогут прямо по протоколу IMAP узнать назначение каждого из специальных каталогов, вне зависимости от их названия. Это бывает полезно, если каталог имеет нестандартное название или название на языке пользователя ящика, например "Входящие" или "Спам".
Настройка плагина quota
Плагин quota
позволяет назначить для почтового ящика ограничения на объём хранящихся в нём писем или даже на их общее количество. На мой взгляд, ограничение на общее количество писем может защитить от исчерпания inode'ов файловой системы и от долгой отдачи списка писем при проверке почты, а какого-то другого практического смысла не имеет.
Если нужно изменить значение квоты какого-то пользователя, то в поле extra_fields
файла /etc/dovecot/users
нужно указать квоту в следующем виде:
userdb_quota_rule=*:storage=10G
Включим использование плагина в файле /etc/dovecot/conf.d/10-mail.conf
:
mail_plugins = acl quota
Плагин acl
был включен в предыдущем разделе. Если вы его не включали, то указывать его не нужно.
В файл /etc/dovecot/conf.d/15-lda.conf
впишем, что в случае превышения квоты Dovecot должен сообщать о временной ошибке, но не отклонять письмо окончательно. Почтовый сервер отправителя (или наш MTA) будет периодически предпринимать повторные попытки в надежде на то, что адресат почистит свой ящик от ненужных писем.
quota_full_tempfail = yes
В файл /etc/dovecot/conf.d/20-imap.conf
добавим поддержку квот в IMAP-сервере:
protocol imap {
mail_plugins = $mail_plugins imap_acl imap_quota
}
Плагин imap_acl
был включен в предыдущем разделе. Если вы его не включали, то указывать его не нужно.
Плагин imap_quota
позволит почтовым клиентам, работающим по протоколу IMAP, узнавать квоту почтового ящика и её текущее использование.
Укажем в файле /etc/dovecot/conf.d/90-quota.conf
, что значения квот берутся из словаря и зададим пустое правило по умолчанию:
plugin {
quota = dict:user::file:%h/dovecot-quota
quota_rule = *:
}
В каталоге каждого почтового ящика будет создаваться файл dovecot-data, внутри которого будет вестись учёт текущего количества сообщений в ящике и их объёма. Чтобы принудительно пересчитать квоты всех почтовых ящиков, можно воспользоваться следующей командой:
# doveadm quota recalc -A
Настройка плагина quota-status
В Dovecot имеется плагин quota-status
, который реализует сервис для проверки превышения квоты получателем письма. Этот сервис можно использовать на SMTP-сервере для отклонения писем для получателей, почтовый ящик котороых превысил квоты.
В файл /etc/dovecot/conf.d/90-quota.conf
вписываем:
plugin {
quota_status_success = OK
quota_status_nouser = NOUSER
quota_status_overquota = OVER
}
service quota-status {
executable = quota-status -p postfix
unix_listener exim-quota-status {
mode = 0660
user = Debian-exim
group = Debian-exim
}
client_limit = 1
}
Настройка автоочистки
Для очистки почтовых ящиков от устаревших сообщений можно добавить в настройки ящика опцию autoexpunge
с указанием времени сохранения писем. Все письма старше указанного времени будут автоматически удаляться при отключении пользователя. Например, наcтроим в файле конфигурации /etc/dovecot/conf.d/15-mailboxes.conf
автоматическое удаление писем старше 2 недель из папок со спамом и корзины:
namespace inbox {
mailbox Junk {
special_use = \Junk
autoexpunge = 2w
autoexpunge_max_mails = 1000
}
mailbox Trash {
special_use = \Trash
autoexpunge = 2w
autoexpunge_max_mails = 1000
}
}
Кроме включения самого автоматического удаления рекомендуется включить индекс писем, включить хранение отметки времени в индексе и ограничить количество писем, удаляемых за один раз.
Для этого пропишем в файле конфигурации /etc/dovecot/conf.d/10-mail.conf
следущие опции:
mailbox_list_index = yes
mail_always_cache_fields = date.save
Настройка SSL
Процедуру получения сертификатов SSL оставим за рамками этой статьи. Предположим сейчас, что сертификаты уже были получены и тогда можно включить поддержку SSL в файле /etc/dovecot/conf.d/10-ssl.conf
и указать в нём пути к файлам сертификата:
ssl = yes
ssl_cert = </etc/ssl/mail_public.pem
ssl_key = </etc/ssl/mail_private.pem
Для настройки нестандартных параметров генерации сеансовых ключей по алгоритму Диффи-Хеллмана можно сгенерировать параметры такой командой:
# openssl dhparam -out /etc/ssl/mail_dh.pem 4096
Путь к сгенерированному файлу параметров указывается в том же файле /etc/dovecot/conf.d/10-ssl.conf
:
ssl_dh = </etc/ssl/mail_dh.pem
Настройка плагина sieve
Sieve - это скрипты фильтрации почты, которые выполняются агентом локальной доставки (LDA) в момент получения письма от почтового сервера (MTA). Скрипты позволяют раскладывать письма в разные папки, ориентируясь на их содержимое - тему письма, получателей, отправителей и т.п. Можно удалить письмо, переслать его на другой ящик или отправить уведомление отправителю, причём использовать можно любое поле заголовка или содержимое тела письма.
Главное преимущество Sieve заключается в том, что пользователю не нужно настраивать правила фильтрации в каждом из используемых им почтовых клиентов - правила едины для всех почтовых клиентов сразу. Кроме того, фильтрация происходит вообще без участия клиента. Клиент, подключившись к почтовому ящику, имеет возможность работать уже с отсортированной почтой. Кроме того, отправка уведомлений о получении или пересылка писем на другой ящик вообще может происходить без участия почтового клиента.
Конечно, в наши времена больших почтовых сервисов типа Gmail или Яндекс-почты, этим никого не удивишь. Но тут плюс заключается в том, что перед нами не стоит дилемма "удобство" - "безопасность". Мы можем хранить почту у себя, не делясь ею с посторонними компаниями, имея над ней полный контроль, и в то же время можем пользоваться удобствами, характерными для больших почтовых сервисов.
Установим пакет с плагином Sieve:
# apt-get install dovecot-sieve
Включим использование плагина в файле /etc/dovecot/conf.d/15-lda.conf
:
protocol lda {
mail_plugins = $mail_plugins sieve
}
Укажем настройки плагина в файле /etc/dovecot/conf.d/90-sieve.conf
:
plugin {
sieve = /srv/vmail/%Ld/%n/active.sieve # Расположение активного скрипта
sieve_max_script_size = 1M # Максимальный размер одного скрипта
sieve_quota_max_scripts = 50 # Максимальное количество скриптов
sieve_quota_max_storage = 1M # Максимальный общий объём скриптов
}
Подробнее о скриптах Sieve можно почитать на Википедии, в статье Sieve.
Настройка сервиса managesieve
Плагин sieve не был бы столь полезным, если бы Sieve-скриптами нельзя было бы управлять прямо из почтового клиента. Именно эту функцию и реализует сервис ManageSieve. Он ожидает подключений клиентов на отдельном TCP-порту 4190. Для управления скриптами клиент использует учётные данные своего почтового ящика.
Для включения сервиса достаточно лишь установить дополнительный пакет:
# apt-get install dovecot-managesieved
В следующих заметках фильтрация писем при помощи Sieve будет рассмотрена подробнее - я покажу, как им пользоваться в почтовых клиентах.
Результирующий файл конфигурации
Поскольку настроек очень много, проверить их можно при помощи следующей команды:
# doveconf -nP
Опция n предписывает показывать только те настройки, которые отличаются от настроек по умолчанию. У меня со всеми плагинами, настройка которых была тут описана, команда выдаёт следующий результат:
# 2.3.19.1 (9b53102964): /etc/dovecot/dovecot.conf
# Pigeonhole version 0.5.19 (4eae2f79)
# OS: Linux 6.1.0-26-amd64 x86_64 Debian 12.7 ext4
# Hostname: mail.domain.tld
auth_default_realm = domain.tld
auth_mechanisms = plain login
disable_plaintext_auth = no
first_valid_gid = 120
first_valid_uid = 120
last_valid_gid = 120
last_valid_uid = 120
log_timestamp = "%Y-%m-%d %H:%M:%S "
mail_always_cache_fields = date.save
mail_gid = vmail
mail_home = /srv/vmail/%Ld/%Ln
mail_location = maildir:/srv/vmail/%Ld/%Ln
mail_plugins = acl quota
mail_privileged_group = mail
mail_uid = vmail
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext
namespace {
list = children
location = maildir:%%h/Maildir:INDEX=%h/shared/%%u
prefix = shared/%%u/
separator = /
type = shared
}
namespace inbox {
inbox = yes
location =
mailbox Drafts {
special_use = \Drafts
}
mailbox Junk {
autoexpunge = 2 weeks
autoexpunge_max_mails = 1000
special_use = \Junk
}
mailbox Sent {
special_use = \Sent
}
mailbox "Sent Messages" {
special_use = \Sent
}
mailbox Trash {
autoexpunge = 2 weeks
autoexpunge_max_mails = 1000
special_use = \Trash
}
prefix =
}
passdb {
args = scheme=CRYPT username_format=%u /etc/dovecot/users
driver = passwd-file
}
plugin {
acl = vfile
acl_shared_dict = file:/srv/vmail/%Ld/shared-mailboxes.db
quota = dict:user::file:%h/dovecot-quota
quota_rule = *:
quota_status_nouser = NOUSER
quota_status_overquota = OVER
quota_status_success = OK
sieve = /srv/vmail/%Ld/%Ln/active.sieve
sieve_max_script_size = 1M
sieve_quota_max_scripts = 50
sieve_quota_max_storage = 1M
}
postmaster_address = postmaster@domain.tld
protocols = " imap sieve pop3"
quota_full_tempfail = yes
service auth {
unix_listener auth-userdb {
mode = 0660
user = Debian-exim
}
}
service quota-status {
client_limit = 1
executable = quota-status -p postfix
unix_listener exim-quota-status {
group = Debian-exim
mode = 0660
user = Debian-exim
}
}
ssl_cert = </etc/ssl/mail_public.pem
ssl_client_ca_dir = /etc/ssl/certs
ssl_dh = </etc/ssl/mail_dh.pem
ssl_key = </etc/ssl/mail_private.pem
userdb {
args = username_format=%u /etc/dovecot/users
default_fields = uid=vmail gid=vmail userdb_home=/srv/vmail/%Ld/%Ln userdb_location=maildir:/srv/vmail/%Ld/%Ln userdb_quota_rule=*:storage=1G
driver = passwd-file
}
protocol lda {
mail_plugins = acl quota sieve
}
protocol imap {
mail_plugins = acl quota imap_acl imap_quota
}
Настройка Exim
Настройка Exim, как уже было сказано, зависит от настроенного Dovecot. Exim использует сервис Dovecot для SMTP-аутентификации и список почтовых ящиков из файла /etc/dovecot/users
.
Установка
Установим SMTP-сервер Exim:
# apt-get install exim4-daemon-heavy
Создаём файл конфигурации /etc/exim4/exim4.conf
со следующим начальным содержимым:
# Имя нашей почтовой системы
primary_hostname = mail.domain.tld
# Список доменов нашей почтовой системы
domainlist local_domains = /etc/exim4/local_domains
# Список доменов, для которых наша почтовая система является резервной
domainlist relay_domains = /etc/exim4/relay_domains
# Список узлов, почту от которых будем принимать без проверок
hostlist relay_from_hosts =
# Правила для проверок
acl_not_smtp = acl_check_not_smtp
acl_smtp_rcpt = acl_check_rcpt
#acl_smtp_data = acl_check_data
# Сокет-файл антивируса ClamAV
# av_scanner = clamd:/run/clamav/clamd.ctl
# Сокет-файл SpamAssassin
# spamd_address =
# Отключаем IPv6, слушаем порты 25 и 587
disable_ipv6
daemon_smtp_ports = 25 : 587
# Дописываем домены отправителя и получателя, если они не указаны
qualify_domain = domain.tld
qualify_recipient = domain.tld
# Exim никогда не должен запускать процессы от имени пользователя root
never_users = root
# Проверять прямую и обратную записи узла отправителя по DNS
host_lookup = *
# Отключаем проверку пользователей узла отправителя по протоколу ident
rfc1413_hosts = *
rfc1413_query_timeout = 0s
# Только эти узлы могут не указывать домен отправителя или получателя
sender_unqualified_hosts = +relay_from_hosts
recipient_unqualified_hosts = +relay_from_hosts
# Лимит размера сообщения, 30 мегабайт
message_size_limit = 30M
# Запрещаем использовать знак % для явной маршрутизации почты
percent_hack_domains =
# Настройки обработки ошибок доставки, используются значения по умолчанию
ignore_bounce_errors_after = 2d
timeout_frozen_after = 7d
begin acl
# Проверки для локальных отправителей
acl_check_not_smtp:
accept
# Проверки на этапе RCPT
acl_check_rcpt:
accept hosts = :
# Отклоняем неправильные адреса почтовых ящиков
deny message = Restricted characters in address
domains = +local_domains
local_parts = ^[.] : ^.*[@%!/|]
# Отклоняем неправильные адреса почтовых ящиков
deny message = Restricted characters in address
domains = !+local_domains
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
# В локальные ящики postmaster и abuse принимает почту всегда
accept local_parts = postmaster : abuse
domains = +local_domains
# Проверяем существование домена отправителя
require verify = sender
# Принимаем почту от доверенных узлов, попутно исправляя заголовки письма
accept hosts = +relay_from_hosts
control = submission
# Принимаем почту от аутентифицированных узлов, попутно исправляя заголовки письма
accept authenticated = *
control = submission/domain=
# Для не доверенных и не аутентифицированных требуется, чтобы получатель был в домене,
# ящик которого находится у нас или для которого мы являемся резервным почтовым сервером
require message = Relay not permitted
domains = +local_domains : +relay_domains
# Если домен правильный, то проверяем получателя
require verify = recipient
accept
begin routers
# Поиск транспорта для удалённых получателей
dnslookup:
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
# Пересылки для локальных получателей из файла /etc/aliases
system_aliases:
driver = redirect
allow_fail
allow_defer
domains = domain.tld
data = ${lookup{$local_part}lsearch{/etc/aliases}}
# Пересылки для получателей в разных доменах
aliases:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part@$domain}lsearch{/etc/exim4/aliases}}
# Получение почты на локальный ящик
mailbox:
driver = accept
condition = ${lookup{$local_part@$domain}lsearch{/etc/dovecot/users}{yes}{no}}
user = dovecot
transport = dovecot_virtual_delivery
cannot_route_message = Unknown user
begin transports
# Транспорт для удалённых получателей
remote_smtp:
driver = smtp
# Транспорт для локальных получателей из Dovecot
dovecot_virtual_delivery:
driver = pipe
command = /usr/lib/dovecot/dovecot-lda -d $local_part@$domain -f $sender_address
message_prefix =
message_suffix =
delivery_date_add
envelope_to_add
return_path_add
log_output
user = vmail
temp_errors = 64 : 69 : 70: 71 : 72 : 73 : 74 : 75 : 78
begin retry
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
begin rewrite
begin authenticators
# Использование LOGIN-аутентификации из Dovecot
dovecot_login:
driver = dovecot
public_name = LOGIN
server_socket = /var/run/dovecot/auth-userdb
server_set_id = $auth1
# Использование PLAIN-аутентификации из Dovecot
dovecot_plain:
driver = dovecot
public_name = PLAIN
server_socket = /var/run/dovecot/auth-userdb
server_set_id = $auth1
Сразу поменяем права доступа к файлу конфигурации:
# chmod u=rw,g=r,o= /ect/exim4/exim4.conf
# chown root:Debian-exim /etc/exim4/exim4.conf
Можно дать остальным пользователям доступ на чтение, т.к. никакой особо секретной информации в файле конфигурации нет. С другой стороны - нужды давать такой доступ тоже нет.
Чтобы Exim мог читать файл /etc/dovecot/users
, включим пользователя Debian-exim
в группу dovecot
:
# usermod -aG dovecot Debian-exim
Создадим файл /etc/exim4/local_domains
со списками локальных доменов, для которых Exim будет принимать почту к доставке:
domain.tld
Создадим файл /etc/exim4/relay_domains
со списокм доменов, для которых Exim будет выступать в качестве резервного. Он будет принимать почту для дальнейшей пересылки на SMTP-сервер с более высоким приоритетом, соответствующим меньшему значению в записи MX
соответствующего домена. В моём случае этот файл будет пустым.
Теперь можно перезапустить Exim, чтобы он начал работать в минимальной конфигурации:
# systemctl restart exim4
Также теперь можно перезапустить и Dovecot, т.к. после установки Exim в системе должен появиться пользователь Debian-exim
, упоминаемый в конфигурации Dovecot:
# systemctl restart dovecot
Настройка антивируса
Устанавливаем демон ClamAV для проверки файлов на вирусы:
# apt-get install clamav-daemon
Поскольку по велению Cisco обновления антивирусных баз данных больше не раздаются IP-адресам из России, нам понадобится настроить использование зеркала. Для этого нужно закомментировать в файле /etc/clamav/freshclam.conf
настроенные серверы с обновлениями и их зеркала, отключить опцию ScriptedUpdates
и указать российское зеркало, которое отдаёт обновления антивирусных баз:
#DNSDatabaseInfo current.cvd.clamav.net
ScriptedUpdates no
#DatabaseMirror db.local.clamav.net
#DatabaseMirror database.clamav.net
DatabaseMirror http://clamav.ufanet.ru
Сразу же обновляем антивирусную базу:
# freshclam
Включим clamav
в группу Debian-exim
, чтобы он мог сканировать файлы, созданные Exim'ом:
# usermod -aG Debian-exim clamav
Добавим в главную секцию файла /etc/exim4/exim4.conf
путь к сокет-файлу ClamAV и ACL для этапа DATA:
av_scanner = clamd:/run/clamav/clamd.ctl
acl_smtp_data = acl_check_data
В секцию acl
файла /etc/exim4/exim4.conf
добавим правило, запрещающее приём писем, содержащих вирусы:
acl_check_data:
deny message = message contains a virus ($malware_name)
malware = *
accept
Перезагрузим Exim, чтобы настройки вступили в силу:
# systemctl reload exim4
Осталось проверить, что антивирусная система используется. Для этого создадим специально предназначенный для таких целей тестовый файл EICAR:
$ echo -n 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > eicar.txt
И попробуем его отправить во вложении с какого-нибудь почтового ящика почтовой системы на тот же ящик. Если письмо не пришло, значит антивирусная система работает. Для полной уверенности можно ещё заглянуть в журнал почтовой системы /var/log/exim4/mainlog
, где должна появиться строчка вида:
2014-04-12 22:06:06 1WZ0RR-0007TO-RM H=localhost (domain.tld) [127.0.0.1] F= A=dovecot_plain:box@domain.tld rejected after DATA: message contains a virus (Eicar-Test-Signature)
Проверка квот
В файл конфигурации /etc/exim4/exim4.conf
добавим в правило acl_check_rcpt
дополнительную проверку перед окончательным действием accept
:
defer message = 422 Mailbox $local_part@$domain is over quota
domains = +local_domains
condition = ${if eq{${extract{action}\
{${readsocket{/var/run/dovecot/exim-quota-status}\
{size=$message_size\nrecipient=$local_part@$domain\n\n}\
{5s}\
{ }\
{action=FAIL}}}}}\
{OVER}\
{yes}\
{no}}
Указанное выше условие отправляет в сокет /var/run/dovecot/exim-quota-status
три строки. В первой строке указан размер письма, во второй - его получатель, третья строка - пустая. Пустая строка сигнализирует о завершении запроса. Дальше в течение 5 секунд ожидается ответ. Если ответ поступил, то все переводы строк в ответе заменяются на пробелы. Если ответ не поступил, то вместо ответа дальше будет использоваться строка "action=FAIL". В результате должна получиться строка, в которой содержится ассоциативный массив, в котором разделителями элементов являются пробелы, а разделителями ключей и значений - знаки "равно", вот такая:
key1=value1 key2=value2 key3=value3
Выражение extract извлекает из этого словаря значение ключа action. Если значение равно OVER, то считается, что ящик переполнен и отправителю сообщается, что он должен отложить письмо в очередь, т.к. в данный момент почтовый ящик одного из получателей переполнен.
Перезагрузим Exim, чтобы настройки вступили в силу:
# systemctl reload exim4
Письма на переполненный ящик отбиваются с таким сообщением в журнале:
2019-06-11 22:48:31 H=forward100p.mail.yandex.net [77.88.28.100] X=TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256 CV=no F= temporarily rejected RCPT : 422 Mailbox yyy@domain.tld is over quota
Настройка SSL/TLS
Изменим настройки прослушиваемых портов в главной секции файла /etc/exim4/exim4.conf
, добавив в список порт 465:
daemon_smtp_ports = 25 : 465 : 587
Добавим настройки TLS в главную секцию файла /etc/exim4/exim4.conf
:
tls_on_connect_ports = 465
tls_advertise_hosts = *
tls_certificate = /etc/ssl/mail_public.pem
tls_privatekey = /etc/ssl/mail_private.pem
Порт 465 используется для подключения сразу по защищённому каналу SSL, без явного согласования перехода на защищённый обмен данными. Порт 587 обычно используется для подключений со стороны почтовых клиентов, как правило, с обязательным использованием аутентификации. В рассматриваемой конфигурации порты 25 и 587 никак не различаются, поведение сервера одинаково на обоих портах.
Для задействования добавленных настроек TLS, перезапустим почтовый сервер:
# systemctl restart exim4
Настройка DKIM-подписей
Для удобного создания ключей DKIM-подписей можно установить пакет opendkim-tools
:
# apt-get install opendkim-tools
На самом деле необходимые ключи можно генерировать и при помощи openssl
, т.к. пакет opendkim-tools
содержит набор shell-скриптов, являющихся обёрткой над утилитой openssl
.
Теперь создадим каталог для ключей и сгенерируем пару ключей для домена domain.tld
:
# mkdir /etc/exim4/dkim
# cd /etc/exim4/dkim
# opendkim-genkey -D /etc/exim4/dkim/ -d domain.tld -s mail
# mv mail.private mail.domain.tld.private
# mv mail.txt mail.domain.tld.txt
Далее можно сгенерировать ключи для других доменов, обслуживаемых нашей почтовой системой.
В файле /etc/exim4/dkim/domains
пропишем соответствие доменов отправителей и используемых приватных ключей:
domain.tld: key=/etc/exim4/dkim/mail.domain.tld.private
Выставим права доступа к файлам приватных ключей:
# cd /etc/exim4/dkim
# chmod u=rw,g=r,o= *.private
# chown root:Debian-exim *.private
Добавляем в секцию транспортов файла /etc/exim4/exim4.conf
, в транспорт remote_smtp
, настройки для добавления DKIM-подписей к письмам:
# Транспорт для удалённых получателей
# Добавляем к исходящим письмам DKIM-подпись
remote_smtp:
driver = smtp
dkim_domain = ${lookup{$sender_address_domain}lsearch*@{/etc/exim4/dkim/domains}{$sender_address_domain}{}}
dkim_selector = mail
dkim_private_key = ${extract{key}{${lookup{$sender_address_domain}lsearch*@{/etc/exim4/dkim/domains}}}{$value}{}}
Достаточно перезагрузить конфигурацию, чтобы письма во внешние домены начали подписываться DKIM-ключами:
# systemctl reload exim4
Проверка DKIM-подписей
Воспользуемся встроенной в Exim возможностью проверки DKIM-подписей входящих писем. Я буду проверять подписи у тех писем, в которых они есть. Плюс к тому, будем требовать наличия правильной DKIM-подписи для доменов публичных почтовых сервисов, о которых заведомо известно, что они добавляют DKIM-подписи к своим письмам. Это позволит защититься от поддельных писем, якобы исходящих из доменов этих почтовых сервисов.
Зададим в главной секции файла /etc/exim4/exim4.conf
домены, для которых требуется правильная DKIM-подпись:
domainlist dkim_required_domains = gmail.com : yandex.ru : rambler.ru : \
mail.ru : bk.ru : list.ru : inbox.ru
Практика показала, что неправильная настройка DKIM-подписей - не редкость даже в солидных компаниях. Тем не менее, от них бывает нужно принимать письма. Впишем в главную секцию файла /etc/exim4/exim4.conf
домены, у которых не нужно проверять DKIM-подпись, даже при её наличии:
# Домены, до которых подпись DKIM проверяться не будет
domainlist dkim_accept_domains = bashgaz.ru : brsc.ru
В эту же главную секцию файла /etc/exim4/exim4.conf добавим имя списка управления доступом, который будет проверять DKIM-подпись:
acl_smtp_dkim = acl_check_dkim
В секцию acl файла /etc/exim4/exim4.conf
добавим описание самого списка управления доступом:
acl_check_dkim:
# Принимаем письма от указанных доменов без проверки подписи DKIM
accept sender_domains = +dkim_accept_domains
# Отклоняем письма с неправильной DKIM-подписью
deny message = Wrong DKIM signature
dkim_status = fail
# Для выбранных доменов требуем наличия DKIM-подписи
deny message = Valid DKIM signature needed for mail from $sender_domain
sender_domains = +dkim_required_domains
dkim_status = none
accept
Перезагрузим файл конфигурации Exim, чтобы настройки вступили в силу:
# systemctl reload exim4
Настройка грейлистинга
Для грейлистинга воспользуемся демоном greylistd
, написанном на Python. Этот демон не настолько сложен, как milter-greylist
, которым я воспользовался для настройки грейлистинга в Postfix, однако его простота с лихвой компенсируется возможностями Exim. Установим пакет greylistd
:
# apt-get install greylistd
greylistd
предоставляет механизм, а политику можно определить в конфигурации Exim. Я придерживаюсь политики подвергать грейлистингу те узлы, которые оказались в чёрном списке. Для того, чтобы включить грейлистинг, нужно в самый конец списка управления доступом acl_check_rcpt
до финального правила accept добавить следующую проверку:
# Если отправитель попал в чёрный список, отправляем его в грейлистинг
defer message = Greylisting in action, try later
!senders = :
!hosts = ${if exists{/etc/greylistd/whitelist-hosts}\
{/etc/greylistd/whitelist-hosts}{}} : \
${if exists{/var/lib/greylistd/whitelist-hosts}\
{/var/lib/greylistd/whitelist-hosts}{}}
dnslists = zen.spamhaus.org : exitnodes.tor.dnsbl.sectoor.de
condition = ${readsocket{/var/run/greylistd/socket}\
{--grey $sender_host_address $sender_address $local_part@$domain}\
{5s}{}{false}}
В поле !senders
можно прописать адреса тех отправителей, которые не должны подвергаться грейлистингу. Соответственно, чтобы узел с определённым IP-адресом не подвергался грейлистингу, его можно добавить в файл /etc/greylistd/whitelist-hosts
.
Включим Debian-exim
в группу greylist
, чтобы Exim имел доступ к сокету и файлам greylistd
:
# usermod -aG greylist Debian-exim
Осталось попросить Exim перезагрузить файл конфигурации, чтобы новые настройки вступили в силу:
# systemctl reload exim4
Настройка SPF-записи
SPF-запись - это TXT-запись следующего вида:
domain.tld. IN TXT "v=spf1 +mx ~all"
Если указанный домен обслуживает Sender Policy Framework, описывающему синтаксис SFP-записи - SPF Record Syntax. Стоит также прочесть о наиболее частых ошибках, допускаемых при создании SFP-записи - Common mistakes.
Проверка SPF-записей
К счастью, поддержка проверки SPF, встроенная в Exim, более не находится в статусе экспериментальной и для её включения больше не нужно пересобирать пакет, т.к. он уже собран с соответствующей опцией.
SPF-записи могут классифицировать IP-адрес одним из следующих образов:
pass (+)
- рекомендуется принять почту,fail (-)
- рекомендуется отклонить почту,softfail (~)
- рекомендуется принять почту, но пометить её как подозрительную,neutral (?)
- рекомендуется обрабатывать почту таким образом, как будто SPF-записи не существует.
Дополнительно, есть ещё два статуса, которые сообщают о постоянной или временной ошибке проверки IP-адреса.
Когда SPF-записи были только придуманы, некоторые системные администраторы слишком буквально воспринимали их рекомендации. Случались ситуации, когда первичный почтовый сервер не принимал почту от своего резервного сервера лишь потому, что его IP-адресу соответствовала SPF-запись, предписывающая не принимать письмо. Поэтому сложилась практика не указывать политику fail
, а использовать вместо неё политики softfail
или neutral
. На мой взгляд, если бы не было таких прямолинейных системных администраторов, не было бы никакого смысла в политиках, отличных от pass
и fail
.
Нормальная почта может исходить только от серверов отправителя и должна приходить на серверы получателя без каких-либо посторонних промежуточных серверов. Сервер получателя должен проверять соответствие отправителя SPF-политике на основных и резервных серверах, а при приёме писем с резервного сервера на основной уже не обращать внимания на то, что его резервный сервер не удовлетворяет политике SPF. Именно поэтому я воспринимаю политики softfail
и neutral
точно так же, как воспринимаю политику fail
. Я в любом случае приму почту от резервного сервера, не смотря на рекомендации SPF-записи, но на резервном сервере я обязательно их проверю.
Перед правилами проверки грейлистинга в списке управления доступом acl_check_rcpt
в секции acl файла /etc/exim4/exim4.conf
добавим следующее правило, соответствующее описанным выше соображениям:
# Отклоняем письма, не соответствующие политике домена отправителя
deny message = Reject due SPF policy
spf = fail : softfail : neutral
Осталось лишь перезапустить Exim, чтобы заработал демон из собранного нами пакета и вступили в силу новые настройки:
# systemctl reload exim4
Требование аутентификации
Чтобы запретить локальным пользователям отправлять почту без аутентификации, добавим в конфигурацию такое правило:
# Не даём локальным отправителям слать почту без аутентификации
deny message = Local sender must be authenticated
sender_domains = +local_domains
!authenticated = *
Чтобы аутентифицированный пользователь не пытался подставить чужой адрес отправителя, добавим в конфигурацию такое правило:
# Не даём локальным отправителям представляться чужим именем
deny message = Send your letters from your mailbox
condition = ${if eq{$authenticated_id}{$sender_address}{no}{yes}}
authenticated = *
Оба правила нужно добавить в секцию acl файла /etc/exim4/exim4.conf, в правило acl_check_rcpt перед принятием почты от аутентифицированных пользователей.
Осталось перезагрузить файл конфигурации, чтобы она вступила в силу:
# systemctl reload exim4
Итоговый файл конфигурации
В конечном итоге у меня получился такой файл конфигурации /etc/exim4/exim4.conf
:
# Имя нашей почтовой системы
primary_hostname = mail.domain.tld
# Список доменов нашей почтовой системы
domainlist local_domains = /etc/exim4/local_domains
# Список доменов, для которых наша почтовая система является резервной
domainlist relay_domains = /etc/exim4/relay_domains
# Домены, для которых требуется наличие правильной DKIM-подписи
domainlist dkim_required_domains = gmail.com : yandex.ru : rambler.ru : \
mail.ru : bk.ru : list.ru : inbox.ru
# Домены, до которых подпись DKIM проверяться не будет
domainlist dkim_accept_domains = bashgaz.ru : brsc.ru
# Список узлов, почту от которых будем принимать без проверок
hostlist relay_from_hosts =
# Правила для проверок
acl_not_smtp = acl_check_not_smtp
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
# Сокет-файл антивируса ClamAV
av_scanner = clamd:/run/clamav/clamd.ctl
# Сокет-файл SpamAssassin
# spamd_address =
# Отключаем IPv6, слушаем порты 25 и 587
disable_ipv6
daemon_smtp_ports = 25 : 465: 587
tls_on_connect_ports = 465
tls_advertise_hosts = *
tls_certificate = /etc/ssl/mail_public.pem
tls_privatekey = /etc/ssl/mail_private.pem
# Дописываем домены отправителя и получателя, если они не указаны
qualify_domain = domain.tld
qualify_recipient = domain.tld
# Exim никогда не должен запускать процессы от имени пользователя root
never_users = root
# Проверять прямую и обратную записи узла отправителя по DNS
host_lookup = *
# Отключаем проверку пользователей узла отправителя по протоколу ident
rfc1413_hosts = *
rfc1413_query_timeout = 0s
# Только эти узлы могут не указывать домен отправителя или получателя
sender_unqualified_hosts = +relay_from_hosts
recipient_unqualified_hosts = +relay_from_hosts
# Лимит размера сообщения, 30 мегабайт
message_size_limit = 30M
# Запрещаем использовать знак % для явной маршрутизации почты
percent_hack_domains =
# Настройки обработки ошибок доставки, используются значения по умолчанию
ignore_bounce_errors_after = 2d
timeout_frozen_after = 7d
begin acl
# Проверки для локальных отправителей
acl_check_not_smtp:
accept
# Проверки на этапе RCPT
acl_check_rcpt:
accept hosts = :
# Отклоняем неправильные адреса почтовых ящиков
deny message = Restricted characters in address
domains = +local_domains
local_parts = ^[.] : ^.*[@%!/|]
# Отклоняем неправильные адреса почтовых ящиков
deny message = Restricted characters in address
domains = !+local_domains
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
# В локальные ящики postmaster и abuse принимает почту всегда
accept local_parts = postmaster : abuse
domains = +local_domains
# Проверяем существование домена отправителя
require verify = sender
# Принимаем почту от доверенных узлов, попутно исправляя заголовки письма
accept hosts = +relay_from_hosts
control = submission
# Не даём локальным отправителям слать почту без аутентификации
deny message = Local sender must be authenticated
sender_domains = +local_domains
!authenticated = *
# Не даём локальным отправителям представляться чужим именем
deny message = Send your letters from your mailbox
condition = ${if eq{$authenticated_id}{$sender_address}{no}{yes}}
authenticated = *
# Принимаем почту от аутентифицированных узлов, попутно исправляя заголовки письма
accept authenticated = *
control = submission/domain=
# Для не доверенных и не аутентифицированных требуется, чтобы получатель был в домене,
# ящик которого находится у нас или для которого мы являемся резервным почтовым сервером
require message = Relay not permitted
domains = +local_domains : +relay_domains
# Если домен правильный, то проверяем получателя
require verify = recipient
# Отклоняем письма, не соответствующие политике домена отправителя
deny message = Reject due SPF policy
spf = fail : softfail : neutral
# Отклоняем письма для адресатов, превысивших квоту
defer message = 422 Mailbox $local_part@$domain is over quota
domains = +local_domains
condition = ${if eq{${extract{action}\
{${readsocket{/var/run/dovecot/exim-quota-status}\
{size=$message_size\nrecipient=$local_part@$domain\n\n}\
{5s}\
{ }\
{action=FAIL}}}}}\
{OVER}\
{yes}\
{no}}
# Если отправитель попал в чёрный список, отправляем его в грейлистинг
defer message = Greylisting in action, try later
!senders = :
!hosts = ${if exists{/etc/greylistd/whitelist-hosts}\
{/etc/greylistd/whitelist-hosts}{}} : \
${if exists{/var/lib/greylistd/whitelist-hosts}\
{/var/lib/greylistd/whitelist-hosts}{}}
dnslists = zen.spamhaus.org : exitnodes.tor.dnsbl.sectoor.de
condition = ${readsocket{/var/run/greylistd/socket}\
{--grey $sender_host_address $sender_address $local_part@$domain}\
{5s}{}{false}}
accept
acl_check_data:
deny message = message contrains a virus ($malware_name)
malware = *
accept
acl_check_dkim:
# Принимаем письма от указанных доменов без проверки подписи DKIM
accept sender_domains = +dkim_accept_domains
# Отклоняем письма с неправильной DKIM-подписью
deny message = Wrong DKIM signature
dkim_status = fail
# Для выбранных доменов требуем наличия DKIM-подписи
deny message = Valid DKIM signature needed for mail from $sender_domain
sender_domains = +dkim_required_domains
dkim_status = none
accept
begin routers
# Поиск транспорта для удалённых получателей
dnslookup:
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
# Пересылки для локальных получателей из файла /etc/aliases
system_aliases:
driver = redirect
allow_fail
allow_defer
domains = domain.tld
data = ${lookup{$local_part}lsearch{/etc/aliases}}
# Пересылки для получателей в разных доменах
aliases:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part@$domain}lsearch{/etc/exim4/aliases}}
# Получение почты на локальный ящик
mailbox:
driver = accept
condition = ${lookup{$local_part@$domain}lsearch{/etc/dovecot/users}{yes}{no}}
user = dovecot
transport = dovecot_virtual_delivery
cannot_route_message = Unknown user
begin transports
# Транспорт для удалённых получателей
remote_smtp:
driver = smtp
dkim_domain = ${lookup{$sender_address_domain}lsearch*@{/etc/exim4/dkim/domains}{$sender_address_domain}{}}
dkim_selector = mail
dkim_private_key = ${extract{key}{${lookup{$sender_address_domain}lsearch*@{/etc/exim4/dkim/domains}}}{$value}{}}
# Транспорт для локальных получателей из Dovecot
dovecot_virtual_delivery:
driver = pipe
command = /usr/lib/dovecot/dovecot-lda -d $local_part@$domain -f $sender_address
message_prefix =
message_suffix =
delivery_date_add
envelope_to_add
return_path_add
log_output
user = vmail
temp_errors = 64 : 69 : 70: 71 : 72 : 73 : 74 : 75 : 78
begin retry
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
begin rewrite
begin authenticators
# Использование LOGIN-аутентификации из Dovecot
dovecot_login:
driver = dovecot
public_name = LOGIN
server_socket = /var/run/dovecot/auth-userdb
server_set_id = $auth1
# Использование PLAIN-аутентификации из Dovecot
dovecot_plain:
driver = dovecot
public_name = PLAIN
server_socket = /var/run/dovecot/auth-userdb
server_set_id = $auth1
Заключение
В описанном варианте настройки мне не нравится то, что файлы с настройками не удалось передать в собственность отдельной группы пользователей. Это позволило бы предоставлять определённым системным пользователям доступ на редактирование этих файлов. Кроме того, можно было бы написать даже веб-приложение для управления доменами, почтовыми ящиками и пересылками. Не удалось это сделать по той простой причине, что Dovecot наотрез отказывается использовать вторичные группы при доступе к файлам. Возможно это недоработка самого Dovecot, а может быть - предусмотренное поведение. Сейчас у меня нет острой необходимости разобраться в этом, поэтому я оставил всё как получилось: пользователь Debian-exim
состоит в группах dovecot
и greylist
, а в группе Debian-exim
состоит пользователь clamav
.