Postfix и Dovecot. Установка и настройка. Часть 1 из 2
Почтовая служба
Типичная почтовая служба состоит из следующих основных компонентов
- Почтовый сервер, или агент пересылки сообщений (Mail Transport Agent, MTA). Этот компонент ответственен за перемещение электронной почты между почтовыми серверами, этим занимаются, например, Sendmail и Postfix.
- Агент доставки электронной почты (Mail Delivery Agent, MDA). Этот компонент отвечает за распределение полученных сообщений по локальным почтовым ящикам пользователей. Например, это Maildrop и Procmail. MDA иногда называют LDA (Local Delivery Agent).
- Агент отправки электронной почты (Message Submission Agent, MSA). Этот компонент отвечает за получение сообщений от MUA и передачу этих сообщений MTA. Многие MTA также выполняют функцию MSA, но существуют специально разработанные MSA без полной функциональности MTA.
- Агент доступа к электронной почте (Mail Access Agent, MAA). Этот компонент взаимодействует с почтовыми клиентами пользователей MUA по протоколу POP3 и/или IMAP. Например, это Cyrus или Dovecot для работы с POP3/IMAP.
- Почтовый клиент, который ещё называют почтовым агентом (Mail User Agent, MUA). Именно с ним взаимодействует пользователь, например это Thunderbird или MS Outlook. Они позволяют пользователю читать почту и писать электронные письма.
Почтовый сервер (Mail Transport Agent, MTA) может выступать в роли сервера и в роли клиента. Когда почтовый сервер получает письма — он выступает как SMTP-сервер. Когда почтовый сервер отправляет письма — он выступает как SMTP-клиент. Поэтому в составе Postfix есть SMTP-клиент smtp и SMTP-сервер smtpd.
Отправка почты, протоколы SMTP и ESMTP
Протокол SMTP (Simple Mail Transfer Protocol) определяет правила пересылки почты между компьютерами, при этом, он не регламентирует правила хранения или визуализации сообщений. Протокол используется как для пересылки сообщений между двумя почтовыми серверами, так и для отправления сообщения от почтового клиента почтовому серверу.
Протокол SMTP — это старый и очень простой протокол, в котором отсутствуют многие возможности, без которых работа сегодня немыслима, поэтому сегодня практически везде используется его расширенная версия ESMTP (Extended Simple Mail Transfer Protocol), но очень часто название протокола, даже в технической литературе, продолжает указываться как SMTP.
Протокол SMTP использует для работы порт 25/TCP и изменить это значение нельзя, точнее, мы можем это сделать, но после этого нас не смогут найти другие почтовые сервера, которые решат доставить нам почту. Также это единственный порт, который обязательно должен быть доступен из внешней сети.
Современные коммуникации нельзя представить без шифрования и SMTP не исключение. Первоначально развитие многих протоколов шло по пути создания защищенной при помощи SSL/TLS версии и назначения для нее отдельного порта. Поэтому появился защищённый протокол SMTPS (SMTP over SSL), который работает на 465-ом порту.
Однако такой подход не принес желаемого результата и для протокола SMTP было разработано расширение STARTTLS. Почтовые серверы начинают сессию как обычную SMTP, а потом договариваются о шифровании и согласовывают алгоритмы. Всегда используется самый стойкий алгоритм из доступных, если же договориться не получилось, то обмен почтой происходит в открытом виде.
Все это взаимодействие происходит через один-единственный порт — 25, как для зашифрованной, так и незашифрованной почты.
В настоящий момент принято соглашение, что порт 25 используется только для ретрансляции почты, т.е. взаимодействия между серверами, клиентам использовать этот порт не следует. Для взаимодействия с клиентами была создана новая служба MSA (Message submission agent) — агент отправки почты, который обычно является частью MTA, но использует собственный порт 587 TCP.
Более того, вынесение обслуживания клиентов в отдельную службу позволяет более тонко настроить безопасность, например, запретив незащищенные подключения, что не позволит пользователю настроить почтовый клиент неправильно. При разделении функций MTA и MSA наилучшей практикой будет открытие 25 порта только во внешнюю сеть, а 587 — во внутреннюю.
submissions (последняя s означает SSL).
Получение почты, протоколы POP3 и IMAP
Когда почтовый клиент настроен на использование POP3, он подключается к почтовому серверу и загружает все входящие сообщения пользователя. Эти сообщения сохраняются на компьютере пользователя и обычно удаляются с сервера электронной почты. Некоторые почтовые клиенты могут быть настроены так, чтобы оставлять копии сообщений на сервере. POP3 не обеспечивает синхронизацию между почтовыми клиентами и сервером. После загрузки сообщения любые изменения на почтовом сервере — не будут видны на почтовом клиенте.
IMAP — протокол, используемый для доступа к почтовым сообщениям, хранящимся на сервере электронной почты. Протокол позволяет пользователям получать доступ к сообщениям электронной почты непосредственно на сервере. Когда клиент запрашивает доступ к электронному сообщению, сервер отправляет копию сообщения клиенту. Когда пользователь удаляет или перемещает сообщение на одном устройстве, сервер обновляет статус сообщения, чтобы все другие устройства, имеющие доступ к той же учетной записи — отразили эти изменения.
Протоколы имеют свои защищенные версии — POP3S, который использует порт 995/TCP и IMAPS, который использует порт 993/TCP. И есть расширение STARTTLS, которое позволяет создать зашифрованное соединение прямо поверх обычного TCP-соединения — по аналогии с протоколом SMTP.
Существуют различные реализации IMAP и POP3, самый популярный — сервер Dovecot, который позволяет работать с обоими протоколами. В системах, основанных на Debian, функционал IMAP и POP3 предоставляются в двух разных пакетах — dovecot-imapd и dovecot-pop3d.
Порты для почтового сервера
Таким образом, на почтовом сервер для нормальной работы должны быть открыты следующие порты
Протокол SMTP
- 25/TCP SMTP (стандартный, поддержка TLS/SSL с помощью STARTTLS)
- 587/TCP submission (порт для почтовых агентов MUA для отправки сообщений)
- 465/TCP SMTPS (нужна предварительная установка TLS/SSL соединения)
Протокол POP3
- 110/TCP POP3 (стандартный, поддержка TLS/SSL с помощью STARTTLS)
- 995/TCP POP3S (нужна предварительная установка TLS/SSL соединения)
Протокол IMAP
- 143/TCP IMAP (стандартный, поддержка TLS/SSL с помощью STARTTLS)
- 993/TCP IMAPS (нужна предварительная установка TLS/SSL соединения)
ДНС-записи для почтового сервера
Во-первых, добавим A-запись, которая свяжет ip-адрес почтового сервера 222.222.222.222 с доменным именем mail.example.com. Во-вторых, добавим MX-запись, которая сообщит отправителю, что письмо с адресом получателя feedback@example.com нужно отправлять серверу mail.example.com. В-третьих, добавим SPF-запись, которая сообщит получателям список наших доверенных серверов. Получатели должны принимать только письма от серверов, указанных в A-записях и MX-записях.
| Name | Type | TTL | Value |
|---|---|---|---|
example.com |
A |
3600 |
111.111.111.111 |
www.example.com |
A |
3600 |
111.111.111.111 |
mail.example.com |
A |
3600 |
222.222.222.222 |
example.com |
MX 10 |
3600 |
mail.example.com |
example.com |
TXT |
3600 |
v=spf1 a mx -all |
SPF (Sender Policy Framework) позволяет владельцу домена указать, какие почтовые серверы имеют право отправлять почту от имени этого домена. Строка spf1 a mx -all указывает версию протокола SPF, разрешает отправку с ip-адреса, на который указывает A-запись домена, разрешает отправку с ip-адресов, указанных в MX-записях домена, строго запрещает отправку со всех прочих ip-адресов.
DKIM позволяет добавить цифровую подпись к сообщениям, которые отправляет наш почтовый сервер. Эта подпись связана с доменом и подтверждает, что письмо отправлено нашим сервером. Для этого нужно создать пару ключей — приватный и публичный. Приватный ключ хранится на сервере и используется для подписи каждого исходящего сообщения. Публичный ключ доступен всем — для проверки, что сообщение было отправлено нашим сервером.
DMARC — техническая спецификация, созданная для борьбы с подделкой адресов почты. DMARC стандартизирует, как почтовые серверы-получатели должны обрабатывать письма, которые не прошли проверку подлинности SPF и/или DKIM, а также предоставляет механизм отчетности для доменов-отправителей. Владелец домена публикует DMARC-политику в виде TXT-записи в DNS. Эта запись сообщает принимающим серверам, что домен использует DMARC, и какие действия следует предпринять, если письмо не проходит проверку.
Установка SMTP сервера Postfix
Здесь все просто — нужно установить пакет postfix без файла конфигурации
# apt install postfix
Минимальная конфигурация Postfix
Теперь создадим файл конфигурации — в нем будет минимум опций, которые нужны для настройки безопасной конфигурации. Нужно задать свои значения опций myhostname и myorigin — чтобы указать почтовый домен, например example.com. Кроме того, на этапе настройки желательно задать значение опции mynetworks — чтобы почтовый сервер принимал почту только от одного ip-адреса. С этого ip-адреса (у меня это 123.123.123.123) будем отправлять почтовые сообщения для тестирования конфигурации.
# nano /etc.postfix/main.cf
# Какие настройки по умолчанию использовать. У разных версий Postfix разные # настройки по умолчанию — они изменяются от версии к версии. compatibility_level = 3.6 # Установка значения yes (вместо значения по умолчению no) приводит к тому, # что ответы с кодами 5xx преобразуются в 4xx. Соответственно, почтовые # серверы будут пытаться доставить сообщения на наш почтовый сервер позже. За # это время можно проверить логи и убедиться, правильно ли работают правила. soft_bounce = yes # Местоположение очередей и корневой каталог демонов Postfix, которые # запускаются в среде с измененным корневым каталогом (chroot). queue_directory = /var/spool/postfix # Местоположение всех утилит Postfix — postdrop, postmap, postqueue и другие. command_directory = /usr/sbin # Местоположение всех демонов Postfix, перечисленных в файле конфигурации # master.cf. Эта директория должна принадлежать пользователю root. daemon_directory = /usr/lib/postfix/sbin # Местоположение, куда Postfix будет записывать данные в процессе работы, # например кэши. Эта директория должна принадлежать $mail_owner, см.ниже. data_directory = /var/lib/postfix # Задает владельца очередей и большинства процессов демона Postfix. Нужно # задать аккаунт в системе, который больше нигде не используется и которому # не принадлежат другие файлы или процессы. Нельзя использовать nobody или # daemon. mail_owner = postfix # Используется для указания имени хоста почтового сервера. Типичные примеры # имён хостов почтовых серверов — smtp.example.com или mail.example.com. myhostname = mail.example.com # Позволяет указать почтовый домен, обслуживанием которого занимается сервер, # например — example.com. mydomain = example.com # Позволяет указать доменное имя, используемое в почтовых сообщениях, # отправленных с сервера. Когда пользователи отправляют почту без указания # имени домена в адресах конверта (SMTP ENVELOPE) или заголовках (MAIL # HEADER), эта опция определяет, какое имя домена должно быть добавлено. # По умолчанию используется значение опции $myhostname. myorigin = $mydomain # Задает сетевой интерфейс, который будет прослушиваться Postfix. По умолчанию # прослушиваются все сетевые интерфейсы на сервере (значение all). inet_interfaces = $myhostname, localhost # Содержит список доменов, которые сервер будет считать конечными пунктами # назначения для входящей почты. Другими словами — сервер не будет такие # письма пересылать дальше, а будет оставлять у себя. Значение по # умолчанию — $myhostname, localhost.$mydomain, localhost mydestination = $mydomain, localhost # Определяет локальных получателей, которые определены в файле паролей Linux # и картах псевдонимов. Письма другим получателям будут отклоняться (reject). # Сообщения предназначено локальному получателю, если доменная часть адреса # получателя совпадает с $mydestination или $inet_interfaces + была найдена # запись в /etc/passwd или в картах $alias_maps. local_recipient_maps = proxy:unix:passwd.byname $alias_maps # Код ответа сервера, если локальный получатель не найден, см. предыдущую # опцию конфигурации. Значение по умолчанию 550, но для начальном этапе # настройки есть смысл установить значение 450 (попробуйте снова позже). unknown_local_recipient_reject_code = 450 # Позволяет указать, с каких ip-адресов можно отправлять почту через этот # сервер. Обычно разрешают отправлять только из своей локальной сети. mynetworks = 127.0.0.0/8, 123.123.123.123/32 # Опция указывает на необходимость приема почты для этих доменов несмотря на # то, что этот сервер не является местом их конечного назначения. Самое # безопасное значение опции — пустое. relay_domains = # Задает список карт псевдонимов, используемых агентом доставки почты (MDA). # Карта позволяет перенаправить почту для каких-то получателей (даже не # существующих) другому получателю или получателям. alias_maps = hash:/etc/aliases # Задает список карт псевдонимов, которые создаются командой newaliases. Это # отдельная опция конфигурации, поскольку alias_maps может указывать на карты, # которые не обязательно находятся под контролем Postfix. alias_database = hash:/etc/aliases # Вся почта будет попадать в директорию /var/mail, где имеется файл для # каждого пользователя. Если в конце добавить слэш — то сообщения будут # сохраняться в виде отдельных файлов внутри /var/mail/username. mail_spool_directory = /var/mail/ # Задает имя файла для хранения сообщений в домашних директориях пользователей. # Можно задать значение опции в виде Maildir/ — в этом случае сообщения будут # сохраняться в виде отдельных файлов в директории /home/username/Maildir. #home_mailbox = Postbox #home_mailbox = Postdir/ # Использовать для локальной доставки почты указанного агента доставки почты. #mailbox_command = /usr/bin/procmail # Позволяет задать ответ, который возвращает сервер при подключении клиентов. # Лучше всего установить значение, чтобы оно не указывало — какой почтовый # сервер используется. В начале должно быть $myhostname — это требование RFC. smtpd_banner = $myhostname ESMTP $mail_name # Задает путь к утилите sendmail, которая входит в поставку Postfix и # предоставляет совместимый с Sendmail интерфейс для приложений, способных # только вызывать /usr/sbin/sendmail. sendmail_path = /usr/sbin/sendmail # Задает путь к команде newaliases, которая входит в поставку Postfix и # предназначенной для создания карты алиасов. Команда newaliases является # символической ссылкой на Postfix утилиту sendmail. newaliases_path = /usr/bin/newaliases # Задает путь к команде mailq, которая входит в поставку Postfix и # предназначена для просмотра очередей. Команда mailq является символической # ссылкой на Postfix утилиту sendmail. mailq_path = /usr/bin/mailq # Задает имя группы, которая используется для выполнения команд отправки почты # и управления очередями. Это должна быть группа, которая не используется # другими учетными записями, даже учетной записью Postfix. setgid_group = postdrop # Задает версию ip-протокола, которую будет использовать сервер при # установлении соединений. По умолчанию имеет значение all. inet_protocols = ipv4 # Добавлять важные заголовки, если их нет — например Date или Message-ID always_add_missing_headers = yes # Максимальное время для установки соединения с удалённым SMTP-сервером smtp_connect_timeout = 20s # Mакс.время ожидания ответа от удалённого SMTP-сервера на команду HELO/EHLO smtp_helo_timeout = 10s # ОГРАНИЧЕНИЯ НА ПРИЕМ ПОЧТЫ ЭТИМ ПОЧТОВЫМ СЕРВЕРОМ smtpd_helo_required = yes strict_rfc821_envelopes = yes disable_vrfy_command = yes # Не принимать почту на адреса, для которых у нас нет почтовых ящиков. # Вместо этой опции можно использовать правило reject_unlisted_recipient # в триггере smtpd_recipient_restrictions. В любом случае нужно создать # список почтовых адресов получателей, которые нужно принимать. #smtpd_reject_unlisted_recipient = yes # Ограничения на этапе установке подключения smtpd_client_restrictions = # Разрешить клиентов, прошедших аутентификацию по RFC 4954 permit_sasl_authenticated # Разрешить клиентов из доверенных сетей (опция $mynetworks) permit_mynetworks # Отклонять клиентов, у которых доменное имя из PTR-записи # не разрешается в тот же ip-адрес по A-записи reject_unknown_client_hostname # Ограничения на этапе команды HELO/EHLO smtpd_helo_restrictions = # Отклонять клиентов, использующих неправильный синтаксис домена reject_invalid_helo_hostname # Отклонять клиентов, указывающих не полное доменное имя FQDN reject_non_fqdn_helo_hostname # Разрешить клиентов, прошедших аутентификацию по RFC 4954 permit_sasl_authenticated # Разрешить клиентов из доверенных сетей (опция $mynetworks) permit_mynetworks # Отклонять клиентов, ДНС-имя которых из команды HELO/EHLO не # имеет A- или MX-записи или ip-адрес не является правильным reject_unknown_helo_hostname # Ограничения на этапе команды MAIL FROM smtpd_sender_restrictions = # Отклонять почту от отправителей с неполным доменным именем reject_non_fqdn_sender # Отклонять почту от отправителей из несуществующих доменов reject_unknown_sender_domain # Разрешить клиентов, прошедших аутентификацию по RFC 4954 permit_sasl_authenticated # Разрешить клиентов из доверенных сетей (опция $mynetworks) permit_mynetworks # Запросить сервер, обслуживающий указанный адрес отправителя, # на предмет существования на нём пользователя с этим адресом reject_unverified_sender # Ограничения на этапе команды RCPT TO (relay) smtpd_relay_restrictions = # Разрешить клиентов, прошедших аутентификацию по RFC 4954 permit_sasl_authenticated # Разрешить клиентов из доверенных сетей (опция $mynetworks) permit_mynetworks # Отклонять почту, если домена получателя нет в $mydestination, # $virtual_alias_domains, $virtual_mailbox_domains, $relay_domains reject_unauth_destination # Отклонять почту для получателя, если наш сервер не является # конечной точкой, а домен получателя не имеет A- и MX-записей reject_unknown_recipient_domain # Ограничения на этапе команды RCPT TO (spam) smtpd_recipient_restrictions = # Отклонять почту для получателей с неполным доменным именем reject_non_fqdn_recipient # Всегда принимать почту для учетных записей postmaster и abuse check_recipient_access hash:/etc/postfix/permit_recipients # Разрешить клиентов, прошедших аутентификацию по RFC 4954 permit_sasl_authenticated # Разрешить клиентов из доверенных сетей (опция $mynetworks) permit_mynetworks # Отклонять почту на адреса, для которых нет почтовых ящиков reject_unlisted_recipient # Ограничения на команду ETRN от почтовых серверов smtpd_etrn_restrictions = reject # Задержка в 1 сек между отправкой писем. Наш сервер # не сможет отправить больше 60 писем за минуту. smtp_transport_rate_delay = 1s # ОГРАНИЧЕНИЯ НА ПРИЁМ СООБЩЕНИЙ ОТ SMTP-КЛИЕНТОВ # Максимальное количество получателей сообщения, то # есть количество записей RCPT: (по умолчанию 1000) smtpd_recipient_limit = 100 # Период времени, на протяжении которого действуют # ограничения для клиента, перечисленые ниже anvil_rate_time_unit = 60s # клиент может отправить 30 сообщений за 60 секунд smtpd_client_message_rate_limit = 30 # не больше 30 получателей сообщений за 60 секунд smtpd_client_recipient_rate_limit = 30 # количество одновременно разрешенных подключений smtpd_client_connection_count_limit = 10 # количество разрешенных подключений за 60 секунд smtpd_client_connection_rate_limit = 30 # Клиенты, на которых не действуют ограничения. По # умолчанию $mynetworks, но у нас исключений нет smtpd_client_event_limit_exceptions = # Максимальное кол-во попыток аутентификации в минуту # с одного ip-адреса (для защиты от подбора паролей) smtpd_client_auth_rate_limit = 1 # ОГРАНИЧЕНИЯ НА РАЗМЕР ЯЩИКА И РАЗМЕР СООБЩЕНИЯ # ограничение на размер почтового ящика 500 Мбайт mailbox_size_limit = 524288000 virtual_mailbox_limit = 524288000 # макс. размер входящего и исходящего сообщения 15 Мб; # реальный размер сообщения будет около 10 Мб, потому # что кодирование бинарных данных увеличивает размер message_size_limit = 15728640
Такой файл конфигурации подойдет для небольшого кол-ва пользователей, каждый из которых имеет учетную запись в операционной системе. Это классический вариант настройки почтового сервера, который сейчас практически не используется.
По умолчанию вся почта для пользователя username сохраняется в файл /var/mail/username. Но мы в настройках Postfix указали сохранять почту в виде отдельных файлов в директории /var/mail/username. Давайте создадим эти директории для каждого пользователя системы. Возможно, есть способ при создании пользователя автоматически создавать директорию, но в файле конфигурации команды useradd есть только настройка для создания файла /var/mail/username.
# mkdir /var/mail/evgeniy # chown evgeniy /var/mail/evgeniy # mkdir /var/mail/sergey # chown sergey /var/mail/sergey
mbox (все сообщения хранятся в одном файле и отделены пустой строкой) и maildir (каждое сообщение хранится в отдельном файле).
Чтобы соответствовать требованиям RFC — почтовый сервер должен всегда принимать сообщения для пользователей abuse и postmaster. В нашем файле конфигурации есть правило check_recipient_access — но мы еще не создали карту доступа для этих пользователей.
# nano /etc/postfix/permit_recipients
# карта доступа, которая разрешает принимать почту для abuse и # postmaster, чтобы почтовый сервер отвечал требованиям RFC postmaster@example.com PERMIT abuse@example.com PERMIT
# postmap hash:/etc/postfix/permit_recipients
В результате будет создана индексированная карта /etc/postfix/permit_recipients.db, но в файле конфигурации мы ссылаемся на эту карту как hash:/etc/postfix/permit_recipients — с префиксом hash и без расширения .db.
Это все хорошо, но у нас на сервере нет пользователей abuse и postmaster — это значит, что почту для них должен получать кто-то другой. Скорее всего, это будет администратор сервера — который заодно будет получать почту для пользователя root.
# nano /etc/aliases
# почта для root, abuse и postmaster будет направлена # в почтовый ящик администратора сервера evgeniy root: evgeniy abuse: evgeniy postmaster: evgeniy
# postalias hash:/etc/aliases # создание индексированной карты
abuse и postmaster в файл /etc/aliases — нет необходимости в использовании check_recipient_access. Единственное ограничение, которое могло бы отклонить сообщение для abuse и postmaster — это reject_unlisted_recipient. Но это ограничение не сработает, потому что при проверке карты поиска /etc/aliases.db будут найдены записи abuse и postmaster.
Теперь все готово, запускаем почтовый сервер
# systemctl start postfix.service
permit_mynetworks и permit_sasl_authenticated потенциально опасные, поскольку позволяют отправлять почту «своим» SMTP-клиентам. Если была утечка пароля или компьютер пользователя заражен вирусом — возможна рассылка спама с нашего почтового сервера. Эти два правила лучше удалить из конфигурации после запуска демонов submission(s) — «свои» почтовые клиенты должны отправлять почту через них. Правила permit_mynetworks и permit_sasl_authenticated должны действовать только для демонов submission(s), но не для демона smtpd на 25-ом порту.
Отправка тестового сообщения
Отправлять будем с ip-адресов 123.123.123.123 (доверенная сеть) и 101.101.101.101 (чужая сеть). Получателем одного сообщения будет evgeniy@example.com, получателем другого сообщения будет somebody@mail.ru.
Правило reject_unauth_destination предписывает отбрасывать сообщение, если наш сервер не является местом назначения. Правила permit_sasl_authenticated и permit_mynetworks, расположенные выше — разрешают принимать такие сообщения. Эти два правила срабатывают только для «своих» клиентов, которые находятся в доверенной сети или прошли аутентификацию.
Отправим сообщение с ip-адреса 123.123.123.123 «своему» получателю evgeniy@example.com — и посмотрим, что ответит сервер
$ telnet mail.example.com 25 Trying 222.222.222.222... Connected to mail.example.com. Escape character is '^]'. 220 mail.example.com ESMTP Postfix HELO [123.123.123.123] 250 mail.example.com MAIL FROM: <sergey@example.com> 250 2.1.0 Ok RCPT TO: <evgeniy@example.com> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> Hello, Evgeniy! . 250 2.0.0 Ok: queued as 4497C20078 # сообщение принято и поставлено в очередь QUIT 221 2.0.0 Bye
Отправим сообщение с ip-адреса 123.123.123.123 «чужому» получателю somebody@mail.ru — и посмотрим, что ответит сервер
$ telnet mail.example.com 25 Trying 222.222.222.222... Connected to mail.example.com. Escape character is '^]'. 220 mail.example.com ESMTP Postfix HELO [123.123.123.123] 250 mail.example.com MAIL FROM: <sergey@example.com> 250 2.1.0 Ok RCPT TO: <somebody@mail.ru> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> Hello, Somebody! . 250 2.0.0 Ok: queued as 2AB80213C8 # сообщение принято и поставлено в очередь QUIT 221 2.0.0 Bye
Отправим сообщение с ip-адреса 101.101.101.101 «своему» получателю evgeniy@example.com — и посмотрим, что ответит сервер
$ telnet mail.example.com 25 Trying 222.222.222.222... Connected to mail.example.com. Escape character is '^]'. 220 mail.example.com ESMTP Postfix HELO [101.101.101.101] 250 mail.example.com MAIL FROM: <sergey@example.com> 250 2.1.0 Ok RCPT TO: <evgeniy@example.com> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> Hello, Evgeniy! . 250 2.0.0 Ok: queued as 4497C20078 # сообщение принято и поставлено в очередь QUIT 221 2.0.0 Bye
Отправим сообщение с ip-адреса 101.101.101.101 «чужому» получателю somebody@mail.ru — и посмотрим, что ответит сервер
$ telnet mail.example.com 25 Trying 222.222.222.222... Connected to mail.example.com. Escape character is '^]'. 220 mail.example.com ESMTP Postfix HELO [101.101.101.101] 250 mail.example.com MAIL FROM: <sergey@example.com> 250 2.1.0 Ok RCPT TO: <somebody@mail.ru> 554 5.7.1 <somebody@mail.ru>: Relay access denied QUIT 221 2.0.0 Bye
Давайте временно разрешим пересылку почты для домена mail.ru — для этого изменим значение опции relay_domains
# nano /etc/postfix/main.cf
# Опция указывает на необходимость приема почты для этих доменов, несмотря # на то, что этот сервер не является местом их конечного назначения. Самое # безопасное значение опции — пустое. relay_domains = mail.ru
# systemctl reload postfix.service # перечитать файлы конфигурации
Еще раз пробуем отправить сообщение «чужому» получателю somebody@mail.ru — теперь наш сервер принял сообщение
$ telnet mail.example.com 25 Trying 222.222.222.222... Connected to mail.example.com. Escape character is '^]'. 220 mail.example.com ESMTP Postfix HELO [101.101.101.101] 250 mail.example.com MAIL FROM: <sergey@example.com> 250 2.1.0 Ok RCPT TO: <somebody@mail.ru> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> Hello, Somebody! . 250 2.0.0 Ok: queued as D3DA0213BE # сообщение принято и поставлено в очередь QUIT 221 2.0.0 Bye
Обязательно возвращаем в исходное состояние файл конфигурации Postfix — потому что сейчас настройки небезопасные.
# nano /etc/postfix/main.cf
# Опция указывает на необходимость приема почты для этих доменов, несмотря # на то, что этот сервер не является местом их конечного назначения. Самое # безопасное значение опции — пустое. relay_domains =
# systemctl reload postfix.service # перечитать файлы конфигурации
Утилита управления postconf
Утилита предназначена для просмотра и изменения настроек сервера Postfix.
# postconf -p # значения всех опций настройки почтового сервера
# postconf -d опция_настройки # значение опции настройки по умолчанию
# postconf -n опция_настройки # значение опции настройки в файле конфигурации
# postconf -x опция_настройки # значение опции после подстановки переменных
# postconf -e опция=значение # установка значения в файле конфигурации
Примеры использования утилиты
# postconf -d mydestination mydestination = $myhostname, localhost.$mydomain, localhost
# postconf -n mydestination mydestination = $mydomain, localhost
# postconf -x mydestination mydestination = example.com, localhost
# postconf -p | grep ^my mydestination = $mydomain, localhost mydomain = example.com myhostname = mail.example.com mynetworks = 127.0.0.0/8, 123.123.123.123/32 mynetworks_style = ${{$compatibility_level} <level {2} ? {subnet} : {host}} myorigin = $mydomain
# postconf -px | grep ^my mydestination = example.com, localhost mydomain = example.com myhostname = mail.example.com mynetworks = 127.0.0.0/8, 123.123.123.123/32 mynetworks_style = host myorigin = example.com
Утилита sendmail
Утилита sendmail входит в поставку Postfix и предоставляет совместимый с Sendmail интерфейс для приложений, способных только вызывать /usr/sbin/sendmail.
# sendmail [опции] адрес_получателя
После указания адреса получателя можно вводить текст письма. Для завершения ввода текста предназначена комбинация клавиш Ctrl+D в начале новой строки. Опция -c — адрес копии, опция -c — адрес скрытой копии, опция -F — имя отправителя, опция -f — адрес отправителя, опция -v — подробный режим, опция -d — режим отладки, опция -t — получить заголовки из тела сообщения.
# sendmail -F evgeniy -f evgeniy@example.com sergey@example.com Hello, Sergey! Ctrl+D
Завершить ввод тела письма можно с помощью точки, которая будет единственным символом на новой строке
# sendmail -F evgeniy -f evgeniy@example.com sergey@example.com Hello, Sergey! .
Пример автоматического получения заголовков сообщения из тела сообщения при использовании опции -t
# sendmail -t << EOF From: evgeniy@example.com To: sergey@example.com Subject: Hello from Evgeniy Hello, Sergey! EOF
Посмотрим на это сообщение в почтовом ящике пользователя sergey — все заголовки были добавлены
# cat /var/mail/sergey/new/1745653143.Vfd01I212d9M63609.2283199-mail Return-Path: <root@example.com> X-Original-To: sergey@example.com Delivered-To: sergey@example.com Received: by mail.example.com (Postfix, from userid 0) id 0D9A8212DF; Sat, 26 Apr 2025 10:39:03 +0300 (MSK) To: sergey@example.com From: evgeniy@example.com Subject: Hello from Evgeniy Message-Id: <20250426073903.0D9A8212DF@mail.example.com> Date: Sat, 26 Apr 2025 10:39:03 +0300 (MSK) Hello, Sergey!
Коды ответов Postfix
Коды ответов определены в стандарте SMTP (RFC 5321), каждый код состоит из трёх цифр. Кроме того, существуют расширенные коды статуса (RFC 3463), которые идут после основного кода.
250 2.1.0 Ok │ │ │ │ └─ Текстовое описание для человека │ │ │ └─── Детальный код (третья цифра) │ │ └───── Категория (вторая цифра) │ └─────── Класс (первая цифра, дублирует основной код) └─────────── Основной код SMTP (RFC 5321)
Примеры кодов ответа Postfix
# Успешные операции 220 mail.example.com ESMTP Postfix # Приветствие 250 2.1.0 Ok # Команда выполнена 221 2.0.0 Bye # Закрытие соединения # Промежуточные 354 End data with <CR><LF>.<CR><LF> # Готов принять данные # Временные ошибки 421 4.3.0 Server too busy # Сервер перегружен 450 4.1.1 Mailbox unavailable # Ящик временно недоступен 451 4.3.0 Internal error # Внутренняя ошибка 452 4.3.1 Insufficient storage # Недостаточно места # Постоянные ошибки 500 5.5.1 Command unrecognized # Неизвестная команда 550 5.1.1 User unknown # Пользователь не найден 552 5.2.3 Message size exceeds limit # Превышен размер 554 5.7.1 Relay access denied # Релей запрещен
Директива конфигурации soft_bounce позволяет заменить все коды 5xx на 4xx — это полезно при отладке.
# Все коды 5xx заменяются на 4xx soft_bounce = yes # Нормальный режим (по умолчанию) soft_bounce = no
Упрощенная логика в исходном коде Postfix
if (user_not_found) { if (soft_bounce == yes) { return "450 4.1.1 User unknown"; // временная } else { return "550 5.1.1 User unknown"; // постоянная } }
Директивы конфигурации, которые задают коды ответов
# Проблемы с хостами unknown_client_reject_code = 450 unknown_hostname_reject_code = 450 unknown_helo_hostname_tempfail_code = 450 invalid_hostname_reject_code = 501 # Проблемы с форматом non_fqdn_reject_code = 504 # Relay ограничение relay_domains_reject_code = 554 # Неизвестные получатели unknown_local_recipient_reject_code = 550 unknown_relay_recipient_reject_code = 550 unknown_virtual_alias_reject_code = 550 unknown_virtual_mailbox_reject_code = 550 # Проверка адресов unknown_address_reject_code = 450 unknown_address_tempfail_code = 450 unverified_recipient_reject_code = 450 unverified_recipient_defer_code = 450 unverified_recipient_tempfail_code = 450 unverified_sender_reject_code = 450 unverified_sender_defer_code = 450 unverified_sender_tempfail_code = 450 # Коды smtpd_xxxxx_restrictions reject_unknown_client_hostname_code = 450 reject_unknown_helo_hostname_code = 450 reject_unknown_sender_domain_code = 450 reject_unknown_recipient_domain_code = 450 reject_unauth_destination_code = 554 reject_unauth_pipelining_code = 554
Основные демоны Postfix
Архитектура Postfix является модульной и содержит различные демоны, каждый из которых решает свою узкую задачу. Всех демонов мы рассмотрим позже, а сейчас перечислим только основных.
master— главный демон, который запускает и управляет всеми остальными демонамиsmtpd— демон SMTP-сервера, обрабатывающий входящие соединения для получения писем с другого почтового сервераsmtp— демон SMTP-клиента, обрабатывающий исходящие соединения для отправки писем на другой почтовый серверlocal— локальный агент доставки, отвечающий за доставку сообщений в почтовые ящики системных пользователейvirtual— локальный агент доставки, отвечающий за доставку сообщений в почтовые ящики виртуальных пользователейqmgr— менеджер очереди, обрабатывает и контролирует все сообщения в почтовой очереди
Почтовые пользователи могут быть пользователями системы (есть в файле /etc/passwd), либо виртуальными пользователями. Виртуальные пользователи не имеют учетной записи, почту для них получает специальный пользователь системы, которого нужно предварительно создать.
Виртуальные домены и ящики
Более подробно рассмотрим эту тему ниже, а сейчас только в общих чертах, поскольку «виртуальные пользователи» будут упоминаться постоянно. Кроме классического варианта настройки почтового сервера, когда есть пользователи системы и их почтовые ящики — есть еще два.
Домены виртуальных псевдонимов — увеличивает количество доменов, для которых сервер является местом конечного назначения. Это позволяет создать дополнительные адреса почты на других доменах — и перенаправить сообщения для этих адресов существующим пользователям системы. Пользователю sergey можно направить письмо на любой из этих адресов — sergey@example.com, sergey@example.net, sergey@example.org.
myhostname = mail.example.com mydomain = example.com myorigin = $mydomain mydestination = $mydomain, localhost virtual_alias_domains = example.net, example.org virtual_alias_maps = hash:/etc/postfix/virtual_alias_map
Домены виртуальных почтовых ящиков — это почтовые ящики для пользователей, у которых нет локальной учетной записи (нет в файле /etc/passwd). Почтовые сообщения для таких пользователей — будут перенаправлены в почтовый ящик /var/vmail/domain/username. Виртуальных доменов может быть несколько — поэтому путь к ящику включает domain.
Давайте посмотрим, как можно изменить наш вариант настройки почтового сервера. Для этого канонический домен example.com делаем виртуальным — то есть, переносим из mydestination в virtual_mailbox_domains. Далее можно увеличивать количество доменов, которые обслуживает почтовый сервер — просто добавляя новые домены в virtual_mailbox_domains.
# Переносим example.com из mydestination в virtual_mailbox_domains. Сообщения # для example.com попадают в ящики /var/vmail/example.com/username, сообщения # для localhost попадают в ящики /var/mail/username. myhostname = mail.example.com mydomain = localhost myorigin = localhost mydestination = localhost virtual_mailbox_domains = example.com virtual_uid_maps = static:2000 virtual_gid_maps = static:2000 virtual_mailbox_base = /var/vmail virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox_map virtual_alias_maps = hash:/etc/postfix/virtual_alias_map
Аутентификация SMTP-клиентов (Cyrus SASL)
Под клиентами в данном случае подразумевается MUA — например, Thunderbird или MS Outlook. MUA не должны отправлять почту через 25-ый порт — мы во время настройки будем нарушать это правило.
SASL означает «Simple Authentication and Security Layer» (простой уровень аутентификации и безопасности). Сам по себе SASL — это не более чем список требований к механизмам и протоколам аутентификации, чтобы быть совместимыми с SASL, как описано в RFC 4422.
Многие путают SASL с одной конкретной реализацией SASL — библиотекой Cyrus SASL. Например, Dovecot тоже имеет собственную реализацию SASL, которая может (когда-нибудь) быть отделена от самого Dovecot, чтобы «конкурировать» с библиотекой Cyrus SASL в качестве альтернативной реализации.
Postfix не реализует сам SASL, а вместо этого использует существующие реализации — Cyrus SASL или Dovecot SASL. Сейчас рассмотрим первый вариант, а второй — когда доберемся до Dovecot. Cyrus и Dovecot — это IMAP/POP3 почтовые серверы, которые обеспечивают работу почтовых клиентов MUA с почтовыми ящиками на сервере. Проверить, что Postfix поддерживает Cyrus SASL и/или Dovecot SASL — можно с помощью команды.
# postconf -a cyrus dovecot
Запуск демона saslauthd
Первым делом нам потребуется демон saslauthd, для этого нужно установить пакет sasl2-bin.
# apt install sasl2-bin
Демон saslauthd создает сокет в своем рабочем каталоге /var/run/saslauthd. Демон smtpd нуждается в доступе к этому сокету. Если smtpd работает в среде chroot, saslauthd также должен работать в этой среде chroot. Но есть и другие службы, которые ожидают сокет saslauthd в его «обычном» месте.
Рекомендуемый способ решения этой проблемы — запустить отдельные процессы для smtpd и для других. Для этого создаем отдельный файл в директории /etc/default (подробее см. в скрипте управления демоном /etc/init.d/saslauthd).
# cp /etc/default/saslauthd /etc/default/saslauthd-smtpd # nano /etc/default/saslauthd-smtpd
START = yes DESC = "SASL Authentication Daemon for Postfix" NAME = "saslauthd-smtpd" # максимум 15 символов # указываем демону, к какому бэкенду аутентификации обращаться для проверки пароля; # здесь возможны разные варианты, например — pam, shadow, ldap, getpwent и т.д. MECHANISMS = "sasldb" OPTIONS = "-c -m /var/spool/postfix/var/run/saslauthd"
Далее нужно создать директорию для сокета и установить на нее права, владельца и группу
# dpkg-statoverride --add root postfix 710 /var/spool/postfix/var/run/saslauthd
Добавляем пользователя postfix в группу sasl — для возможности чтения сокета
# usermod -a -G sasl postfix
/etc/default/saslauthd нет директивы START=yes — так что демон будет обслуживать только запросы на аутентификацию от Postfix, используя файл /etc/default/saslauthd-smtpd. Если в дальнейшем потребуется, чтобы демон обслуживал запросы на аутентификацию от других сервисов с настройками по умолчанию — нужно просто добавить директиву START=yes в файл /etc/default/saslauthd.
# service saslauthd restart # service saslauthd status ● saslauthd.service - LSB: saslauthd startup script Loaded: loaded (/etc/init.d/saslauthd; generated) Active: active (running) since Fri 2025-01-24 14:46:04 MSK; 3s ago Docs: man:systemd-sysv-generator(8) Process: 47924 ExecStart=/etc/init.d/saslauthd start (code=exited, status=0/SUCCESS) Tasks: 5 (limit: 1067) Memory: 2.9M CPU: 22ms CGroup: /system.slice/saslauthd.service ├─47944 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 ├─47945 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 ├─47946 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 ├─47947 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 └─47948 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5
saslauthd-smtpd.service, которая будет работать под управлением Systemd.
# nano /etc/default/saslauthd-smtpd # переменные окружения, доступные службе
START = yes DESC = "SASL Authentication Daemon for Postfix" NAME = "saslauthd-smtpd" # максимум 15 символов # указываем демону, к какому бэкенду аутентификации обращаться для проверки пароля; # здесь возможны разные варианты, например — pam, shadow, ldap, getpwent и т.д. MECHANISMS = "sasldb" MECH_OPTIONS = "" THREADS = 5 OPTIONS = "-c -m /var/spool/postfix/var/run/saslauthd"
# mkdir -p /var/spool/postfix/var/run/saslauthd # директория сокета, чтобы Postfix имел доступ # chmod 710 /var/spool/postfix/var/run/saslauthd # права доступа для директории сокета # chown :sasl /var/spool/postfix/var/run/saslauthd # группа для директории сокета # usermod -a -G sasl postfix # добавляем пользователя postfix в группу sasl
# nano /etc/systemd/system/saslauthd-smtpd.service # создаем новый юнит Systemd
[Unit] Description=SASL Authentication Daemon for Postfix [Service] Type=forking PIDFile=/var/spool/postfix/var/run/saslauthd/saslauthd.pid EnvironmentFile=/etc/default/saslauthd-smtpd ExecStart=/usr/sbin/saslauthd -a $MECHANISMS $MECH_OPTIONS $OPTIONS -n $THREADS RuntimeDirectory=saslauthd [Install] WantedBy=multi-user.target
# systemctl daemon-reload # сообщаем Systemd о новом юните # systemctl enable saslauthd-smtpd.service # добавляем службу в автозагрузку # systemctl start saslauthd-smtpd.service # запускаем службу в работу # systemctl status saslauthd-smtpd.service ● saslauthd-smtpd.service - SASL Authentication Daemon for Postfix Loaded: loaded (/etc/systemd/system/saslauthd-smtpd.service; enabled; preset: enabled) Active: active (running) since Sat 2025-03-22 16:28:23 MSK; 5s ago Process: 45071 ExecStart=/usr/sbin/saslauthd -a $MECHANISMS $MECH_OPTIONS $OPTIONS -n $THREADS (code=exited, status=0/SUCCESS) Main PID: 45072 (saslauthd) Tasks: 5 (limit: 1090) Memory: 3.0M (peak: 3.5M) CPU: 20ms CGroup: /system.slice/saslauthd-smtpd.service ├─45072 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 ├─45073 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 ├─45074 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 ├─45075 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 └─45076 /usr/sbin/saslauthd -a sasldb -c -m /var/spool/postfix/var/run/saslauthd -n 5 .......... systemd[1]: Starting saslauthd-smtpd.service - SASL Authentication Daemon for Postfix... .......... saslauthd[45072]: : master pid is: 45072 .......... saslauthd[45072]: : listening on socket: /var/spool/postfix/var/run/saslauthd/mux .......... systemd[1]: Started saslauthd-smtpd.service - SASL Authentication Daemon for Postfix.
Конфигурация демона smtpd
Создаем файл конфигурации для демона smtpd
# nano /etc/postfix/sasl/smtpd.conf
# проверкой логина-пароля будет заниматься демон saslauthd, обращаясь к базе данных BerkeleyDB # (файл /etc/sasldb2, пароли без шифрования), который мы уже настроили и запустили pwcheck_method: saslauthd # список механизмов аутентификации, которые может предложить служба, заданная в pwcheck_method; # когда pwcheck_method имеет значение saslauthd — допускется использовать только PLAIN и LOGIN mech_list: PLAIN LOGIN
LOGIN отправялет логин и пароль как две отдельные строки. Механизм PLAIN отправляет логин и пароль одной строкой с разделителем.
Создание базы данных клиентов
Cyrus SASL поставляется с двумя плагинами — saslpasswd2 для управления пользователями и sasldblistusers2 для получения списка всех пользователей в базе данных BerkeleyDB (файл /etc/sasldb2, пароли без шифрования).
# saslpasswd2 -c -u $(postconf -h mydomain) evgeniy Password: qwerty Again (for verification): qwerty
# saslpasswd2 -u $(postconf -h mydomain) sergey Password: 123456 Again (for verification): 123456
# sasldblistusers2 evgeniy@example.com: userPassword sergey@example.com: userPassword
Проверяем SASL аутентификацию для пользователя evgeniy
# testsaslauthd -u evgeniy -p qwerty -f /var/spool/postfix/var/run/saslauthd/mux -r $(postconf -h mydomain) 0: OK "Success."
Наконец, редактируем файл конфигурации Postfix main.cf
# nano /etc/postfix/main.cf
# АУТЕНТИФИКАЦИЯ КЛИЕНТОВ С ИСПОЛЬЗОВАНИЕМ CYRUS SASL # Включаем аутентификацию клиентов с использованием Cyrus или Dovecot smtpd_sasl_auth_enable = yes # Может принимать значения cyrus или dovecot, сейчас используем cyrus smtpd_sasl_type = cyrus # Имя файла конфигурации демона smtpd, но без расширения .conf. Путь к # файлу конфигурации задается опцией cyrus_sasl_config_path, см.ниже smtpd_sasl_path = smtpd # Путь к файлу конфигурации smtpd.conf демона smtpd (который входит в # состав Postfix и работает под управлением демона master) cyrus_sasl_config_path = /etc/postfix/sasl # Добавлять доменное имя к логину клиента, который не имеет доменной # части, то есть преобразовать «username» в «username@example.com» smtpd_sasl_local_domain = $mydomain # Запрещаем анонимную аутентификацию клиентов на почтовом сервере smtpd_sasl_security_options = noanonymous
Если от клиента получен логин типа username — Postfix может его дополнить до username@example.com с помощью директивы smtpd_sasl_local_domain. Демон saslauthd тоже может дополнить логин, если его запустить с опцией -x example.com.
Для аутентификации клиента сервер Postfix отправляет Cyrus SASL область аутентификации REALM вместе с логином и паролем клиента. Простые механизмы SASL (PLAIN, LOGIN) — используют только имя пользователя (чаще всего это адрес почты). Сложные механизмы SASL (DIGEST-MD5, CRAM-MD5) предназначены обслуживать несколько независимых «областей» пользователей. В качестве области аутенификации удобно использовать домен, в нашем случае example.com.
При создании базы данных логинов-паролей клиентов с помощью утилиты saslpasswd2 — область задается при помощи опции -u. При проверке SASL аутентификации с использованием утилиты testsaslauthd — область задается при помощи опции -r.
Даем указание Postfix перечитать файлы конфигурации
# systemctl reload postfix.service
Проверка логина и отправителя
Директива smtpd_sender_login_maps используется для проверки соответствия между аутентифицированным пользователем (SASL login username) и адресом отправителя, указанным в команде MAIL FROM во время SMTP-сессии. Эта директива работает только в паре с ограничением reject_sender_login_mismatch — которое тоже нужно добавить.
# nano /etc/postfix/main.cf
# Проверка соответствия между аутентифицированным пользователем и адресом # отправителя, указанным в команде MAIL FROM во время SMTP-сессии smtpd_sender_login_maps = hash:/etc/postfix/sender_login_map # Ограничения на этапе команды MAIL FROM smtpd_sender_restrictions = # Отклонять почту от отправителей с неполным доменным именем reject_non_fqdn_sender # Отклонять почту от отправителей из несуществующих доменов reject_unknown_sender_domain # Разрешить клиентов, прошедших аутентификацию по RFC 4954 permit_sasl_authenticated # Отклонять, если логин не соответствует отправителю в MAIL FROM reject_sender_login_mismatch # Разрешить клиентов из доверенных сетей (опция $mynetworks) permit_mynetworks # Запросить сервер, обслуживающий указанный адрес отправителя, # на предмет существования на нём пользователя с этим адресом reject_unverified_sender
Создаем карту соответствия между адресом почты отправителя и SASL-логином
# nano /etc/postfix/sender_login_map
# Адрес отправителя (MAIL FROM) Список разрешенных SASL-логинов через запятую
evgeniy@example.com evgeniy
sergey@example.com sergey
# postmap hash:/etc/postfix/sender_login_map
Отправка тестового сообщения
Нужно отправить сообщение с ip-адреса 101.101.101.101, который не входит в доверенную сеть. И сообщение должно быть отправлено получателю, для которого наш сервер не является местом назначения. Мы уже знаем, что в этом случае сработает правило reject_unauth_destination — которое отбросит такое сообщение. Но выше расположено правило permit_sasl_authenticated — которое разрешит принять сообщение от аутентифицированного пользователя.
$ echo -ne '\0evgeniy\0qwerty' | base64 AGV2Z2VuaXkAcXdlcnR5 $ telnet mail.example.com 25 Trying 222.222.222.222... Connected to mail.example.com. Escape character is '^]'. 220 mail.example.com ESMTP Postfix EHLO [101.101.10.101] 250-mail.example.com 250-PIPELINING 250-SIZE 15728640 250-ETRN 250-AUTH PLAIN LOGIN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250-SMTPUTF8 250 CHUNKING AUTH PLAIN AGV2Z2VuaXkAcXdlcnR5 235 2.7.0 Authentication successful MAIL FROM: <evgeniy@example.com> 250 2.1.0 Ok RCPT TO: <somebody@mail.ru> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> Hello, Somebody! . 250 2.0.0 Ok: queued as 4150C21500 QUIT 221 2.0.0 Bye
Посмотрим, что у нас в логе почтового сервера
# cat /var/log/mail.log
... postfix/smtpd[60464]: connect from unknow [101.101.101.101]
... postfix/smtpd[60464]: 4150C21500: client=unknown[101.101.101.101], sasl_method=PLAIN,
sasl_username=evgeniy@example.com
... postfix/cleanup[60467]: 4150C21500: message-id=<>
... postfix/qmgr[60240]: 4150C21500: from=<evgeniy@example.com>, size=201, nrcpt=1 (queue active)
... postfix/smtpd[60464]: disconnect from unknown[101.101.101.101] ehlo=1 auth=1 mail=1 rcpt=1 data=1 commands=5
... postfix/smtp[60468]: 4150C21500: to=<somebody@mail.ru>, relay=mxs.mail.ru[94.100.180.31]:25, delay=1.5,
delays=0.07/1/0.04/0.36, dsn=5.0.0, status=bounced (host mxs.mail.ru[94.100.180.31] said: 550 Message
was not accepted -- invalid mailbox. Local mailbox somebody@mail.ru is unavailable: user is terminated
(in reply to end of DATA command))
... postfix/cleanup[60467]: B85FB21591: message-id=<20250323091924.B85FB21591@mail.example.com>
... postfix/bounce[60469]: 4150C21500: sender non-delivery notification: B85FB21591
... postfix/qmgr[60240]: B85FB21591: from=<>, size=2405, nrcpt=1 (queue active)
... 2283199-evgeniy345 postfix/qmgr[60240]: 4150C21500: removed
... postfix/local[60470]: B85FB21591: to=<evgeniy@example.com>, relay=local, delay=0.03, delays=0/0.01/0/0.02,
dsn=2.0.0, status=sent (delivered to maildir)
... postfix/qmgr[60240]: B85FB21591: removed
Наш почтовый сервер принял сообщение и попробовал его отправить на почтовый сервер mxs.mail.ru — тот ответил отказом. Тогда Postfix создал новое почтовое сообщение для evgeniy@example.com о неудачной попытке доставки. После этого менеджер очередей postfix/qmgr удалил сообщение 4150C21500 из очереди. А вот сообщение B85FB21591 с отчетом о неудачной доставке было доставлено в ящик пользователя evgeniy. После этого менеджер очередей postfix/qmgr удалил сообщение B85FB21591 из очереди.
Использование плагина
Главный недостаток описанной выше аутентификации — логин и пароль почтовый клиент отправляет на сервер в открытом виде. Но есть альтернативный вариант — использовать плагины, которые входят в поставку Cyrus SASL. Мы будем использовать базу данных паролей BerkeleyDB — это файл /etc/sasldb2, куда мы записали логины и пароли двух пользователей.
Редактируем файл конфигурации демона smtpd
# nano /etc/postfix/sasl/smtpd.conf
pwcheck_method: auxprop # плагин поддерживает проверку паролей, сохраненных в файле /etc/sasldb2 (BerkeleyDB, без шифрования) auxprop_plugin: sasldb # список механизмов аутентификации, которые может предложить плагин, заданный в опции auxprop_plugin mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5
Редактируем файл конфигурации Postfix main.cf
# nano /etc/postfix/main.cf
# Запрещаем анонимную аутентификацию и использование PLAIN и LOGIN smtpd_sasl_security_options = noanonymous, noplaintext
При использовании плагина не нужно запускать службу saslauthd — демон smtpd будет сам обращаться к плагину для проверки логина-пароля. Но демон smtpd работает в среде chroot и не может оттуда получить доступ к базе данных паролей /etc/sasldb2 — давайте изменим это.
# nano /usr/lib/postfix/configure-instance.sh
FILES="etc/localtime etc/services etc/resolv.conf etc/hosts \ etc/host.conf etc/nsswitch.conf etc/nss_mdns.config \ $chroot_extra_files etc/sasldb2"
Теперь нужно перезагрузить службу Postfix (команды reload будет недостаточно)
# systemctl restart postfix.service
PLAIN и LOGIN, но когда мы настроим TLS/SSL шифрование — эти методы аутентификации можно будет использовать безопасно. Но в этом случае нужно для почтовых клиентов сделать обязательных использование TLS/SSL — за это отвечает опция smtpd_tls_auth_only.
Отправка тестового сообщения
При использовании механизма аутентфикации CRAM-MD5 пароль не передается по сети в открытом виде. Вместо этого сервер отправляет клиенту токен, клиент должен получить md5-сумму от токена и пароля. И отправить серверу свой логин и полученную md5-сумму. Сервер тоже вычисляет md5-сумму, используя токен и пароль клиента. Если md5-суммы совпадают — аутентификация пройдена.
- сервер отправляет клиенту токен
base64(token) - клиент вычисляет
md5_hash_client = md5(token + password) - клиент отправляет
base64(username md5_hash_client) - сервер вычисляет
md5_hash_server = md5(token + password) - успешно, если
md5_hash_client == md5_hash_server
Нам нужно взять токен от почтового сервера и сформировать ответ для отправки на сервер. Другими словами, нам нужно два терминала — на одном будем выполнять команду telnet, на другом — запускать скрипт для формирования ответа.
$ telnet mail.example.com 25 # первый терминал Trying 222.222.222.222... Connected to mail.example.com. Escape character is '^]'. 220 mail.example.com ESMTP Postfix EHLO [101.101.10.101] 250-mail.example.com 250-PIPELINING 250-SIZE 15728640 250-ETRN 250-AUTH CRAM-MD5 DIGEST-MD5 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250-SMTPUTF8 250 CHUNKING AUTH CRAM-MD5 334 PDMxMjcwMz.....5vbmxpbmU+ # сообщение от сервера ZXZnZW5peS.....c1ZDRhMQ== # наш ответ серверу 235 2.7.0 Authentication successful QUIT 221 2.0.0 Bye
$ /path/to/cram-md5.sh evgeniy qwerty PDMxMjcwMz.....5vbmxpbmU+ # второй терминал ZXZnZW5peS.....c1ZDRhMQ== # наш ответ серверу
Скрипт для формирования ответа почтовому серверу
#!/bin/bash USERNAME=$1 PASSWORD=$2 CHALLENGE=$3 CHALLENGE=$(echo $CHALLENGE | base64 -d) HMAC_HEX=$(printf '%s' "$CHALLENGE" | openssl dgst -md5 -mac HMAC -macopt key:$PASSWORD | awk '{print $2}') RESPONSE="$USERNAME $HMAC_HEX" RESPONSE=$(echo -n "$RESPONSE" | base64) echo $RESPONSE
Добавляем TLS/SSL шифрование
Чтобы защитить данные, которые клиент отправляет серверу — нужно добавить шифрование (особенно при использовании аутентификации PLAIN и LOGIN). Для этого на почтовом сервере получаем TLS сертификат и редактируем файл конфигурации main.cf.
Сертификат Let's Encrypt
Чтобы получить сертификат Let's Encrypt — устанавливаем пакетcertbot (подробнее см. здесь).
# apt install certbot
Плагин standalone позволяет запустить встроенный в certbot веб-сервер, чтобы ответить на http-запросы проверки принадлежности домена. Если уже есть работающий на 80-м порту веб-сервер — то его нужно остановить на время получения сертификата. В этом помогут хуки хук pre-hook и post-hook.
Регистрируем аккаунт в Let's Encrypt
# certbot register --email somebody@yandex.ru Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.4-April-3-2024.pdf. You must agree in order to register with the ACME server. Do you agree? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: Y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing, once your first certificate is successfully issued, to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: N Account registered.
На первом шаге запрашивается согласие с условиями использования сервиса Let's Encrypt, с которыми можно предварительно ознакомиться по предложенному адресу. На втором шаге предлагается выразить согласие на получение новостной рассылки от разработчиков certbot.
Получаем сертификат для домена mail.example.com
# certbot certonly --standalone -d mail.example.com Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for mail.example.com Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/mail.example.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/mail.example.com/privkey.pem This certificate expires on 2025-05-04. These files will be updated when the certificate renews. Certbot has set up a scheduled task to automatically renew this certificate in the background.
Теперь в директории /etc/letsencrypt/live/mail.example.com есть четыре файла (на самом деле — символические ссылки)
cert.pem— собственно, сертификат для доменаmail.example.comchain.pem— цепочка доверия, включает корневой и промежуточный сертификатыfullchain.pem— полная цепочка, включает в себяcert.pemиchain.pemprivkey.pem— закрытый ключ сертификата, данный файл является секретным
Файл конфигурации main.cf
Прописать в файле конфигурации приватный ключ и сертификат можно с помощью одной опции — если предварительно записать ключ и цепочку сертификатов в один файл.
# Приватный ключ + сертификат почтового сервера + сертификат вышестоящего CA, # который подписал сертификат сервера и которому доверяют почтовые клиенты smtpd_tls_chain_files = /etc/letsencrypt/live/mail.example.com/privkey-fullchain.pem
Но у нас будет работать регламентное задание, которое обновляет сертификаты Let's Encrypt — при этом certbot ожидает найти определенные файлы в определенном месте. А создавать отдельное регламентное задание, которое будет дополнительно объединять приватный ключ и цепочку сертификатов в один файл — не очень неудобно. Поэтому будем использовать две опции.
# TLS/SSL ПРИ ПОЛУЧЕНИИ СООБЩЕНИЙ ПО SMTP-ПРОТОКОЛУ (МЫ — SMTP-СЕРВЕР) # Приватный ключ + сертификат почтового сервера + сертификат вышестоящего # CA, который подписал сертификат и которому доверяют почтовые клиенты smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem # Уровень подробности логирования при использовании TLS/SSL шифрования smtpd_tls_loglevel = 1 # Включить информацию о протоколе и используемом шифре, а также CommonName # клиента и эмитента в заголовок сообщения Received (полезно при отладке) smtpd_tls_received_header = yes # Опция предписывает Postfix сообщать удаленным SMTP-клиентам о поддержке # STARTTLS, но не требует от клиентов использования шифрования TLS/SSL. Если # установить значение «encrypt» — Postfix не будет принимать сообщения без # шифрования, для публичного сервера такое поведение нежелательно (RFC 2487). smtpd_tls_security_level = may # Postfix поддерживает возобновление сеанса TLS RFC 5077, если клиент SMTP # также поддерживает RFC 5077. Это экономит ресурсы и сокращает задержку. # Значение ноль отключает кэширование. smtpd_tls_session_cache_timeout = 3600s # Аутентфикация SASL возможна только при использовании TLS/SSL шифрования smtpd_tls_auth_only = yes
Сервер Postfix может выступать как в роли SMTP-сервера, так и в роли SMTP-клиента. Рекомендуемая настройка для клиента — оставить значения по умолчанию. Для этого — закомментировать указанные ниже опции, если они присутствуют в файле конфигурации.
# TLS/SSL ПРИ ОТПРАВКЕ СООБЩЕНИЙ ПО SMTP-ПРОТОКОЛУ (МЫ — SMTP-КЛИЕНТ) # Удалить или закомментировать, чтобы использовать значения по умолчанию smtp_tls_cert_file = smtp_tls_dcert_file = smtp_tls_key_file = smtp_tls_dkey_file = smtp_tls_eccert_file = smtp_tls_eckey_file = smtp_tls_chain_files =
Значение may для опций smtpd_tls_security_level и smtp_tls_security_level означает, что шифрование TLS/SSL является оппортунистическим. Другими словами, транзакция SMTP шифруется, если другая сторона поддерживает функцию STARTTLS. В противном случае данные передаются без шифрования.
Теперь, когда все данные шифруются при передаче — можем разрешить PLAIN и LOGIN
# nano /etc/postfix/main.cf
# Запрещаем анонимную аутентификацию, разрешаем использовать PLAIN и LOGIN smtpd_sasl_security_options = noanonymous
Даем указание Postfix перечитать файлы конфигурации
# systemctl reload postfix.service
Проверка TLS/SSL сертификата
Давайте отправим сообщение, используя утилиту openssl — как раньше использовали telnet. Отправлять будем с доверенного ip-адреса 123.123.123.123 без аутентификации — чтобы немного упростить задачу.
$ openssl s_client -connect mail.example.com:25 -starttls smtp -crlf -quiet depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1 verify return:1 depth=1 C = US, O = Let's Encrypt, CN = R11 verify return:1 depth=0 CN = mail.example.com verify return:1 250 CHUNKING helo [123.123.123.123] 250 mail.example.com mail from: <evgeniy@example.com> 250 2.1.0 Ok rcpt to: <somebody@mail.ru> 250 2.1.5 Ok data 354 End data with <CR><LF>.<CR><LF> Hello, Somebody! . 250 2.0.0 Ok: queued as 8B6AFF60 quit 221 2.0.0 Bye
Хотя сертфикат обновляется автоматически — лучше не пускать это дело на самотек, а проверять сертификат самостоятельно. Давайте напишем скрипт, который будет проверять сертфикат и сообщать в телеграм, когда заканчивается срок действия.
#!/bin/bash TELEGRAM_API_KEY='.........' TELEGRAM_CHAT_ID='.........' SERVER_HOST='mail.example.com' # Порт для проверки SSL (465 для SMTPS, 587 для SMTP+STARTTLS) SERVER_PORT='587' # Для STARTTLS нужно добавить -starttls smtp к команде openssl START_TLS='' if [[ $SERVER_PORT == '587' ]]; then START_TLS='-starttls smtp' fi function telegram { curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_API_KEY/sendMessage" \ -d chat_id="$TELEGRAM_CHAT_ID" \ -d text="${1}" > /dev/null } # Получаем информацию о сертификате с помощью openssl tmp=$(printf '' | openssl s_client -connect $SERVER_HOST:$SERVER_PORT $START_TLS -servername $SERVER_HOST 2>/dev/null) cert_not_after=$(echo "$tmp" | openssl x509 -noout -enddate 2>/dev/null) if [[ -z "$cert_not_after" ]]; then telegram "CRITICAL! Не удалось получить SSL-сертификат для ${SERVER_HOST}:${SERVER_PORT}" exit 1 fi # Убираем из "notAfter=Month Day HH:MM:SS YYYY GMT" лишнее expire_date_string=$(echo "$cert_not_after" | sed 's/notAfter=//') if [[ -z "$expire_date_string" ]]; then telegram "CRITICAL! Не удалось извлечь дату истечения SSL-сертификата для ${SERVER_HOST}" exit 1 fi # Преобразуем дату истечения в формат TIMESTAMP и YYYY-MM-DD expire_timestamp=$(date --date="$expire_date_string" "+%s" 2>/dev/null) if [[ $? -ne 0 ]]; then telegram "CRITICAL! Не удалось распознать дату истечения SSL-сертификата для ${SERVER_HOST}" exit 1 fi expire_formatted=$(date -d "@$expire_timestamp" "+%Y-%m-%d") # Текущая дата в формате TIMESTAMP и в формате YYYY-MM-DD current_timestamp=$(date "+%s") current_formatted=$(date -d "@$current_timestamp" "+%Y-%m-%d") # Проверяем, не истек ли срок действия сертификата if [[ "$expire_timestamp" -lt "$current_timestamp" ]]; then telegram "DANGER! Срок действия SSL-сертификата для ${SERVER_HOST} закончился ${expire_formatted}" exit 1 fi # Количество дней, которые остались до завершения сертификата (( diff_expire_secs = expire_timestamp - current_timestamp )) (( diff_expire_days = diff_expire_secs / 86400 )) if (( diff_expire_days < 10 )); then telegram "WARNING! До окончания SSL-сертификата для ${SERVER_HOST} осталось ${diff_expire_days} дней" fi # Для отладки можно вывести информацию, если все в порядке debug="SSL-сертификат для ${SERVER_HOST} истекает ${expire_formatted}, осталось ${diff_expire_days} дней" # echo $debug # telegram "$debug" exit 0
Настройка MSA (Message submission agent)
Postfix демон submission
Порт 25 предназначен только для ретрансляции почты, т.е. взаимодействия между почтовыми серверами (Mail Transport Agent, MTA). Для взаимодейтсвия с почтовыми клиентами (Mail User Agent, MUA) — нужно дать указание демону master, чтобы он запустил демона submission (MSA). Демон submission — это все тот же smtpd, только на порту 587 и запущенный с другими настройками.
# nano /etc/postfix/main.cf
# ОГРАНИЧЕНИЯ НА ПРИЕМ ПОЧТЫ ДЕМОНАМИ SUBMISSION(S) # ОТ «СВОИХ» ПОЧТОВЫХ КЛИЕНТОВ, ПОРТ 587 ИЛИ 465 # Ограничения на этапе установке подключения submission_client_restrictions = permit_sasl_authenticated reject # Ограничения на этапе команды HELO/EHLO submission_helo_restrictions = # Ограничения на этапе команды MAIL FROM submission_sender_restrictions = reject_sender_login_mismatch reject_non_fqdn_sender reject_unknown_sender_domain permit_sasl_authenticated reject # Ограничения на этапе команды RCPT TO (relay) submission_relay_restrictions = permit_sasl_authenticated reject # Ограничения на этапе команды RCPT TO (spam) submission_recipient_restrictions = reject_non_fqdn_recipient reject_unknown_recipient_domain permit_sasl_authenticated reject_unauth_destination reject
# nano /etc/postfix/master.cf
# Choose one: enable submission for loopback clients only, or for any client. #127.0.0.1:submission inet n - y - - smtpd submission inet n - y - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_tls_auth_only=yes -o smtpd_client_restrictions=$submission_client_restrictions -o smtpd_helo_restrictions=$submission_helo_restrictions -o smtpd_sender_restrictions=$submission_sender_restrictions -o smtpd_relay_restrictions=$submission_relay_restrictions -o smtpd_recipient_restrictions=$submission_recipient_restrictions
Даем указание Postfix перечитать файлы конфигурации
# systemctl reload postfix.service
Мы здесь переопределяем директивы конфигурации, заданые глобально в файле main.cf, используя переменные $submission_xxxxx_restrictions. Правило permit_mynetworks для ограничений smtpd_xxxxx_restrictions не используется — только аутентификация, никаких доверенных сетей.
Теперь можем убрать правила permit_sasl_authenticated и permit_mynetworks для демона smtpd на 25-ом порту, который принимает сообщения от других MTA. Другие MTA не могут быть из нашей доверенной сети и они не должны проходить процедуру аутентификации.
# nano /etc/postfix/main.cf
# ОГРАНИЧЕНИЯ НА ПРИЕМ ПОЧТЫ ОТ ДРУГИХ MTA НА 25-ОМ ПОРТУ # Ограничения на этапе установке подключения smtpd_client_restrictions = # Отклонять клиентов, у которых доменное имя из PTR-записи # не разрешается в тот же ip-адрес по A-записи reject_unknown_client_hostname # Ограничения на этапе команды HELO/EHLO smtpd_helo_restrictions = # Отклонять клиентов, использующих неправильный синтаксис домена reject_invalid_helo_hostname # Отклонять клиентов, указывающих не полное доменное имя FQDN reject_non_fqdn_helo_hostname # Отклонять клиентов, ДНС-имя которых из команды HELO/EHLO не # имеет A- или MX-записи или ip-адрес не является правильным reject_unknown_helo_hostname # Ограничения на этапе команды MAIL FROM smtpd_sender_restrictions = # Отклонять почту от отправителей с неполным доменным именем reject_non_fqdn_sender # Отклонять почту от отправителей из несуществующих доменов reject_unknown_sender_domain # Запросить сервер, обслуживающий указанный адрес отправителя, # на предмет существования на нём пользователя с этим адресом reject_unverified_sender # Ограничения на этапе команды RCPT TO (relay) smtpd_relay_restrictions = # Отклонять почту, если домена получателя нет в $mydestination, # $virtual_alias_domains, $virtual_mailbox_domains, $relay_domains reject_unauth_destination # Отклонять почту для получателя, если наш сервер не является # конечной точкой, а домен получателя не имеет A- и MX-записей reject_unknown_recipient_domain # Ограничения на этапе команды RCPT TO (spam) smtpd_recipient_restrictions = # Отклонять почту для получателей с неполным доменным именем reject_non_fqdn_recipient # Всегда принимать почту для учетных записей postmaster и abuse check_recipient_access hash:/etc/postfix/permit_recipients # Отклонять почту на адреса, для которых нет почтовых ящиков reject_unlisted_recipient
Давайте отправим сообщение с ip-адреса 123.123.123.123 на порт 587 — требуется обязательная аутентификация
$ echo -ne '\0evgeniy\0qwerty' | openssl base64 AGV2Z2VuaXkAcXdlcnR5 $ openssl s_client -connect mail.example.com:587 -starttls smtp -quiet depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1 verify return:1 depth=1 C = US, O = Let's Encrypt, CN = R11 verify return:1 depth=0 CN = mail.example.com verify return:1 250 CHUNKING ehlo [123.123.123.123] 250-mail.example.com 250-PIPELINING 250-SIZE 10240000 250-ETRN 250-AUTH PLAIN LOGIN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250-SMTPUTF8 250 CHUNKING auth plain AGV2Z2VuaXkAcXdlcnR5 235 2.7.0 Authentication successful mail from: <evgeniy@example.com> 250 2.1.0 Ok rcpt to: <somebody@mail.ru> 250 2.1.5 Ok data 354 End data with <CR><LF>.<CR><LF> Hello, Somebody! . 250 2.0.0 Ok: queued as 075EF144C quit 221 2.0.0 Bye
Теперь попробуем отправить сообщение с ip-адреса 123.123.123.123 на порт 25 — правила permit_sasl_authenticated и permit_mynetworks больше не действуют, потому что мы их удалили. Наш почтовый сервер на 25-ом порту ожидает соединений от других почтовых серверов — и применяет большой набор правил, прежде чем принять сообщение.
Первым правилом в этом наборе будет reject_unknown_client_hostname — ip-адрес клиента должен резолвится в имя через PTR-запись, а имя через A-запись должно резолвится в тот же ip-адрес.
Второе правило в этом наборе — reject_unknown_helo_hostname. Если в команде HELO/EHLO мы представились как smtp.mail.ru — то для этого доменного имени должна быть A-запись. Если в команде HELO/EHLO мы представились как [123.123.123.123] — то 123.123.123.123 должен быть синтаксически корректным ip-адресом.
HELO/EHLO допускает любой ip-адрес или любое имя, например yandex.ru или google.com. Эти две проверки — достаточно формальные, обойти их не представляет труда.
Следующее правило будет reject_unknown_sender_domain — домен отправителя из команды MAIL FROM должен иметь MX-запись или A-запись.
Следующее правило reject_unverified_sender требует убедиться в существовании пользователя с этим адресом. Наш сервер открывает встречную SMTP-сессию, пытаясь отправить письмо по адресу отправителя.
mail.ru или yandex.ru. Так что эти две проверки — тоже достаточно формальные, обойти их не представляет труда.
Следующее правило reject_unauth_destination не сработает, если будем отправлять на username@example.com — это «свой» домен. Но сработает, если будем отправлять на username@gmail.com — это «чужой» домен.
reject_unauth_destination — самое важное и строгое, не позволяет всем желающим отправлять сообщения через наш почтовый сервер на любые адреса. Только «свои» почтовые клиенты и только через демона submission могут отправлять сообщения на любые адреса.
Следующее правило reject_unknown_recipient_domain — домен получателя из команды RCPT TO должен иметь MX-запись или A-запись. Если будем отправлять сообщение для username@example.com — то правило не сработает, это «свой» домен. Но сработает, если домен «чужой» и ДНС-записи не найдены.
Правило reject_unlisted_recipient сработает, если будем отправлять сообщение для username@example.com, но пользователя username нет в файле /etc/passwd или в карте поиска $aliases.
Наш почтовый сервер будет принимать сообщения для «своего» домена example.com — при условии, что почтовый ящик получателя существует. И будет отвергать попытки переслать сообщение на «чужой» домен — будет срабатывать правило reject_unauth_destination. И даже если пройти аутентификацию — это не поможет, правило permit_sasl_authenticated мы удалили.
$ openssl s_client -connect mail.example.com:25 -starttls smtp -quiet depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1 verify return:1 depth=1 C = US, O = Let's Encrypt, CN = E6 verify return:1 depth=0 CN = mail.example.com verify return:1 250 CHUNKING ehlo [123.123.123.123] 250-mail.example.com 250-PIPELINING 250-SIZE 15728640 250-ETRN 250-AUTH PLAIN LOGIN CRAM-MD5 DIGEST-MD5 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250-SMTPUTF8 250 CHUNKING mail from: <evgeniy@example.com> 250 2.1.0 Ok rcpt to: <somebody@mail.ru> 454 4.7.1 <somebody@mail.ru>: Relay access denied quit 221 2.0.0 Bye
Postfix демон submissions
Протокол SMTPS на 465-ом порту в настоящее время не используется почтовыми серверами для взаимодействия друг с другом. Но зато этот протокол часто использует служба MSA, которая взаимодействует с почтовыми клиентами MUA. В этом случае служба MSA часто называется submissions (последняя s означает SSL/TLS).
Редактируем файл конфигурации master.cf — даем указание демону master, чтобы он запустил демона submissions.
# nano /etc/postfix/master.cf
# Choose one: enable submissions for loopback clients only, or for any client. #127.0.0.1:submissions inet n - y - - smtpd submissions inet n - y - - smtpd -o syslog_name=postfix/submissions -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes -o smtpd_client_restrictions=$submission_client_restrictions -o smtpd_helo_restrictions=$submission_helo_restrictions -o smtpd_sender_restrictions=$submission_sender_restrictions -o smtpd_relay_restrictions=$submission_relay_restrictions -o smtpd_recipient_restrictions=$submission_recipient_restrictions
С помощью директивы smtpd_tls_wrappermode мы включаем для протокола SMTP «TLS wrapper mode» (то есть SMTPS) вместо использования расширения STARTTLS.
# systemctl reload postfix.service
Что такое transport в Postfix?
Transport в Postfix — это способ доставки почтового сообщения до получателя, то есть выбор конкретного механизма как и куда отправить письмо.
В Postfix под транспортом обычно понимают «транспортный агент передачи сообщений», то есть компонент, который занимается передачей почты дальше — другому SMTP-серверу, локальному агенту доставки (LDA), по протоколу LMTP и т.п. Основные виды транспорта перечислены ниже.
smtp— доставка сообщений на внешний SMTP серверlocal— доставка в локальный почтовый ящикvirtual— используется для виртуальных пользователейlmtp— отправка через LMTP, наприемер к Dovecotpipe— отправка сообщения сторонней программе (скрипту)relay— для передачи сообщения через внешний relay-серверdiscard— удаление сообщения, используется для фильтрацииerror— принудительный возврат ошибки
Транспорт для почты определяется с помощью директивы transport_maps
transport_maps = hash:/etc/postfix/transport_map
Пример создания индексированной карты транспорта
# nano /etc/postfix/transport_map
# домен или адрес почты получателя —> какой транспорт использовать для доставки сообщения # письма для домена some-domain.com будут отправляться через указанный SMTP-сервер (relay) some-domain.com smtp:[smtp.other-domain.com] # письма для домена some-domain.local будут отправляться с использованием транспорта local some-domain.local local # письма для домена dovecot-one.local будут отправляться к Dovecot по LMTP через unix-сокет dovecot-one.local lmtp:unix:private/dovecot-lmtp # письма для домена dovecot-two.local будут отправляться к Dovecot по LMTP по сети, порт 24 dovecot-two.local lmtp:[127.0.0.1]:24 # использовать транспорт telegram (транспорт должен быть определен в /etc/postfix/master.cf) alert@example.com telegram
# postmap hash:/etc/postfix/transport_map
Когда Postfix получает письмо, он проверяет, какой транспорт применим к адресу получателя. Если находит домен или конкретный адрес в transport_maps — будет применён соответствующий транспорт. Если не находит — будет использован транспорт по умолчанию, который задается в local_transport, virtual_transport и default_transport.
# для адреса получателя, указанного в опции mydestination — будет использован транспорт local local_transport = local:$myhostname # для адреса получателя в virtual_mailbox_domains/virtual_alias_domains — будет использован транспорт virtual virtual_transport = virtual # для всех прочих адресов получателя — будет использован транспорт smtp default_transport = smtp
# postconf -d local_transport local_transport = local:$myhostname # postconf -d virtual_transport virtual_transport = virtual # postconf -d default_transport default_transport = smtp
Все транспорты — это демоны, которых запускает главный демон master, используя файл конфигурации /etc/postfix/master.cf. Имя демона — в первой колонке, команда на запуск — в последней колонке.
smtp unix - - y - - smtp relay unix - - y - - smtp -o syslog_name=postfix/$service_name -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 error unix - - y - - error discard unix - - y - - discard local unix - n n - - local virtual unix - n n - - virtual lmtp unix - - y - - lmtp
Опция mailbox_transport определяет транспорт доставки почты в почтовый ящик конкретного пользователя. Она позволяет переопределить значение local_transport и применяется на уровне каждого отдельного пользователя.
# для адреса получателя, указанного в mydestination — использовать транспорт local local_transport = local:$myhostname # для некоторых пользователей переопределить транспорт, заданный в local_transport mailbox_transport_maps = hash:/etc/postfix/mailbox_transport_map
Создание индексированной карты транспорта
# nano /etc/postfix/mailbox_transport_map
# Для пользователя admin отправлять сообщения в Dovecot по протоколу LMTP через unix-сокет, # доставкой сообщений будет заниматься Dovecot, а не локальный агент доставки Postfix local admin lmtp:unix:private/dovecot-lmtp # Для пользователя alert использовать транспорт telegram вместо транспорта local (транспорт # telegram должен быть определен файле конфигурации /etc/postfix/master.cf) alert telegram
# postmap hash:/etc/postfix/mailbox_transport_map
Опция mailbox_transport может быть задана глобально, переопределяя значение local_transport сразу для всех почтовых пользователей.
mailbox_transport = lmtp:unix:private/dovecot-lmtp
Мы до этого говорили только о почтовых ящиках пользователей операционной системы. Но есть вариант настройки Postfix, когда почтовые пользователи не являются пользователями Linux. И этот вариант настройки в настоящее время является основным. Для таких почтовых пользователей тоже можно переопределить транспорт по умолчанию.
Приходит почтовое сообщение
▼
Поиск транспорта через transport_maps
▼
Есть правило для домена/адреса?
▼
Да ┌─────────────────────────────────────────────────────────────┐ Нет
│ │
Использовать транспорт, Адрес попадает в какой-либо
указанный в transport_maps из внутренних доменов сервера?
│
Да ┌──────────────────────────────────┴─────────────────────┐ Нет
│ │
┌──────────────────────────┼───────────────────────────┐ Адрес попадает
mydestination (локальный) virtual_mailbox_domains virtual_alias_domains в relay_domains?
│ │ │ │
Использовать Использовать Использовать Да ┌──────────┴─────────┐ Нет
local_transport virtual_transport virtual_transport │ │
▼ ▼ ▼ │ │
┌─────────────────────────────────────────────────────────────────────────┐ Использовать Использовать
│ Внутри local_transport или virtual_transport │ relay_transport default_transport
│ │ │ (внешний адрес) (внешний адрес)
│ Существуют карты mailbox_transport_maps? │
│ │ │
│ Да ┌───────────────────┴─────────────────┐ Нет │
│ │ │ │
│ Использовать транспорт, указанный Использовать транспорт, указанный в │
│ в mailbox_transport_maps local_transport/virtual_transport │
└─────────────────────────────────────────────────────────────────────────┘
Настройка для виртуальных пользователей почтового сервера
# Домены виртуальных почтовых ящиков, сообщения для почтовых пользователей на # новых доменах example.net и example.org перенаправляются в почтовые ящики # /var/vmail/domain/username. Мы оставляем почтовые ящики пользователей ОС # на старом месте /var/mail/username myhostname = mail.example.com mydomain = example.com myorigin = $mydomain mydestination = $mydomain, localhost virtual_mailbox_domains = example.net, example.org virtual_uid_maps = static:2000 virtual_gid_maps = static:2000 virtual_mailbox_base = /var/vmail virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox_map
# для адреса получателя, чей домен указан в директиве mydestination — использовать транспорт local local_transport = local:$myhostname # для адреса получателя в virtual_mailbox_domains/virtual_alias_domains — использовать транспорт virtual virtual_transport = virtual # для некоторых пользователей переопределить транспорт, заданный в local_transport или virtual_transport mailbox_transport_maps = hash:/etc/postfix/mailbox_transport_map
Создание индексированной карты транспорта
# nano /etc/postfix/mailbox_transport_map
# Для доставки сообщений получателям evgeniy@example.com, evgeniy@example.net, # evgeniy@example.net — использовать транспорт telegram (транспорт должен быть # определен файле конфигурации /etc/postfix/master.cf) evgeniy telegram
# postmap hash:/etc/postfix/mailbox_transport_map
local и virtual. А может отправлять их Dovecot по протоколу LMTP — по сети либо через unix-сокет. И тогда распределением сообщений по почтовым ящикам пользователей будет заниматься Dovecot — этот вариант предпочтительнее.
Отправка сообщений в Телеграм
Рассмотрим небольшой пример работы с транспортом в Postfix — все сообщения для пользователей будем отправлять в группу Телеграм.
Для начала создаем новый транспорт telegram для доставки сообщений
# nano /etc/postfix/master.cf
telegram unix - n n - - pipe flags=RO user=nobody argv=/usr/local/bin/telegram.sh $sender $recipient $original_recipient
Теперь напишем bash-скрипт telegram.sh, который будет получать на stdin почтовое сообщение. В качестве параметров скрипту передаются адрес почты отправителя и адрес почты получателя.
# nano /usr/local/bin/telegram.sh
#!/bin/bash TELEGRAM_API_KEY='.........' TELEGRAM_CHAT_ID='.........' function telegram { curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_API_KEY/sendMessage" \ -d chat_id="$TELEGRAM_CHAT_ID" \ -d text="${1}" > /dev/null } # отправитель и получатель сообщения sender=$1 recipient=$2 original=$3 # почтовое сообщение с заголовками message=$(cat) # почтовое сообщение без заголовков content=$(echo "$message" | awk 'BEGIN {content=0} /^$/ {content=1; next} content==1 {print $0}') # отправляем в телеграм отправителя, получателя и тело сообщения telegram "Отправитель: $sender\nПолучатель: $original\nПеренаправлено: $recipient\n\n$content"
Изменяем владельца файла скрипта и предоставляем права на выполнение
# chown nobody:nogroup /usr/local/bin/telegram.sh # chmod 550 /usr/local/bin/telegram.sh
Редактируем файл конфигурации Postfix для добавления нового транспорта
# nano /etc/postfix/main.cf
telegram_destination_recipient_limit = 1 transport_maps = hash:/etc/postfix/transport_map
Создаем индексированную карту транспорта
# nano /etc/postfix/transport_map
example.com telegram
# postmap hash:/etc/postfix/transport_map
Отправим сообщение root (согласно /etc/aliases получит evgeniy)
$ sendmail -f noreply@example.com root@example.com Used space on hard disk partitions Root 45%, Home 43%, MySQL 51%, Logs 26%, Backup 65% Used inode on hard disk partitions Root 37%, Home 8%, MySQL 1%, Logs 1%, Backup 7% .
На нашем почтовом сервере есть только два администратора evgeniy и sergey — так что пересылка всех сообщений в группу Телеграм будет подходящим вариантом. Но если пользователей больше, то пересылать все подряд — будет не самым лучшим выбором. Однако, можно сделать пересылку сообщений только для некоторых пользователей — например, только для админов.
# nano /etc/postfix/transport_map
evgeniy@example.com telegram sergey@example.com telegram
# postmap hash:/etc/postfix/transport_map
Не обязательно задавать транспорт для доставки в карте transport_maps. Если Postfix не найдет транспорт в этой карте — будет использован транспорт по умолчанию. А транспорт по умолчанию можно задать самостоятельно. Или использовать карту mailbox_transport_maps, чтобы переопределить транспорт по умолчанию для конкретных пользователей.
# Первый вариант настройки пересылки в телеграм (для двух пользователей) telegram_destination_recipient_limit = 1 transport_maps = hash:/etc/postfix/transport_map
# Второй вариант настройки пересылки в телеграм (для всех пользователей) telegram_destination_recipient_limit = 1 #transport_maps = hash:/etc/postfix/transport_map local_transport = telegram
# Третий вариант настройки пересылки в телеграм (для всех пользователей) telegram_destination_recipient_limit = 1 #transport_maps = hash:/etc/postfix/transport_map #local_transport = telegram mailbox_transport = telegram
# Четвертый вариант настройки пересылки в телеграм (для двух пользователей) telegram_destination_recipient_limit = 1 #transport_maps = hash:/etc/postfix/transport_map #local_transport = telegram #mailbox_transport = telegram mailbox_transport_maps = hash:/etc/postfix/mailbox_transport_map
# nano /etc/postfix/mailbox_transport_map
evgeniy telegram sergey telegram
# postmap hash:/etc/postfix/mailbox_transport_map
pipe:/usr/local/bin/telegram.sh, когда используется встроенный транспорт pipe, в настоящее время устарела и не рекомендуется. Транспорт должен быть явно определен в файле /etc/postfix/master.cf, как мы это делали, создавая транспорт telegram.
Что такое сервер пересылки (relay)
Relay — это почтовый сервер, который принимает сообщения от одного отправителя и передаёт (пересылает) их дальше к другому серверу. Relay-сервер действует как промежуточное звено между исходным отправителем письма и получателем, то есть не является конечным пунктом доставки сообщения.
Основные задачи relay-сервера
- Принять письмо от почтового клиента или другого почтового сервера
- Определить маршрут дальнейшей доставки — другой relay или на сервер домена получателя
- Переслать сообщение на следующий сервер — другой relay или на сервер домена получателя
- При необходимости — добавить служебные заголовки для отслеживания пути сообщения
Поведение relay-сервера настраивается через директивы relay_domains, relay_recipient_maps и политики аутентификации клиентов. Директива relay_domains определяет домены, для которых пересылка разрешена. Директива relay_recipient_maps уточняет для этих доменов, какие конкретно адреса допустимы к обработке, обеспечивая более жёсткий контроль на уровне пользователей.
Транспорт для пересылки можно задать в карте transport_maps, если подходящий для домена транспорт не будет найдет, то будет использован транспорт по умолчанию relay_transport, который мо умолчанию имеет значение relay.
Пересылка для двух почтовых ящиков
Разрешаем пересылку на почтовый сервер, который обслуживает почтовый домен example.net, но только для двух почтовых адресов.
# Почтовые домены, для которых разрешается пересылка сообщений relay_domains = example.net # Не обязательно, но рекомендуется для явного указания next-hop transport_maps = hash:/etc/postfix/transport_map # На какие адреса и на какие домены можно или нельзя пересылать, # здесь можно настроить более точно, чем в relay_domains relay_recipient_maps = hash:/etc/postfix/relay_recipient_map # Ограничения на этапе команды RCPT TO (relay) smtpd_relay_restrictions = # Отклонять почту, если домена получателя нет в $mydestination, # $virtual_alias_domains, $virtual_mailbox_domains, $relay_domains reject_unauth_destination # Отклонять почту для получателя, если наш сервер не является # конечной точкой, а домен получателя не имеет A- и MX-записей reject_unknown_recipient_domain
# nano /etc/postfix/transport_map
# Разрешается пересылка только на почтовый сервер mail.example.net
example.net smtp:[mail.example.net]
# postmap hash:/etc/postfix/transport_map
# nano /etc/postfix/relay_recipient_map
# Разрешается пересылка только для двух почтовых ящиков
user-one@example.net PERMIT
user-two@example.net PERMIT
example.net REJECT
# postmap hash:/etc/postfix/relay_recipient_map
Пересылка от одного почтового сервера
Разрешаем пересылку на почтовый сервер, который обслуживает почтовый домен example.net, но пересылка разрешается только почтовому серверу mail.example.org
# Почтовые домены, для которых разрешается пересылка сообщений relay_domains = example.net # Не обязательно, но рекомендуется для явного указания next-hop transport_maps = hash:/etc/postfix/transport_map smtpd_recipient_restrictions = # Отклонять почту, если домена получателя нет в $mydestination, # $virtual_alias_domains, $virtual_mailbox_domains, $relay_domains reject_unauth_destination # Отклонять почту, если домен получателя не имеет A- и MX-записей reject_unknown_recipient_domain # Разрешаем пересылку только для почтового сервера mail.example.org check_client_access hash:/etc/postfix/check_relay_access
# nano /etc/postfix/transport_map
# Разрешается пересылка только на почтовый сервер mail.example.net
example.net smtp:[mail.example.net]
# postmap hash:/etc/postfix/transport_map
# nano /etc/postfix/check_relay_access
# Разрешается пересылка только от почтового сервера mail.example.org
mail.example.org PERMIT
# postmap hash:/etc/postfix/check_relay_access
Защита от спама в Postfix
Вариантов защиты от спама много, обычно достаточно одного или двух, чтобы не создавать избыточную нагрузку на почтовый сервер.
Postfix демон postscreen
Postfix демон postscreen предназначен для борьбы со спамом на раннем этапе подключения. Чтобы его активировать, нужно в файле конфигурации master.cf изменить порядок подключения клиентов к серверу. Входящее SMTP-соединение сначала передается демону postscreen и уже потом, после проверки клиента, передается демону smtpd.
# nano /etc/postfix/master.cf
# ========================================================================== # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (no) (never) (100) # ========================================================================== #smtp inet n - y - - smtpd smtp inet n - y - 1 postscreen smtpd pass - - y - - smtpd dnsblog unix - - y - 0 dnsblog tlsproxy unix - - y - 0 tlsproxy
dnsblog и tlsproxy делают postscreen более эффективным, потому что выполняют ресурсоемкие операции (DNS и TLS) отдельно от основной логики проверки на спам.
Директивы для управления демоном postscreen
# Включение DNSBL (черные списки) postscreen_dnsbl_action = enforce postscreen_dnsbl_sites = zen.spamhaus.org bl.spamcop.net postscreen_dnsbl_threshold = 1 # Включение теста SMTP Greeting Delay postscreen_greet_action = enforce postscreen_greet_wait = 6s # Сохранение истории ip-адресов postscreen_cache_map = btree:$data_directory/postscreen_cache postscreen_cache_cleanup_interval = 12h postscreen_cache_retention_time = 7d # Реакция на нарушение протокола postscreen_pipelining_action = enforce postscreen_non_smtp_command_action = drop postscreen_bare_newline_action = drop
Черные списки ip-адресов DNSBL
DNSBL (Domain Name System Blacklist) — это чёрный список ip-адресов, замеченных за рассылкой спама. Когда почтовый сервер получает сообщение, он проверяет наличие ip-адреса отправителя в этом списке с помощью DNS-запроса. Если адрес присутствует, письмо можно отклонить или пометить как спам.
# Ограничения на этапе команды RCPT TO (spam) smtpd_recipient_restrictions = # Отклонять почту для получателей с неполным доменным именем reject_non_fqdn_recipient # Всегда принимать почту для учетных записей postmaster и abuse check_recipient_access hash:/etc/postfix/permit_trusted_recipients # Отклонять почту на адреса, для которых нет почтовых ящиков reject_unlisted_recipient # Отклонять клиентов, которые есть в черных списках DNSBL reject_rbl_client zen.spamhaus.org reject_rbl_client bl.spamcop.net
Если уже настроен и запущен демон postscreen с проверкой по черным спискам ip-адресов — то повторная проверка не имеет смысла.
Внутренние фильтры Postfix
В Postfix двухуровневая система фильтрации, директивы smtp_xxxxx_checks — первая линия обороны, директивы xxxxxx_checks — вторая линия обороны. Директивы smtp_xxxxx_checks выполняются демоном smtpd во время SMTP-сессии, до того момента, как сообщение полностью принято. Директивы xxxxxx_check выполняются демоном cleanup после того, как сообщение принято, но до постановки в очередь.
Тут важно понимать, как сообщения попадают в Postfix. Это может быть сообщение, полученное демоном smtpd от МТА (другой почтовый сервер). Это может быть сообщение, полученое демоном submission(s) (демон smtpd с другими настройками) от MUA («свой» почтовый клиент). Кроме того, сообщение может быть создано локально без SMTP-транзакции — системным процессом или командой sendmail.
Проверять на спам нужно как сообщения, полученные от других почтовых серверов, так и сообщения, полученные от «своих» почтовых клиентов. Зачем проверять «своих»? Потому что могла быть утечка пароля или заражение вирусом компьютера пользователя. Но действия для «своих» и для «чужих» может быть разной — например, «своему» клиенту нужно поменять пароль и проверить компьютер на вирусы.
Когда демон smtpd или submission(s) принимает сообщение от SMPT-клиента, оно может быть проверено с использованием директив smtp_xxxxx_checks. После этого сообщение попадает к демону cleanup, где оно может быть проверено еще раз с использованием директив xxxxxx_checks.
smtp_xxxxx_checks используются редко, обычно для настройки достаточно использовать xxxxx_checks. Однако, smtp_xxxxx_checks предоставляют важное преимущество — возможность отклонить нежелательное письмо на самой ранней стадии, еще во время SMTP-сессии.
Внутренние фильтры и SpamAssassin
При подключении SpamAssassin через content_filter (см. ниже) — сперва сработают проверки smtp_xxxxx_checks, потом сообщение отправляется для анализа на спам, потом передаётся обратно в Postfix через утилиту sendmail (re-injection). От утилиты sendmail сообщение через pickup попадет к демону cleanup, где сработают проверки xxxxx_checks.
Директивы конфигурации xxxxx_checks, заданные глобально в файле конфигурации /etc/postfix/main.cf, будут действовать как для демона smtpd на 25-ом порту, так и для демонов submission(s). Это значит, что для «своих» и «чужих» сообщений применяется одна политика, которую мы определим в картах поиска xxxxx_checks.
Но для демонов submission(s) можно задать другие значения директив xxxxx_checks в файле конфигурации /etc/postfix/master.cf. Тогда для сообщений, принятых демоном smtpd на 25-ом порту, будут действовать глобальные значения. А для сообщений, принятых демонами submission(s), будут дейстововать локальные значения.
# Отправлять сообщения на внешний фильтр для проверки на спам content_filter = spam_filter # Отключаем преобразования адреса получателя, чтобы фильтр мог # проверить оригинальное сообщение, до любых изменений. receive_override_options = no_address_mappings # Проверять заголовки почтовых сообщений, первая линия обороны. Выполняет демон # smtpd во время SMTP-сессии, до того момента, как сообщение полностью принято. smtp_header_checks = # Проверять заголовки почтовых сообщений, вторая линия обороны. Выполняет демон # cleanup после того, как сообщение принято, но до постановки в очередь. header_checks = pcre:/etc/postfix/header_checks
# Проверять сообщения на спам и отправлять обратно в Postfix через sendmail spam_filter unix - n n - - pipe user=spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -i -f $sender $recipient # Choose one: enable submission for loopback clients only, or for any client. #127.0.0.1:submission inet n - y - - smtpd submission inet n - y - - smtpd .......... -o smtp_header_checks= # Для «своих» почтовых клиентов, которые прошли аутентификацию -o header_checks=pcre:/etc/postfix/msa_header_checks # Choose one: enable submissions for loopback clients only, or for any client. #127.0.0.1:submissions inet n - y - - smtpd submissions inet n - y - - smtpd .......... -o smtp_header_checks= # Для «своих» почтовых клиентов, которые прошли аутентификацию -o header_checks=pcre:/etc/postfix/msa_header_checks
При подключении SpamAssassin через smtpd_milters (см. ниже) — демон smtpd передает сообщение cleanup, где срабатывают проверки xxxxx_checks. После этого сообщение передается фильтрам, указанным в директиве smtpd_milters. После обработки фильтрами — сообщение возвращется демону smtpd, который помещает его в очередь incoming.
Заголовки X-Spam-Status, X-Spam-Level, X-Spam-Score, добавленные SpamAssassin, не могут быть «отловлены» в header_checks — их еще нет в момент проверки. В этом случае, отреагировать на заголовки может только сам милтер, когда получает сообщение обратно от SpamAssassin.
При этом не забываем, что демоны submission(s) — это все тот же smtpd, но на другом порту и с другими настройками. Все сказанное выше для демона smtpd — будет справедливо и для submission(s). Отреагировать на заголовки, добавленные SpamAssassin, может только сам милтер.
# Применять фильтр для сообщений, которые попадают в Postfix через демона smtpd smtpd_milters = unix:spamass/spamass.sock # Применять фильтр для писем, которые попадают в Postfix не через демона smtpd non_smtpd_milters = $smtpd_milters
# Запускать от имени пользователя spamass-milter, игнорировать письма с localhost (127.0.0.1) OPTIONS="-u spamass-milter -i 127.0.0.1" # Отклонять сообщения (550 reject), у которых scores, присвоенный SpamAssassin, больше 15 OPTIONS="${OPTIONS} -r 15"
Директивы фильтрации xxxxxx_checks
Каждая директива отвечает за свою часть почтового сообщения
header_checks— проверки применяются к заголовкам сообщения, т.е. от первой строки сообщения до первой пустой строкиbody_checks— проверки применяются к телу сообщения; телом считается все, что находится между заголовкамиmime_header_checks— проверки применяются к MIME-заголовкам верхнего уровня и к MIME-заголовкам вложенных сообщенийnested_header_checks— проверки применяются к заголовкам вложенных сообщений message/rfc822, за исключением MIME-заголовков
# Проверять заголовки почтовых сообщений на совпадение с # шаблоном и выполнять указанное действие при совпадении header_checks = pcre:/etc/postfix/incoming_header_checks
/шаблон/модификаторы ДЕЙСТВИЕ [опциональный текст] /шаблон/модификаторы ДЕЙСТВИЕ [опциональный текст] /шаблон/модификаторы ДЕЙСТВИЕ [опциональный текст]
Для каждого шаблона поиска нужно задать одно из следующих действий
IGNORE— удалить из сообщения строку, которая совпадает с шаблоном поиска.REPLACE text— заменить в сообщении строку, которая совпадает с шаблоном поиска, на строкутекст.BCC user@domain— добавить в сообщение заголовокBcc(скрытая копия) при совпадении с шаблоном.REJECT [текст]— не принимать сообщение, необязательныйтекстбудет отправлен клиенту и записан в лог-файл.WARN [текст]— записать в лог-файл предупреждениеwarn:, необязательныйтексттоже будет записан в лог-файл. При этом сообщение будет доставлено без каких-либо изменений.INFO [текст]— записать в лог-файл сообщениеinfo:, необязательныйтексттоже будет записан в лог-файл. При этом сообщение будет доставлено без каких-либо изменений.PREPEND [текст]— добавить указанныйтекстперед строкой, каторая совпадает с шаблоном поиска. Как правило, используется для добавления заголовков.HOLD [текст]— поместить сообщение в очередь отложенных сообщений (пока администратор не решит, что с ним делать). Если указан необязательныйтекст— записать его в лог-файл вместе со строкой, в которой найдено совпадение.DISCARD [текст]— сообщить клиенту об успешной доставке, но молча удалить сообщение. Если указан необязательныйтекст— записать его в лог-файл вместе со строкой, в которой найдено совпадение.FILTER transport:nexthop— отправить сообщение с использованием указанного транспорта (транспорт должен быть определен в файле конфигурацииmaster.cf).REDIRECT user@domain— перенаправить сообщение по указанному адресу вместо того, чтобы доставить его первоначальному получателю (получателям). Подменяет любое действиеFILTER.
Действия REJECT, DISCARD, REDIRECT, FILTER, HOLD являются завершающими, то есть прекращают обработку сообщения. Действия WARN, INFO, IGNORE, BCC, PREPEND не являются завершающими, то есть обработка сообщения продолжается.
# Добавляем запись в лог-файл, если SpamAssassin пометил письмо как спам
/^X-Spam-Flag: YES/ WARN Spam detected
# Отправляем скрытую копию админу, если SpamAssassin пометил письмо как спам
/^X-Spam-Flag: YES/ BCC evgeniy@example.com
# Перенаправляем сообщение админу, если SpamAssassin пометил письмо как спам
/^X-Spam-Level: \*\*\*\*\*/ REDIRECT evgeniy@example.com
Пример отправки сообщения с использованием транспорта, определенного в master.cf
# Отправить сообщение с использованием транспорта, определенного в master.cf
/^X-Spam-Level: \*\*\*\*\*/ FILTER alertspam
# определяем транспорт alertspam в файле конфигурации master.cf alertspam unix - n n - - pipe flags=RO user=nobody argv=/usr/local/bin/alertspam.sh $sender $recipient
#!/bin/bash TELEGRAM_API_KEY='.........' TELEGRAM_CHAT_ID='.........' # почтовое сообщение без заголовков content=$(echo "$(cat)" | awk 'BEGIN {content=0} /^$/ {content=1; next} content==1 {print $0}') # отправляем в телеграм отправителя, получателя и тело сообщения message="SPAM MESSAGE\nОтправитель: $1\nПолучатель: $2\n\n$content" curl -s -X POST "$TELEGRAM_API_KEY" -d chat_id="$TELEGRAM_CHAT_ID" -d text="$message" > /dev/null
# Для каждого получателя сообщения с использованием транспорта # alertspam будет отдельный вызов скрипта alertspam.sh alertspam_destination_recipient_limit = 1
Для поддержки карт pcre нужно установить пакет postfix-pcre
# apt install postfix-pcre
Директивы фильтрации smtp_xxxxxx_checks
Директивы smtp_xxxxx_checks используются редко, обычно для настройки достаточно использовать smtp_xxxxx_checks. Однако, smtp_xxxxx_checks предоставляют важное преимущество — возможность отклонить нежелательное письмо на самой ранней стадии, еще во время SMTP-сессии, до того, как оно будет полностью принято сервером. Это экономит ресурсы (сеть, процессор, диск) и позволяет немедленно уведомить отправителя об отказе.
1. Сервер подвергается атаке, когда тысячи писем приходят с подозрительным заголовком
smtp_header_checks = pcre:/ect/postfix/smtp_header_checks
/^Subject: Your account has been hacked, follow the link!/i REJECT Phishing attempt detected
Как только smtpd получает этот заголовок во время команды DATA, он немедленно прерывает сессию с ошибкой 5xx. Письмо не принимается, не записывается во временные файлы, не передается демону cleanup — это экономит ресурсы почтового сервера.
2. Блокировка по известным вредоносным X-Mailer или другим специфичным заголовкам
smtp_header_checks = pcre:/ect/postfix/smtp_header_checks
/^X-Mailer: SpamBotPro v2.1/i REJECT Known spambot client
3. Раннее обнаружение нежелательных типов вложений (.exe, .bat) по MIME-заголовкам
smtp_mime_header_checks = pcre:/ect/postfix/smtp_mime_header_checks
/^\s*Content-Type:.*name\s*=\s*"?.*\.(exe|bat|scr)"?/i REJECT Executable attachment type not allowed /^\s*Content-Disposition:.*filename\s*=\s*"?.*\.(exe|bat|scr)"?/i REJECT Executable attachment type not allowed
4. Предотвращение некоторых видов DoS-атак через длинный заголовок темы сообщения
smtp_header_checks = pcre:/ect/postfix/smtp_header_checks
/^Subject:.{100,}/ REJECT Subject too long
Внешние фильтры Postfix
Postfix позволяет подключать внешние фильтры — SpamAssassin, ClamAV и другие. Сделать это можно двумя способами — с использованием Milter (Mail Filter API) или через директиву content_filter.
Подключение фильтра через Milter
Milter — это протокол, разработанный Sendmail и адаптированный Postfix, который позволяет внешним программам (milter-ам) взаимодействовать с Postfix на различных этапах SMTP-сессии. Postfix передает данные о соединении, отправителе, получателях и теле сообщения milter-приложению. Milter может изменять эти данные, добавлять/удалять заголовки, отклонять сообщение или временно откладывать его.
# Обычно проверки на вирусы делают раньше проверок на спам, так как зараженное # письмо часто и так является спамом, и отклонять его лучше по причине вируса. smtpd_milters = # tcp-сокет для clamav-milter inet:7357@localhost # tcp-сокет для spamass-milter inet:localhost:8892
# При использовании unix-сокетов — убедитесь, что Postfix имеет права доступа # к ним. Это особенно важно, если Postfix работает в chroot (по умолчанию). smtpd_milters = # unix-сокет /var/spool/postfix/clamav/milter.ctl для clamav-milter unix:clamav/milter.ctl # unix-сокет /var/spool/postfix/spamass/spamass.sock для spamass-milter unix:spamass/spamass.sock
# Milter-ы для сообщений, не проходящих через smtpd (например, через sendmail) non_smtpd_milters = $smtpd_milters # Действие по умолчанию, если milter временно недоступен или вернул ошибку milter_default_action = tempfail # Таймауты для внешних фильтров в секундах (это не обязательно, но желательно) milter_connect_timeout = 30s milter_command_timeout = 30s milter_content_timeout = 300s # Версия протокола Milter, 6-я версия рекомендуется для современных фильтров milter_protocol = 6
Postfix поддерживает два формата для указания TCP-сокета — inet:host:port и inet:port@host, но лучше использовать какой-то один формат.
Подключение через content_filter
При использовании директивы content_filter — Postfix принимает все письмо целиком (после команды DATA и точки). Затем передает сообщение внешнему фильтру через один из поддерживаемых механизмов (по SMTP-протоколу или через pipe). Внешний фильтр обрабатывает сообщение, а затем возвращает его обратно в Postfix (по SMTP-протоколу или через pipe) для дальнейшей доставки или отклонения. Главный недостаток — можно указать только одно значение для директивы content_filter (хотя есть обходные пути).
Подключение SpamAssassin через content_filter
# Отправлять сообщения на внешний фильтр для проверки на спам content_filter = spam_filter # Отключаем преобразования адреса получателя, чтобы фильтр мог # проверить оригинальное сообщение, до любых изменений. receive_override_options = no_address_mappings
spam_filter unix - n n - - pipe user=spamd argv=/usr/bin/spamc -e /usr/sbin/sendmail -i -f $sender $recipient
Подключение ClamAV через content_filter
# Отправлять сообщения на внешний фильтр для проверки на вирус content_filter = virus_filter # Отключаем преобразования адреса получателя, чтобы фильтр мог # проверить оригинальное сообщение, до любых изменений. receive_override_options = no_address_mappings
virus_filter unix - n n - - pipe user=filter argv=/usr/local/bin/virus-filter.sh $sender $recipient
Скрипт virus-filter.sh получает сообщение на stdin и проверяет на наличие вируса. Если вирус не обнаружен — возвращает сообщение в Postfix через утилиту sendmail. Если обнаружен вирус — демон smtpd отправит удаленному SMTP-клиенту (отправителю) постоянную ошибку (5xx). Если была ошибка сканирования — демон smtpd отправит удаленному SMTP-клиенту временную ошибку (4xx).
Что такое Milter в Postfix
Milter — это протокол, разработанный Sendmail и адаптированный Postfix, который позволяет внешним программам (milter-ам) взаимодействовать с Postfix на различных этапах SMTP-сессии. Postfix передает данные о соединении, отправителе, получателях и теле сообщения milter-приложению. Milter может изменять эти данные, добавлять/удалять заголовки, отклонять сообщение или временно откладывать его.
Postfix и Milter общаются через tcp-сокет или unix-сокет. Milter может вмешиваться на этапах CONNECT, HELO, MAIL FROM, RCPT TO, DATA, END-OF-MESSAGE. Данные передаются последовательно по мере поступления, а не все сообщение целиком. Решение об отклонении может быть принято до полного получения сообщения. Postfix может использовать цепочку из нескольких milter-ов, каждый из которых выполняет свою задачу. Порядок milter-ов в smtpd_milters важен, так как они обрабатывают сообщение последовательно.
# Обычно проверки на вирусы делают раньше проверок на спам, так как зараженное # письмо часто и так является спамом, и отклонять его лучше по причине вируса. smtpd_milters = # tcp-сокет для clamav-milter inet:7357@localhost # tcp-сокет для spamass-milter inet:localhost:8892
# При использовании unix-сокетов — убедитесь, что Postfix имеет права доступа # к ним. Это особенно важно, если Postfix работает в chroot (по умолчанию). smtpd_milters = # unix-сокет /var/spool/postfix/clamav/milter.ctl для clamav-milter unix:clamav/milter.ctl # unix-сокет /var/spool/postfix/spamass/spamass.sock для spamass-milter unix:spamass/spamass.sock
# Milter-ы для сообщений, не проходящих через smtpd (например, через sendmail) non_smtpd_milters = $smtpd_milters # Действие по умолчанию, если milter временно недоступен или вернул ошибку milter_default_action = tempfail # Таймауты для внешних фильтров в секундах (это не обязательно, но желательно) milter_connect_timeout = 30s milter_command_timeout = 30s milter_content_timeout = 300s # Версия протокола Milter, 6-я версия рекомендуется для современных фильтров milter_protocol = 6
Директива milter_default_action может принимать значения tempfail (временное отклонение, попробовать позже), accept (принять, как будто фильтр одобрил сообщение), reject (отклонить, как будто фильтр не одобрил сообщение).
Таймауты milter_connect_timeout, milter_command_timeout, milter_content_timeout важны для предотвращения зависания Postfix в ожидании ответа от медленного или неисправного фильтра.
Директива non_smtpd_milters используется для применения фильтров к почтовым сообщениям, которые поступают не через стандартный SMTP-протокол, обрабатываемый демоном smtpd. То есть, это сообщения, которые создаются локально на сервере или отправляются через утилиту sendmail. В этом случае письмо подхватывается демоном pickup, который затем передает его демону cleanup. Именно cleanup передает сообщение на обработку в фильтры, заданные в директиве non_smtpd_milters.
Когда демоны smtpd и cleanup запускаются в chroot-окружении, их файловая система «запирается» внутри /var/spool/postfix. Если unix-сокет Milter-приложения находится вне этой chroot-директории, то Postfix демоны не смогут его найти и подключиться.
Postfix предоставляет механизм, который позволяет «пробросить» unix-сокеты внутрь chroot-окружения. Демон master Postfix (который сам не работает в chroot) может создать копию или специальную точку доступа к unix-сокету внутри chroot-директории перед запуском chroot-демона.
cleanup unix n - n - 0 cleanup # проброс unix-сокета opendkim внутрь chroot для демона cleanup -o unix:private/opendkim_chroot_sock=unix:/var/run/opendkim/opendkim.sock
# обращение к фильтру opendkim через «проброшенный» unix-сокет non_smtpd_milters = unix:private/opendkim_chroot_sock
Важно проверить, что права доступа к оригинальному unix-сокету и к «проброшенному» сокету внутри chroot корректно настроены, чтобы Postfix демоны (работающие под пользователем postfix) могли к ним подключаться.
SpamAssassin для защиты от спама
Postfix будет передавать каждое полученное сообщение SpamAssassin для анализа. SpamAssassin пометит письмо (добавит заголовки, изменит тему) и вернет его обратно Postfix для дальнейшей доставки. Подключить SpamAssassin можно с помощью директивы content_filter, либо с использованием Milter.
Подключение SpamAssassin через content_filter
Нужно установить демона spamd и клиента spamc
# apt install spamassassin spamc
# sytemctl status spamd.service ● spamd.service - Perl-based spam filter using text analysis Loaded: loaded (/usr/lib/systemd/system/spamd.service; enabled; preset: enabled) Active: active (running) since Fri 2025-05-16 12:30:07 MSK; 1min 25s ago Main PID: 175488 (spamd) Tasks: 3 (limit: 1090) Memory: 138.7M (peak: 139.7M) CPU: 2.354s CGroup: /system.slice/spamd.service ├─175488 /usr/bin/perl "-T -w" /usr/sbin/spamd --pidfile=/run/spamd.pid --create-prefs --max-children 5... ├─175601 "spamd child" └─175602 "spamd child" .......... systemd[1]: Started spamd.service - Perl-based spam filter using text analysis. .......... spamd[175488]: zoom: able to use 388/388 'body_0' compiled rules (100%) .......... spamd[175488]: spamd: server started on IO::Socket::IP [::1]:783, IO::Socket::IP [127.0.0.1]:783 (running... .......... spamd[175488]: spamd: server pid: 175488 .......... spamd[175488]: spamd: server successfully spawned child process, pid 175601 .......... spamd[175488]: spamd: server successfully spawned child process, pid 175602 .......... spamd[175488]: prefork: child states: IS .......... spamd[175488]: prefork: child states: II
Cron-задание для автоматического обновления SpamAssassin
# crontab -e 0 3 * * * /usr/bin/sa-update && /bin/systemctl restart spamd.service
Сообщаем Postfix, что нужно использовать внешний фильтр
# nano /etc/postfix/main.cf
# Отправлять сообщения на внешний фильтр для проверки на спам content_filter = spam_filter # Отключаем преобразования адреса получателя, чтобы фильтр мог # проверить оригинальное сообщение, до любых изменений. receive_override_options = no_address_mappings
Создаем демона spam_filter в файле конфигурации /etc/postfix/master.cf
# nano /etc/postfix/master.cf
spam_filter unix - n n - - pipe user=spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -i -f $sender $recipient
Postfix передаёт сообщение spamc, который передает его демону spamd, а потом возвращает обратно в Postfix через утилиту sendmail.
Демон spamd.service добавляет к письмам специальные заголовки и может изменять тему сообщения
X-Spam-Flag: YES— если письмо признано спамомX-Spam-Status: Yes.....— детальный статусX-Spam-Level: ***— уровень спама звездочками
Редактируем файл конфигурации SpamAssassin, чтобы добавлять в заголовок Subject строку ***SPAM***
# nano /etc/spamassassin/local.cf
# Добавлять в заголовок Subject строку ***SPAM*** rewrite_header Subject ***SPAM*** # SpamAssassin может просто добавлять заголовки (0) # или создавать новое письмо с отчетом и прикреплять # исходное сообщение как вложение к отчету (1) report_safe 0
Когда spamc возвращает обработанное письмо обратно в Postfix через sendmail, оно через maildrop и pickup попадает к демону cleanup. На этом этапе можно использовать директиву header_checks для реакции на заголовки, которые добавил SpamAssassin.
# nano /etc/postfix/main.cf
# Это глобальная настройка, действует для демноа smtpd на 25 порту и для демонов submission # и submissions, если для них не задано другое значение в файле /etc/postfix/master.cf header_checks = pcre:/etc/postfix/header_checks
# nano /etc/postfix/header_checks
# Очень мягкая реакция — добавляем запись в лог-файл, если SpamAssassin пометил письмо как спам. Сообщение # будет доставлено получателю, почтовый клиент может поместить сообщение в отдельную папку для спама. /^X-Spam-Flag: YES/ WARN Spam detected
# Если SpamAssassin пометил письмо как спам — оно не будет доставлено получателю. Postfix отправит NDR # (Non-Delivery Report) на адрес конвертного отправителя MAIL FROM и добавит запись в лог-файл о причинах. /^X-Spam-Flag: YES/ REJECT Spam detected
# Довольно опасная реакция — письмо не будет доставлено получателю. Non-Delivery Report не отправляется, # добавляется запись в лог-файл с указанием причины. С точки зрения отправителя — письмо просто пропало. /^X-Spam-Flag: YES/ DISCARD Spam detected
SpamAssassin для демона submission(s)
Директивы content_filter и receive_override_options, заданные в файле /etc/postfix/main.cf — действуют глобально, то есть не только для демона smtpd на 25-ом порту, но и для демонов submission(s). При этом, мы можем установить другое значение header_checks для submission(s) в файле /etc/postfix/master.cf и применять другую политику для «своих» почтовых клиентов.
# nano /etc/postfix/master.cf
# Choose one: enable submission for loopback clients only, or for any client. #127.0.0.1:submission inet n - y - - smtpd submission inet n - y - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_tls_auth_only=yes -o smtpd_client_restrictions=$submission_client_restrictions -o smtpd_helo_restrictions=$submission_helo_restrictions -o smtpd_sender_restrictions=$submission_sender_restrictions -o smtpd_relay_restrictions=$submission_relay_restrictions -o smtpd_recipient_restrictions=$submission_recipient_restrictions # Для «своих» почтовых клиентов, которые прошли аутентификацию -o header_checks=pcre:/etc/postfix/msa_header_checks # Choose one: enable submissions for loopback clients only, or for any client. #127.0.0.1:submissions inet n - y - - smtpd submissions inet n - y - - smtpd -o syslog_name=postfix/submissions -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes -o smtpd_client_restrictions=$submission_client_restrictions -o smtpd_helo_restrictions=$submission_helo_restrictions -o smtpd_sender_restrictions=$submission_sender_restrictions -o smtpd_relay_restrictions=$submission_relay_restrictions -o smtpd_recipient_restrictions=$submission_recipient_restrictions # Для «своих» почтовых клиентов, которые прошли аутентификацию -o header_checks=pcre:/etc/postfix/msa_header_checks
Будем отправлять скрытую копию в специальный почтовый ящик (который нужно предварительно создать)
# nano /etc/postfix/msa_header_checks
# Отправляем скрытую копию в специальный ящик, если письмо помечено как спам
/^X-Spam-Flag: YES/ BCC auth-user-spam@example.com
Добавим мониторинг количества спам-сообщений в этом ящике
#!/bin/bash TELEGRAM_API_KEY='..........' TELEGRAM_CHAT_ID='..........' SPAM_MAILDIR='/var/mail/auth-user-spam' # ящик спам-сообщений OLD_MINUTES=1440 # удалять старые сообщения (больше 24 часов) ALERT_COUNT=50 # уведомлять, если 50 или больше спам-сообщений function telegram { curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_API_KEY/sendMessage" \ -d chat_id="$TELEGRAM_CHAT_ID" \ -d text="${1}" > /dev/null } # Удаление старых писем из ящика find $SPAM_MAILDIR -type f -mmin +$OLD_MINUTES -delete # Подсчет оставшихся сообщений count=$(find $SPAM_MAILDIR -type f | wc -l) # Отправка уведомления в телеграм if [ "$count" -ge "$ALERT_COUNT" ]; then telegram "Внимание! Почтовые клиенты отправили $count спам-сообщений" fi
Подключение SpamAssassin с использованием Milter
Нужно установить пакет spamass-milter — это демон, который будет принимать сообщения от Postfix по Milter-протоколу. Далее демон spamass-milter передает сообщение на проверку демону spamd через tcp-сокет 127.0.0.1:783.
# apt install spamass-milter
# systemctl status spamass-milter.service ● spamass-milter.service - LSB: milter for spamassassin Loaded: loaded (/etc/init.d/spamass-milter; generated) Active: active (running) since Sun 2025-05-25 11:42:18 MSK; 7min ago Docs: man:systemd-sysv-generator(8) Process: 265822 ExecStart=/etc/init.d/spamass-milter start (code=exited, status=0/SUCCESS) Tasks: 5 (limit: 1090) Memory: 660.0K (peak: 1.3M) CPU: 33ms CGroup: /system.slice/spamass-milter.service └─265844 /usr/sbin/spamass-milter -P /var/run/spamass/spamass.pid -f -p /var/spool/postfix/spamass/spamass.sock -u spamass-milter -i 127.0.0.1 .......... systemd[1]: Starting spamass-milter.service - LSB: milter for spamassassin... .......... spamass-milter[265844]: spamass-milter 0.4.0 starting .......... spamass-milter[265822]: Starting Sendmail milter plugin for SpamAssassin: spamass-milter .......... systemd[1]: Started spamass-milter.service - LSB: milter for spamassassin.
$ systemctl status spamd.service ● spamd.service - Perl-based spam filter using text analysis Loaded: loaded (/usr/lib/systemd/system/spamd.service; enabled; preset: enabled) Active: active (running) since Fri 2025-05-16 12:30:07 MSK; 1 week 1 day ago Main PID: 175488 (spamd) Tasks: 3 (limit: 1090) Memory: 133.3M (peak: 139.7M) CPU: 2min 15.137s CGroup: /system.slice/spamd.service ├─175488 /usr/bin/perl "-T -w" /usr/sbin/spamd --pidfile=/run/spamd.pid --create-prefs --max-children 5 ... ├─175601 "spamd child" └─175602 "spamd child" .......... systemd[1]: Started spamd.service - Perl-based spam filter using text analysis. .......... spamd[175488]: zoom: able to use 388/388 'body_0' compiled rules (100%) .......... spamd[175488]: spamd: server started on IO::Socket::IP [::1]:783, IO::Socket::IP [127.0.0.1]:783 .......... spamd[175488]: spamd: server pid: 175488 .......... spamd[175488]: spamd: server successfully spawned child process, pid 175601 .......... spamd[175488]: spamd: server successfully spawned child process, pid 175602 .......... spamd[175488]: prefork: child states: IS .......... spamd[175488]: prefork: child states: II
Редактируем файл конфигурации Postfix
# nano /etc/postfix/main.cf
# Применять фильтр для сообщений, которые попадают в Postfix через демона smtpd smtpd_milters = unix:spamass/spamass.sock # Применять фильтр для писем, которые попадают в Postfix не через демона smtpd non_smtpd_milters = $smtpd_milters
Файл конфигурации spamass-milter можно не трогать
# nano /etc/default/spamass-milter
# Запускать от имени пользователя spamass-milter, # игнорировать письма с localhost (127.0.0.1) OPTIONS="-u spamass-milter -i 127.0.0.1" # Отклонять письма (550 reject), у которых scores, # присвоенный SpamAssassin, больше 15 #OPTIONS="${OPTIONS} -r 15" # Запретить SpamAssassin изменять Subject или тело # сообщения, можно только добавлять заголовки # X-Spam-Status, X-Spam-Level, X-Spam-Score #OPTIONS="${OPTIONS} -m" # Если Postfix установлен (файл /usr/sbin/postfix # существует и исполняемый), то настройки ниже # будут установлены по умолчанию # SOCKET="/var/spool/postfix/spamass/spamass.sock" # SOCKETOWNER="postfix:postfix" # SOCKETMODE="0660"
Отправка тестового сообщения
Для этого отправим сообщение с localhost, которое будет содержать специальную строку, которую предлагает SpamAssassin в документации. Но для этого нам нужно, чтобы сообщения, отправленные с localhost — тоже проверялись на спам. Поэтому редактируем файл конфигурации spamass-milter (временно убираем из OPTIONS опцию -i 127.0.0.1) и перезапускаем демона.
# telnet localhost 25 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 mail.example.com ESMTP Postfix EHLO mail.example.com 250-mail.example.com 250-PIPELINING 250-SIZE 15728640 250-ETRN 250-STARTTLS 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250-SMTPUTF8 250 CHUNKING MAIL FROM: <somebody@mail.ru> 250 2.1.0 Ok RCPT TO: <evgeniy@example.com> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> Subject: Test spam XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X . 250 2.0.0 Ok: queued as 9FB852185D QUIT 221 2.0.0 Bye Connection closed by foreign host.
ClamAV для защиты от вирусов
Postfix будет передавать каждое полученное сообщение ClamAV для анализа. ClamAV может предпринять какие-то действия при обнаружении вируса или просто добавить заголовки и вернуть сообщение Postfix. Подключить ClamAV можно с помощью директивы content_filter, либо с использованием Milter.
Подключение ClamAV с использованием Milter
Если SpamAssassin был подключен с помощью директивы content_filter — подключить ClamAV можно только с помощью Milter, потому что директива разрешает только одно значение (хотя есть обходные пути).
# apt install clamav-daemon clamav-freshclam
ClamAV блокирует обращения с российских ip-адресов, так что нужно заменить в файле конфигурации зеркала для скачивания обновлений базы.
# chmod u+w /etc/clamav/freshclam.conf # nano /etc/clamav/freshclam.conf
# Имя хоста для DNS-запроса номера последний версии базы DNSDatabaseInfo current.cvd.clamav.net # Сколько раз в сутки проверять наличие обновлений базы Checks 4 # Не обращаться к этим зеркалам для скачивания обновлений #DatabaseMirror db.local.clamav.net #DatabaseMirror database.clamav.net # Можно подключиться к этим зеркалам через прокси-сервер #HTTPProxyServer proxy.server.com #HTTPProxyPort 8080 #HTTPProxyUsername username #HTTPProxyPassword password # Обновление из приватного зеркала. У зеркала PrivateMirror # приоритет по отношению к зеркалу DatabaseMirror. PrivateMirror https://packages.microsoft.com/clamav PrivateMirror https://mirror.truenetwork.ru/clamav PrivateMirror https://clamav-mirror.ru
Для начала базу вирусов нужно обновить вручную (можно с подробным выводом всего процесса)
# freshclam -vvv .......... ERROR: Database load killed by signal 9 ..........
Тут меня поджидал неприятный сюрприз — нехватка оперативной памяти. Так что пришлось создать файл подкачки, подробнее здесь. Если вместо обновления базы freshclam выдал предупреждение о превышении лимит запросов к серверу обновлений — нужно удалить файл /var/lib/clamav/freshclam.dat и попробовать еще раз.
Отредактируем файл конфигурации clamav-daemon.service
# nano /etc/clamav/clamd.conf
# Путь к файлу логов демона clamd LogFile /var/log/clamav/clamd.log # Добавлять метку времени в логи LogTime true # Файл для PID процесса clamd PidFile /var/run/clamav/clamd.pid # User, под которым запущен clamd User clamav # Разрешить clamav использовать доп.группы # (для взаимодействия с другими сервисами) AllowSupplementaryGroups true # Директория для хранения базы вирусов DatabaseDirectory /var/lib/clamav # Директория для распаковки файлов TemporaryDirectory /var/tmp # Сокет для связи с Milter LocalSocket /var/run/clamav/clamd.ctl # Группа, которой принадлежит сокет LocalSocketGroup clamav # Права доступа к сокету (rw-rw----) LocalSocketMode 660 # Удалять зависший сокет FixStaleSocket true # Включить сканирование почтовых сообщений ScanMail true # Включить сканирование архивов ScanArchive true # Макс. уровень вложенности архивов MaxRecursion 16 # Макс. кол-во файлов, извлекаемых из архива MaxFiles 10000 # Максимальный объем данных, который демон будет принимать от клиента, # все остальное будет отброшено. Нужно установить значение, равное или # чуть больше message_size_limit из файла конфигурации Postfix. StreamMaxLength 16M # Максимальный объем данных, который будет сканироваться — из того объема, # который был принят с учетом StreamMaxLength. Все, что выходит за это # ограничение — просто не сканируется (хотя там может быть вирус). MaxScanSize 16M # Максимальный размер отдельного файла (например, вложение или файл # внутри архива), который будет сканироваться. Файлы большего размера # не будут сканироваться (хотя в них может быть вирус). MaxFileSize 16M
Запустим в работу демонов и добавим в автозагрузку
# systemctl enable --now clamav-daemon.service # демон сканирования на вирусы # systemctl enable --now clamav-freshclam.service # демон для обновления базы вирусов
Теперь нам нужен Milter для передачи сообщений от Postfix в ClamAV
# apt install clamav-milter
# systemctl status clamav-milter.service ● clamav-milter.service - LSB: ClamAV virus milter Loaded: loaded (/etc/init.d/clamav-milter; generated) Active: active (running) since Wed 2025-05-28 09:52:06 MSK; 15min ago Docs: man:systemd-sysv-generator(8) Process: 31904 ExecStart=/etc/init.d/clamav-milter start (code=exited, status=0/SUCCESS) Tasks: 6 (limit: 1090) Memory: 684.0K (peak: 4.1M swap: 1.2M swap peak: 1.2M) CPU: 187ms CGroup: /system.slice/clamav-milter.service └─32026 /usr/sbin/clamav-milter --config-file=/etc/clamav/clamav-milter.conf .......... systemd[1]: Starting clamav-milter.service - LSB: ClamAV virus milter... .......... clamav-milter[31904]: * Starting Sendmail milter plugin for ClamAV clamav-milter .......... clamav-milter[31904]: ...done. .......... systemd[1]: Started clamav-milter.service - LSB: ClamAV virus milter.
Отредактируем файл конфигурации Milter
# nano /etc/clamav/clamav-milter.conf
# Доступ к Milter-у через tcp-сокет, самый простой вариант MilterSocket inet:7357@localhost # Если обраружен вирус — отклонить письмо с ошибкой SMTP, # отправитель получит от Postfix уведомление о недоставке OnInfected Reject # Если сканирование не удалось по любой причине — Postfix # временно отклонит сообщение (самый безопасный вариант) OnFail Defer # Максимальный размер сообщения, нужно установить значение, # которое задано для message_size_limit или чуть больше MaxFileSize 16M # Пользователь, от имени которого будет работать milter User clamav # Максимальное время ожидания данных от Postfix или от # ClamAV, 120 секунд (2 минуты) обычно достаточно ReadTimeout 120 # Запускать milter в фоновом режиме для продакшен (false) # или на переднем плане для отладки (true). Foreground false # Путь к файлу, где будет храниться PID clamav-milter PidFile /var/run/clamav/clamav-milter.pid # Путь к сокету демона clamd (основного сканера ClamAV) ClamdSocket unix:/var/run/clamav/clamd.ctl # Что делать, если сообщение чистое (не содержит вируса) OnClean Accept # Как добавлять заголовок X-Virus-Scanned: Replace (заменит # существующий), Add (добавит новый), Off (не добавлять) AddHeader Replace # Логировать в syslog (true) или в отдельный файл (false) LogSyslog false # Facility для syslog, если LogSyslog имеет значение true LogFacility LOG_LOCAL6 # Путь к файлу логов, если LogSyslog задано значение false LogFile /var/log/clamav/clamav-milter.log # Разблокировать лог-файл после каждой записи? Может замедлить # работу, поэтому обычно false LogFileUnlock false # Включить более подробное логирование? Установить false для # продакшена или true для отладки LogVerbose false # Логировать информацию о зараженных письмах? Off (выключить), # Basic (кратко), Full (подробно) LogInfected Basic # Логировать информацию о письмах, если вирус не обнаружен? LogClean Off # Добавлять временную метку к каждой записи в лог-файле? LogTime true # Включить встроенную ротацию лог-файла? Обычно не нужно, # потому что при установке создается конфиг для logrotate LogRotate false # Максимальный размер лог-файла перед ротацией (если вкл) LogFileMaxSize 1M # Обрабатывать письмо с несколькими получателями один раз # (false) или несколько раз по кол-ву получателей (true) SupportMultipleRecipients false # Директория для временных файлов, используемых при работе TemporaryDirectory /tmp # Группа для unix-сокета MilterSocket (если используется), может # принимать значение postfix или clamav. Чтобы Postfix имел доступ # к сокету, либо группа postfix должна быть задана как владелец # сокета, либо пользователь postfix должен принадлежать группе # clamav, либо права на сокет должны быть 666 (доступ для всех). #MilterSocketGroup clamav # Права доступа для unix-сокета MilterSocket, если используется #MilterSocketMode 666 # При обнаружении зависшего сокета от прошлого сбоя — удалить #FixStaleSocket true
clamav-milter.service — можно прочитать в документации man clamav-milter.conf.
Редактируем файл конфигурации Postfix
# nano /etc/postfix/main.cf
# Проверять на вирусы сообщения, которые попадают в Postfix через демона smtpd smtpd_milters = inet:7357@localhost # Проверять на вирусы письма, которые попадают в Postfix не через демона smtpd non_smtpd_milters = $smtpd_milters # Действие по умолчанию, если milter временно недоступен или вернул ошибку milter_default_action = tempfail # Таймауты для внешних фильтров в секундах (это не обязательно, но желательно) milter_connect_timeout = 30s milter_command_timeout = 30s milter_content_timeout = 300s # Версия протокола Milter, 6-я версия рекомендуется для современных фильтров milter_protocol = 6
Отправка тестового сообщения
Нужно отправить сообщение, которое будет содержать строку EICAR-Test или файл вложения с этой строкой. EICAR-Test-File — стандартный файл, применяемый для проверки, работает ли антивирус.
# telnet localhost 25 Trying ::1... Connection failed: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 mail.example.com ESMTP Postfix EHLO mail.example.com 250-mail.example.com 250-PIPELINING 250-SIZE 15728640 250-ETRN 250-STARTTLS 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250-SMTPUTF8 250 CHUNKING MAIL FROM: <somebody@mail.ru> 250 2.1.0 Ok RCPT TO: <evgeniy@example.com> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> Subject: Test virus X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* . 550 5.7.1 Command rejected QUIT 221 2.0.0 Bye Connection closed by foreign host.
# tail -1 /var/log/clamav/clamav-milter.log ..... -> Message from <somebody@mail.ru> to <evgeniy@example.com> infected by Eicar-Signature
# tail -1 /var/log/clamav/clamav.log ..... -> /tmp/clamav-a1f165437fd25df78669fc69eafed450.tmp (deleted): Eicar-Signature ... FOUND
# tail -5 /var/log/mail.log ..... postfix/smtpd[4700]: connect from localhost[127.0.0.1] ..... postfix/smtpd[4700]: 5F01E21889: client=localhost[127.0.0.1] ..... postfix/cleanup[5073]: 5F01E21889: message-id=<20250711151715.5F01E21889@mail.example.com> ..... postfix/cleanup[5073]: 5F01E21889: milter-reject: END-OF-MESSAGE from localhost[127.0.0.1]: 5.7.1 Command rejected; from=<somebody@mail.ru> to=<evgeniy@example.com> proto=ESMTP helo=<mail.example.com> ..... postfix/smtpd[4700]: disconnect from localhost[127.0.0.1] ehlo=1 mail=1 rcpt=1 data=0/1 quit=1 commands=4/
Подключение ClamAV через content_filter
Сообщаем Postfix, что нужно использовать внешний фильтр
# nano /etc/postfix/main.cf
# Отправлять сообщения на внешний фильтр для проверки на вирус content_filter = virus_filter # Отключаем преобразования адреса получателя, чтобы фильтр мог # проверить оригинальное сообщение, до любых изменений. receive_override_options = no_address_mappings
Создаем демона virus_filter в файле конфигурации /etc/postfix/master.cf
# nano /etc/postfix/master.cf
virus_filter unix - n n - - pipe user=filter argv=/usr/local/bin/virus-filter.sh $sender $recipient
Скрипт virus-filter.sh получает сообщение на stdin и проверяет на наличие вируса. Если вирус не обнаружен — возвращает сообщение в Postfix через утилиту sendmail. Если обнаружен вирус — демон smtpd отправит удаленному SMTP-клиенту (отправителю) постоянную ошибку (5xx). Если была ошибка сканирования — демон smtpd отправит удаленному SMTP-клиенту временную ошибку (4xx).
#!/bin/sh SENDMAIL='/usr/sbin/sendmail -i' # Сканирует stdin, выводит на stdout, если вирус не обнаружен CLAMDSCAN='/usr/bin/clamdscan --stdout --no-summary -' # Код возврата для Postfix при обнаружении вируса (EX_UNAVAILABLE) EXIT_VIRUS=69 # Код возврата для Postfix для временной ошибки (EX_TEMPFAIL) EXIT_TEMPFAIL=75 # Использование временного файла безопаснее для больших писем TMPFILE=$(mktemp /tmp/postfix-clamav.XXXXXX) # Если mktemp не сработал, выходим из скрипта с временной ошибкой if [ -z "$TMPFILE" ]; then logger -t postfix-clamav 'Failed to create temporary file' exit $EXIT_TEMPFAIL fi trap "rm -f $TMPFILE" EXIT SIGHUP SIGINT SIGQUIT SIGTERM cat > "$TMPFILE" # Если clamdscan находит вирус — возвращает код 1, ничего не выводит. # Если вирус не найден, возвращает код 0 и выводит письмо на stdout. OUTPUT_FROM_SCAN=$("$CLAMDSCAN" < "$TMPFILE") SCAN_RESULT=$? if [ $SCAN_RESULT -eq 0 ]; then # Вирус не найден, возвращаем письмо в Postfix с использованием sendmail echo "$OUTPUT_FROM_SCAN" | $SENDMAIL -f "$SENDER" -- "$RECIPIENT" exit $? elif [ $SCAN_RESULT -eq 1 ]; then # Найден вирус, выходим с кодом $EXIT_VIRUS (код будет получен Posfix-ом) # Добавляем запись в системный журнал syslog с тегом postfix-clamav logger -t postfix-clamav "ClamAV find virus: From <$SENDER> To <$RECIPIENT>" exit $EXIT_VIRUS else # Ошибка, выходим с кодом $EXIT_TEMPFAIL (код будет получен Posfix-ом) # Добавляем запись в системный журнал syslog с тегом postfix-clamav logger -t postfix-clamav "ClamAV scan error ($SCAN_RESULT): From <$SENDER> To <$RECIPIENT>" exit $EXIT_TEMPFAIL fi
В качестве параметров передаем скрипту отправителя и получателя из конверта — хотя в этом нет необходимости. Скрипту будут доступны переменные окружения $SENDER (envelope sender), $RECIPIENT (envelope recipient), $SASL_SENDER (адрес отправителя, аутентифицированного через SASL), $SASL_USERNAME (имя пользователя, аутентифицированного через SASL) и другие.
Установка и настройка OpenDKIM
DKIM позволяет добавить цифровую подпись к сообщениям, которые отправляет наш почтовый сервер. Эта подпись связана с доменом и подтверждает, что письмо отправлено нашим сервером. Кроме того, OpenDKIM позволяет проверять DKIM-подписи входящих сообщений от других почтовых серверов.
# apt install opendkim opendkim-tools
$ systemctl status opendkim.service ● opendkim.service - OpenDKIM Milter Loaded: loaded (/usr/lib/systemd/system/opendkim.service; enabled; preset: enabled) Active: active (running) since Wed 2025-05-28 15:43:48 MSK; 14min ago Docs: man:opendkim(8) man:opendkim.conf(5) man:opendkim-lua(3) man:opendkim-genkey(8) man:opendkim-genzone(8) man:opendkim-testkey(8) http://www.opendkim.org/docs.html Process: 35405 ExecStart=/usr/sbin/opendkim (code=exited, status=0/SUCCESS) Main PID: 35406 (opendkim) Tasks: 6 (limit: 1090) Memory: 2.1M (peak: 7.2M) CPU: 51ms CGroup: /system.slice/opendkim.service L-35406 /usr/sbin/opendkim .......... systemd[1]: Starting opendkim.service - OpenDKIM Milter... .......... systemd[1]: Started opendkim.service - OpenDKIM Milter. .......... opendkim[35406]: OpenDKIM Filter v2.11.0 starting
Создаем директории для ключей и конфигурации
# mkdir -p /etc/opendkim/keys/example.com # chown -R opendkim:opendkim /etc/opendkim # chmod -R go-rwx /etc/opendkim/keys
Создаем ключи для домена example.com
# opendkim-genkey -s default -d example.com -D /etc/opendkim/keys/example.com/ -b 2048 # chown opendkim:opendkim /etc/opendkim/keys/example.com/* # chmod 600 /etc/opendkim/keys/example.com/default.private
Здесь default — селектор для использования в ДНС-записи, /etc/opendkim/keys/example.com/ — директория для сохранения ключей, 2048 — размер ключа в битах (2048 — хороший выбор). В директории будут созданы два файла, default.private — приватный ключ, default.txt — публичный ключ для ДНС-записи.
Теперь редактируем файл конфигурации OpenDKIM
# nano /etc/opendkim.conf
# Общие настройки OpenDKIM Syslog yes UMask 002 UserID opendkim:opendkim # пользователь и группа PidFile /var/run/opendkim/opendkim.pid Canonicalization relaxed/simple Mode sv # s — подписывать, v — верифицировать SubDomains no # подписывать или нет субдомены? # Настройки для подписи KeyTable refile:/etc/opendkim/KeyTable SigningTable refile:/etc/opendkim/SigningTable ExternalIgnoreList refile:/etc/opendkim/TrustedHosts InternalHosts refile:/etc/opendkim/TrustedHosts # Алгоритм подписи (рекомендуется) SignatureAlgorithm rsa-sha256 # Ведение статистики (не обязательно, но полезно для отладки) Statistics /var/spool/opendkim/stats.dat StatisticsDisabled no # установить yes, чтобы включить # Адрес почты, с которого будем отправлять отчет владельцу домена, # если DKIM-подпись сообщения от этого домена, не прошла проверку ReportAddress "Postmaster <postmaster@example.com>" SendReports yes # Настройка tcp-сокета для связи с Postfix Socket inet:8891@localhost # Настройка unix-сокета для связи с Postfix #Socket local:/var/spool/postfix/opendkim/opendkim.sock
postfix.
Директория для статистики (не обязательно, но полезно для отладки)
# mkdir -p /var/spool/opendkim # chown opendkim:opendkim /var/spool/opendkim # chmod 750 /var/spool/opendkim
Создаем файлы KeyTable, SigningTable, TrustedHosts
# nano /etc/opendkim/KeyTable
# Связывает имя ключа (селектор и домен) с путем к приватному ключу
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
# nano /etc/opendkim/SigningTable
# Какие ключи использовать для подписи писем от определенных отправителей, # * означает, что для всех отправителей будет использоваться один ключ *@example.com default._domainkey.example.com
# nano /etc/opendkim/TrustedHosts
# Содержит список внутренних хостов InternalHosts, письма от которых будут # подписываться. Также является списком внешниих хостов ExternalIgnoreList, # для которых не нужно проверять DKIM-подпись, потому что мы им доверяем. # Элемент списка может быть IP-адресом, CIDR-блоком или доменом. 127.0.0.1 localhost *.example.com
Настроим Postfix для использования OpenDKIM
# nano /etc/postfix/main.cf
# Применять фильтры для сообщений, которые попадают в Postfix через демона smtpd smtpd_milters = # проверка на вирусы inet:7357@localhost # проверка на спам unix:spamass/spamass.sock # подпись OpenDKIM inet:localhost:8891 # Применять фильтры для писем, которые попадают в Postfix не через демона smtpd non_smtpd_milters = $smtpd_milters # если в /etc/opendkim.conf указано использовать unix-сокет #smtpd_milters = unix:/var/spool/postfixopendkim/opendkim.sock #non_smtpd_milters = unix:/var/spool/postfix/opendkim/opendkim.sock
Наконец, добавим ДНС-запись из файла /etc/opendkim/keys/example.com/default.txt
| Name | Type | TTL | Value |
|---|---|---|---|
example.com |
A |
3600 |
111.111.111.111 |
www.example.com |
A |
3600 |
111.111.111.111 |
mail.example.com |
A |
3600 |
222.222.222.222 |
example.com |
MX 10 |
3600 |
mail.example.com |
example.com |
TXT |
3600 |
v=spf1 a mx -all |
default._domainkey.example.com |
TXT |
3600 |
v=DKIM1; h=sha256; k=rsa; p=MIIBI...TEEeN... |
В файле в скобках будет три строки в кавычках, эти три части единого целого — их нужно «склеить», чтобы получить значение TXT-записи.
default._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIIBI..." "TEEeN..." )
Подписывать или проверять сообщение?
Решение о том, подписывать или проверять, OpenDKIM принимает на основе директивы конфигурации InternalHosts. Если сообщение от хоста, который есть в InternalHosts — это исходящее сообщение, нужно добавить DKIM-подпись. Если сообщение от хоста, который нет в InternalHosts — это входящее сообщение, нужно проверить DKIM-подпись.
Директива Mode в файле opendkim.conf определяет, какие операции будет выполнять OpenDKIM. Обычно устанавливают значение sv (sign and verify), что позволяет одному экземпляру OpenDKIM выполнять обе функции.
# Файл сопоставления доменов и селекторов с ключами для подписи KeyTable refile:/etc/opendkim/KeyTable # Файл сопоставления адресов отправителей с записями в KeyTable SigningTable refile:/etc/opendkim/SigningTable # Сообщения от хостов, которые есть в списке InternalHosts будут # подписываться. Сообщения от хостов, которых нет в этом списке, # будут проверяться (исключая хосты из списка ExternalIgnoreList) InternalHosts refile:/etc/opendkim/InternalHosts # Не проверять подпись от этих внешних хостов, мы им доверяем ExternalIgnoreList refile:/etc/opendkim/ExternalIgnoreList
Это значит, что нам не нужно делать разные настройки для демона smtpd (для проверки сообщений от «чужих» клиентов) и демонов submission(s) (для подписи сообщений от «своих» клиентов) — заботу об этом OpenDKIM берет на себя.
Подпись для нескольких доменов
Тут все как и для одного домена — создание ключей, редактирование файлов, публикация ДНС-записи.
# mkdir -p /etc/opendkim/keys/example.net # mkdir -p /etc/opendkim/keys/example.org
# chown -R opendkim:opendkim /etc/opendkim/keys/example.net # chown -R opendkim:opendkim /etc/opendkim/keys/example.org
# opendkim-genkey -s default -d example.net -D /etc/opendkim/keys/example.net/ -b 2048 # chown opendkim:opendkim /etc/opendkim/keys/example.net/* # chmod 600 /etc/opendkim/keys/example.net/default.private
# opendkim-genkey -s default -d example.org -D /etc/opendkim/keys/example.org/ -b 2048 # chown opendkim:opendkim /etc/opendkim/keys/example.org/* # chmod 600 /etc/opendkim/keys/example.org/default.private
# nano /etc/opendkim/KeyTable
# Связывает имя ключа (селектор и домен) с путем к приватному ключу
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
default._domainkey.example.net example.net:default:/etc/opendkim/keys/example.net/default.private
default._domainkey.example.org example.org:default:/etc/opendkim/keys/example.org/default.private
# nano /etc/opendkim/SigningTable
# Какие ключи использовать для подписи писем от определенных отправителей, # * означает, что для всех отправителей будет использоваться один ключ *@example.com default._domainkey.example.com *@example.net default._domainkey.example.net *@example.org default._domainkey.example.org
# nano /etc/opendkim/TrustedHosts
# Содержит список внутренних хостов InternalHosts, письма от которых будут # подписываться. Также является списком внешниих хостов ExternalIgnoreList, # для которых не нужно проверять DKIM-подпись, потому что мы им доверяем. # Элемент списка может быть IP-адресом, CIDR-блоком или доменом. 127.0.0.1 localhost *.example.com *.example.net *.example.org
Политика DMARC для почтового сервера
Получение отчетов от других MTA
После того, как мы добавили SPF и DKIM — хотелось бы знать, что происходит с почтовыми сообщениями, которые отправляет наш почтовый сервер. Для этого нужно попросить почтовые серверы, который получают наши сообщения — сообщать о результатах проверки SPF и DKIM. Кроме того, мы будем получать от других почтовых серверов информацию, когда кто-то пытается отправлять сообщения от нашего имени.
| Name | Type | TTL | Value |
|---|---|---|---|
example.com |
A |
3600 |
111.111.111.111 |
www.example.com |
A |
3600 |
111.111.111.111 |
mail.example.com |
A |
3600 |
222.222.222.222 |
example.com |
MX 10 |
3600 |
mail.example.com |
example.com |
TXT |
3600 |
v=spf1 a mx -all |
default._domainkey.example.com |
TXT |
3600 |
v=DKIM1; h=sha256; k=rsa; p=MIIBI...TEEeN... |
_dmarc.example.com |
TXT |
3600 |
v=DMARC1; p=none; rua=mailto:dmarc@example.com; sp=reject; adkim=s; aspf=s; fo=1 |
Директива v=DMARC1 — указывает версию протокола (обязательно). Директива p=none — предприсывает серверу-получателю ничего не предпринимать, даже если проверки не пройдены, а просто отправлять отчеты. Директива rua=mailto:dmarc@example.com указывает адрес почты, на который нужно отправлять отчеты.
Директива sp=none — это политика для поддоменов, если не указана — наследуется от директивы p. Директива adkim=s — домен отправителя должен совпадать с доменом, который прошел проверку DKIM. Директива aspf=s — домен отправителя должен совпадать с доменом, который прошел проверку SPF.
Директива fo=1 — предписывает серверу-получателю отправлять отчет, если любая из проверок не пройдена (fo=1 — если все проверки не пройдены). Директива pct=100 — процент писем (от 0 до 100), к которым применяется политика p (по умолчанию 100).
На основе результатов проверок и политики, указанной в DMARC-записи, принимающий сервер решает, что делать с сообщениями, которые не прошли проверку
p=none— ничего не предпринимать, просто отправлять отчеты наdmarc@example.comp=quarantine— могут быть помечены как спам или подвергнуты дополнительной проверкеp=reject— должны быть отклонены сервером получателя (не доставляться получателю)
Начинать следует с политики p=none и изучения отчетов, которые приходят на адрес почты dmarc@example.com. Следующий шаг — политика p=quarantine и pct=10 (для остальных писем, который не прошли проверку, будет применяться политика p=none). Следующий шаг — политика p=reject и pct=10. Заключительный этап — политика p=reject и pct=100.
Политика DMARC для входящих сообщений
OpenDMARC интегрируется с Postfix через milter, применяет политику DMARC (none, quarantine, reject) для входящих сообщений, отправляет RUA-отчеты серверам-отправителям.
# apt install opendmarc
При установке пакета будет предложено установить базу данных для хранения результатов проверок, чтобы потом составлять и отправлять отчеты. Мы от этого отказываемся, результаты проверок будем хранить в файле HistoryFile.
# nano /etc/opendmarc.conf
# Идентификатор или имя почтового сервера, который выполнил проверки # (SPF, DKIM, DMARC), будет добавлен заголовок Authentication-Results AuthservID mail.example.com # Не отправлять подробные (RUF) отчеты о каждом сбое при проверке FailureReports false # Путь к файлу, в котором будет храниться идентификатор процесса PidFile /run/opendmarc/opendmarc.pid # Путь к файлу, содержащему список общедоступных суффиксов доменов # (важно для правильной работы с субдоменами) PublicSuffixList /usr/share/publicsuffix/public_suffix_list.dat # Применять политику, которая указана в ДНС-записи p= для домена # отправителя. Если указано p=reject — сообщения нужно отклонять. # На начальном этапе лучше оставить false (режим мониторинга). RejectFailures false # Используем TCP-сокет (порт 8893 — стандартный для OpenDMARC) Socket inet:8893@localhost # Включить логирование событий OpenDMARC через системный syslog Syslog true # Если сообщение уже прошло проверку SPF/DKIM на другом доверенном # сервере — OpenDMARC будет доверять этим результатам TrustedAuthservIDs HOSTNAME, mail.example.com # Маска прав доступа для файлов, создаваемых OpenDMARC (socket и пр). # Владелец и группа — чтение и запись, все остальные — только чтение. UMask 0002 # Имя пользователя, от имени которого будет работать демон OpenDMARC UserID opendmarc # Записывать историю DMARC-проверок входящей почты в этот файл. Потом # ReportCommand использует этот файл для генерации отчетов. HistoryFile /var/spool/opendmarc/opendmarc.dat # Утилита генерации отчетов из файла истории проверок HistoryFile ReportCommand /usr/sbin/opendmarc-reports # Периодичность отправки отчетов, которые были созданы ReportCommand ReportInterval 86400 # Организация, которой принадлежит сервер, по умолчанию AuthservID ReportOrgName "MAIL SERVER mail.example.com" # С какого адреса почты отправлять отчеты от имени нашего сервера ReportSender dmarc-reports-do-not-reply@example.com # Список хостов (IP-адресов или имен), для которых DMARC-проверки # проводиться не будут (для доверенных источников) IgnoreHosts /etc/opendmarc/ignore.hosts # Добавляет заголовок OpenDMARC к обработанным сообщениям, указывая # версию и результаты проверки (полезно для отладки) SoftwareHeader true
Директория для хранения HistoryFile
# mkdir -p /var/spool/opendmarc # chown opendmarc:opendmarc /var/spool/opendmarc # chmod 750 /var/spool/opendmarc
Редактируем файл конфигурации Postfix
# nano /etc/postfix/main.cf
# Применять фильтры для сообщений, которые попадают в Postfix через демона smtpd smtpd_milters = # проверка на вирусы inet:7357@localhost # проверка на спам unix:spamass/spamass.sock # подпись OpenDKIM inet:localhost:8891 # политика DMARC inet:localhost:8893 # Применять фильтры для писем, которые попадают в Postfix не через демона smtpd non_smtpd_milters = $smtpd_milters
Взаимодействие Postfix и Dovecot
Передача сообщений от Postfix к Dovecot
Postfix может сам доставлять почту для «своих» пользователей, используя транспорт local и virtual. А может отправлять их Dovecot по протоколу LMTP — по сети либо через unix-сокет. И тогда распределением сообщений по почтовым ящикам пользователей будет заниматься Dovecot — этот вариант предпочтительнее.
Через unix-сокет /var/spool/postfix/private/dovecot-lmtp
# файл конфигурации /etc/postfix/main.cf local_transport = lmtp:unix:private/dovecot-lmtp virtual_transport = lmtp:unix:private/dovecot-lmtp
# файл конфигурации /etc/dovecot/conf.d/10-master.conf service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0660 user = postfix group = postfix } }
По сети, через интерфейс обратной петли loopback
# файл конфигурации /etc/postfix/main.cf local_transport = lmtp:[127.0.0.1]:24 virtual_transport = lmtp:[127.0.0.1]:24
# файл конфигурации /etc/dovecot/conf.d/10-master.conf service lmtp { # требуется установка пакета dovecot-lmtpd inet_listener lmtp { address = 127.0.0.1 port = 24 } }
Есть еще один способ передачи сообщений от Postfix к Dovecot — использовать утилиту dovecot-lda. Можно запускать команду из директивы mailbox_command или создать транспорт dovecot (как мы уже создавали транспорт telegram). Первый вариант подходит для небольшого кол-ва системных пользователей, второй вариант позволяет обслуживать большое кол-во виртуальных пользователей.
Usage: dovecot-lda [-c <config file>] [-d <username>] [-p <path>]
[-m <mailbox>] [-e] [-k] [-f <envelope sender>]
[-a <original envelope recipient>]
[-r <final envelope recipient>]
Запускать команду, указанную в директиве mailbox_command
# без поиска по базе userdb (например, для определения квоты) mailbox_command = /usr/lib/dovecot/dovecot-lda -f "$SENDER" -a "$RECIPIENT"
# с поиском по базе userdb (например, для определения квоты) mailbox_command = /usr/lib/dovecot/dovecot-lda -f "$SENDER" -a "$RECIPIENT" -d "$USER"
Cоздать транспорт dovecot в файле /etc/postfix/master.cf
dovecot unix - n n - - pipe flags=DRSOu user=vmail:vmail argv=/usr/lib/dovecot/dovecot-lda -f $sender -d $recipient
# использовать транспорт dovecot для виртуальных пользователей dovecot_destination_recipient_limit = 1 virtual_transport = dovecot
Файл конфигурации /etc/dovecot/conf.d/15-lda.conf
protocol lda { log_path = /var/log/dovecot-lda.err info_log_path = /var/log/dovecot-lda.log } # Адрес, с которого отправляются сообщения об ошибке (например, при отклонении # письма). По умолчанию используется postmaster@%d, где %d — домен получателя. #postmaster_address = # Имя хоста, которое будет использоваться в заголовках Message-ID и в ответах # LMTP. Если не указано, используется системное имя по умолчанию. #hostname = # Если включено (yes), то при превышении квоты письмо не отклоняется с ошибкой, # а временно задерживается для повторной попытки доставки. #quota_full_tempfail = no # Путь к утилите sendmail, которая используется для отправки писем из Dovecot # (например, для автоматических ответов или сообщений об ошибках). #sendmail_path = /usr/sbin/sendmail # Если задано, сообщения будут отправляться через этот SMTP-хост, а не через # утилиту sendmail. Формат значения директивы — smtp.example.com[:port] #submission_host = # Тема для сообщений с отказами; здесь %s — это тема оригинального письма. #rejection_subject = Rejected: %s # Сообщение об ошибке при отклонении письма. Здесь %n — перевод строки (CRLF), # %r — причина отказа, %s — тема оригинального письма, %t — адрес получателя #rejection_reason = Your message to <%t> was automatically rejected:%n%r
Передача сообщений от Dovecot к Postfix
Почтовые клиенты MUA могут отправлять сообщения для доставки демону Postfix submission(s) или демону Dovecot submission(s). В первом случае Postfix должен сам аутентифицировать клиентов, используя Cyrus SASL или Dovecot SASL. Во втором случае аутенификацией клиентов занимается Dovecot — и потом передает SMTP-соединение Postfix, выступая в качестве прокси.
# файл конфигурации /etc/dovecot/dovecot.conf protocols = imap pop3 submission
# файл конфигурации /etc/dovecot/conf.d/10-master.conf service submission-login { inet_listener submission { port = 587 } inet_listener submissions { port = 465 } } service submission { # требуется установка пакета dovecot-submissiond }
# файл конфигурации /ect/dovecot/conf.d/20-submission.conf hostname = mail.example.com submission_relay_host = 127.0.0.1 submission_relay_port = 10025 submission_relay_trusted = yes
Мы здесь отправляем клиентов на 127.0.0.1:10025 — соответственно, нужно запустить Postfix демона, который будет прослушивать tcp-сокет 127.0.0.1:10025.
127.0.0.1:10025 inet n - n - - smtpd
-o syslog_name=postfix/msa_auth_dovecot
-o mynetworks=0.0.0.0/0
-o smtpd_tls_security_level=none
-o smtpd_client_restrictions=
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=reject_sender_login_mismatch
-o smtpd_relay_restrictions=permit_mynetworks,reject_unauth_destination
-o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain
-o smtpd_authorized_xclient_hosts=127.0.0.1,localhost
# Для «своих» почтовых клиентов, которые прошли аутентификацию
-o header_checks=pcre:/etc/postfix/msa_header_checks
Проверка существования «своих» пользователей
Postfix может сам доставлять почту для «своих» пользователей, используя транспорт local и virtual. А может отправлять их Dovecot по протоколу LMTP — через unix-сокет либо по сети. И тогда распределением сообщений по почтовым ящикам пользователей будет заниматься Dovecot — этот вариант предпочтительнее.
# Сообщения для локальных пользователей отправлять Dovecot по протоколу # LMTP через unix-сокет /var/spool/postfix/private/dovecot-lmtp local_transport = lmtp:unix:private/dovecot-lmtp virtual_transport = lmtp:unix:private/dovecot-lmtp
Но в этом случае возникают трудности с проверкой сущестования виртуального пользователя. Для проверки виртуальных пользователей — Postfix использует карты поиска virtual_alias_maps и virtual_mailbox_maps. Для проверки системных пользователей — Postfix использует файл /etc/passwd и карту $alias_maps.
# Карты локальных получателей сообщения (виртуальные) virtual_alias_maps = hash:/etc/postfix/virtual_alias_map virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox_map # Карты локальных получателей сообщения (системные) local_recipient_maps = proxy:unix:passwd.byname $alias_maps
Если сам Postfix не занимается доставкой почты в локальные ящики — ему не обязательно знать, какие пользователи и почтовые ящики существуют. Но если мы используем ограничение reject_unlisted_recipient — карты поиска virtual_alias_maps и virtual_mailbox_maps все-таки придется создавать. И данные почтовых пользователей придется хранить в двух местах — для Postfix и Dovecot.
# Ограничения на этапе команды RCPT TO (spam) smtpd_recipient_restrictions = .......... # Отклонять почту на адреса, для которых нет почтовых ящиков reject_unlisted_recipient
Проще всего эта проблема решается, если использовать карту поиска mysql — вся информация хранится в одной таблице базы данных MySQL. И выполнять SQL-запросы к этой базе могут как Postfix, так и Dovecot. Трудности возникают, если Postfix использует карту поиска hash, а Dovecot хранит пользователей в файле passwd-file. Для создания почтового пользователя — нужно добавить запись в файл карты поиска virtual_mailbox_maps + добавить запись в Dovecot файл passwd-file.
Скрипт для добавления нового виртуального почтового пользователя
#!/bin/bash set -e # Проверка числа аргументов if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then echo "Использование: $0 email password [aliases]" exit 1 fi EMAIL="$1" PASSWORD="$2" ALIASES="${3:-}" PASS_FILE='/etc/dovecot/users' MAIL_BASE='/var/vmail' VMAIL_UID=2000 VMAIL_GID=2000 MAILBOX_MAP='/etc/postfix/virtual_mailbox_map' ALIASES_MAP='/etc/postfix/virtual_alias_map' # Проверка на существование пользователя if grep -q "^$EMAIL:" "$PASS_FILE"; then echo "Ошибка: Пользователь $EMAIL уже существует в $PASS_FILE" exit 1 fi # Получение логина и домена из адреса почты USER_PART=$(echo "$EMAIL" | cut -d@ -f1) DOMAIN_PART=$(echo "$EMAIL" | cut -d@ -f2) # Полный путь к ящику для passwd-file Dovecot MAILDIR_DOVECOT="$MAIL_BASE/$DOMAIN_PART/$USER_PART" # Относительный путь для карты поиска Postfix MAILDIR_POSTFIX="$DOMAIN_PART/$USER_PART/" # Создание директорий почтового ящика (обычно этого не # требуется, Dovecot сам создает необходимые директории, # главное — чтобы была запись в passwd-file) mkdir -p "$MAILDIR_DOVECOT"/{cur,new,tmp} chown -R vmail:vmail "$MAILDIR_DOVECOT" chmod -R 770 "$MAILDIR_DOVECOT" # Формирование поля extra_fields EXTRA_FIELDS="userdb_mail=maildir:$MAILDIR_DOVECOT" # Формирование строки пользователя для passwd-file ENTRY="$EMAIL:{PLAIN}$PASSWORD:$VMAIL_UID:$VMAIL_GID:::::$EXTRA_FIELDS" # Добавление строки пользователя в passwd-file echo "$ENTRY" >> "$PASS_FILE" # Добавление строки в /etc/postfix/virtual_mailbox_map echo -e "$EMAIL\t$MAILDIR_POSTFIX" >> "$MAILBOX_MAP" postmap "$MAILBOX_MAP" # Обработка алиасов, если они предоставлены if [ -n "$ALIASES" ]; then # разбить $ALIASES по запятой и поместить в массив IFS=',' read -ra ARR <<< "$ALIASES" for a in "${ARR[@]}"; do # убираем пробелы на всякий случай ALIAS=$(echo "$a" | xargs) # если алиас уже есть — обновим, иначе добавим if egrep -q "^$ALIAS\s+" "$ALIASES_MAP"; then sed -iE "s/^$ALIAS\s+\S+$/\0,$EMAIL/" $ALIASES_MAP else echo -e "$ALIAS\t$EMAIL" >> $ALIASES_MAP fi done postmap "$ALIASES_MAP" fi
Скрипт для синхронизации записей из passwd-file с картой поиска
#!/bin/bash set -e PASS_FILE='/etc/dovecot/users' MAILBOX_MAP='/etc/postfix/virtual_mailbox_map' TMP_MAILBOX=$(mktemp) # Обработка строк из passwd-file while IFS=: read -r EMAIL TMP1 TMP2 TMP3 TMP4 TMP5 TMP6 EXTRA_FIELDS; do # Извлечение username и domain из email USER_PART=$(echo "$EMAIL" | cut -d@ -f1) DOMAIN_PART=$(echo "$EMAIL" | cut -d@ -f2) # Извлечение пути к ящику (userdb_mail=) MAIL_PATH=$(echo "$EXTRA_FIELDS" | grep -oE 'userdb_mail=maildir:[^ ]+' | cut -d= -f2 | sed 's|^maildir:||') # Получение относительного пути (относительно /var/vmail) REL_PATH=$(echo "$MAIL_PATH" | sed 's|^/var/vmail/||') # Запись в карту почтовых ящиков if [ -n "$REL_PATH" ]; then echo -e "$EMAIL\t$REL_PATH" >> "$TMP_MAILBOX" fi done < "$PASS_FILE" # Перезапись файла карты поиска mv "$TMP_MAILBOX" "$MAILBOX_MAP" # Создание индексированной карты postmap "$MAILBOX_MAP"
- Postfix и Dovecot. Установка и настройка. Часть 2 из 2
- Dante. Установка и настройка прокси-сервера
- Установка WireGuard на Ubuntu 20.04 LTS. Часть вторая из двух
- Установка WireGuard на Ubuntu 20.04 LTS. Часть первая из двух
- Установка OpenVPN на Ubuntu 18.04 LTS. Часть 12 из 12
- Установка OpenVPN на Ubuntu 18.04 LTS. Часть 11 из 12
- Установка OpenVPN на Ubuntu 18.04 LTS. Часть 10 из 12
Поиск: Linux • Клиент • Конфигурация • Настройка • Сервер • Postfix • Dovecot • SMTP • POP3 • IMAP • Почта






