В сети довольно мало информации о подобной интеграции. То, что задача решаема — свидетельствуют редкие комментарии на сайтах, но готового решения я не нашёл. Возможно, это обусловлено тем, большинство используют postfix/exim.
Цели статьи:
- Описать методику решения задачи, чтобы было проще тем, кто пойдёт по моим следам;
- Отвлечь внимание коммьюнити от старого-доброго-древнего postfix и компании.
Введение
В процессе переезда готовой веб/почтовой/серверной инфраструктуры со старого сервера на новые сервера, я столкнулся с необходимостью развернуть почтовую инфраструктуру заново.
Исходная почтовая система разворачивалась в 2012м году. Ansible тогда только зарождался. Системы конфигурирования не были так распространены как сейчас. Автоматизацию работы я тогда любил меньше, чем сейчас, поэтому всё конфигурировалась руками.
Новая система должна была быть построена на докере (что означает максимально подробное описание построения системы "на будущее") и работать в докер кластере.
Конфиги старой системы сохранились, но мне очень не хотелось возвращаться к "dnl" sendmail, куче строк описания логики postfix, непонятному процессу настройки работы amavis+spamasassin, множеству памяти, которое требовалось amavis+spamasassin. Было принято решение поискать альтернативы.
Чтобы никто не говорил "используешь какой-то маргинальный софт потому, что не осилил postfix", я уточню: осилил. Изначальная система состояла из postfix+dovecot+amavisd-new+spamasassin+dkim-proxy+razor+pyzord+dcc.
Забегая вперёд: конечная система состоит из opensmtpd+dbmail+dkim-proxy+rspamd.
Немного теории
opensmtpd — это легковесный почтовый демон, с элементарным процессом конфигурирования от команды OpenBSD. Правила конфигрурированию настолько просты и интуитивно понятны, что сразу запоминаются и позволяют описать десятью строчками полную логику работы почтового сервера.
dbmail — хранилище почты с pop/imap интерфейсом. Основная особенность — хранение почты в базе, что позволяет создавать произвольное число точек входа.
rspamd — почтовая анти-спам система от разработчиков из Рамблера.
Ещё альтернативы? Почему не Zimbra? Почему не ещё какое-то готовое решение вида mail-in-a-box, которое содержит в себе все нужные компоненты?
Zimbra отпадала по причине Java — не хотелось расходовать ресурсы.
mail-in-a-box — опять же все те же postfix и компания.
Описание проблемы
opensmtpd — замечательный демон, но у него есть недостатки, которые не позволяют легко его интегрировать с rspamd.
Это:
Отсутствие поддержки протокола "milter". Данный протокол (расширение) позволил бы интегрироваться с rspamd стандартными средствами.
Отсутствие поддержки фильтров. Разработчик rspamd для интеграции рекомендует использовать фильтры opensmtpd. Разработка же фильтров идёт уже который год, но до сих пор не в продакшене. Существует даже фильтр для rspamd, но до сих пор в отдельной ветке на github.
После определённых доработок кода откомпилировать и запустить данный фильтр у меня получилось, но он выдавал ошибки в процессе работы и дальше не было желания разбираться в коде.
- Отсутствие метода приёма почты кроме как по почтовым протоколам.
Так же, ситуация усложнялась следующими моментами:
Удалённость сервисов друг от друга: демоны работают в докер-контейнерах с разными IP. Фактически, контейнер с opensmtpd и контейнер с rspamd можно представить как работающие на разных серверах.
- Разнородность ПО: opensmtpd есть в стабильной версии Alpine Linux (версия linux, которую часто используют в качестве базового образа Docker), но там нет rspamd. И наоборот: rspamd есть в нестабильной версии Alpine, но там не работает opensmtpd.
Итак, в качестве исходной задачи, мы имеем opensmtpd, который на порту 25 ждёт входящую почту и на порту 10029 ждёт почту, которую проверил rspamd.
Решение проблемы
В opensmtpd существуют mda. Вероятно, это расшифровывается как mail delivery agent. Перенаправление письма в mda вызывает запуск внешней программы. Пример правила перенаправления письма:
accept tagged IN from any for domain <domains> userbase <virtual_users> deliver to mda "/usr/local/bin/rspamd.sh %{sender} %{rcpt}" as admin
Здесь мы вызываем некий внешний скрипт, куда передаём адрес отправителя и адрес получателя.
В комплекте с rspamd идёт rspamc — это аналог почтового mta для rspamd. Данная программа позволяет принять на вход письмо, передать его rspamd по HTTP API и выдать ответ.
Интеграция mda и rspamd выглядит следующим образом
/usr/bin/rspamc --mime -h rspamd.example.ru:11333
На STDIN скрипту мы отправляем письмо, указываем адрес rspamd и параметром --mime говорим, что нужно добавить заголовки обработки письма (по которым в дальнейшем будет определяться спам).
После того, как письмо обработано rspamd, его нужно каким-то образом направить обратно в opensmtpd. Но стандартный mta (в комплекте с opensmtpd) умеет отправлять только на порт 25 (мы же ждём проверенное письмо на другом порту).
Решение: формировать общение по почтовому протоколу самим (хорошо, он несложный) и отправлять данные на нужный порт используя netcat.
Итоговый mda скрипт rspamd.sh интеграции opensmtpd и rspamd
#!/usr/bin/env sh
mail_file=$(mktemp)
echo 'HELO localhost' >> $mail_file
echo "MAIL FROM: <$1>" >> $mail_file
echo "RCPT TO: <$2>" >> $mail_file
echo 'DATA' >> $mail_file
/usr/bin/rspamc --mime -h rspamd.example.ru:11333 >> $mail_file
echo '' >> $mail_file
echo '.' >> $mail_file
echo 'QUIT' >> $mail_file
cut_file=$(mktemp)
sed '/Delivered-To/d' $mail_file > $cut_file
rm "$mail_file"
count=0; IFS=''; while read -r line ; do if [ "$count" -gt "5" ]; then sleep 0.05; else sleep 0.1; fi; echo "$line"; count=$((count+1)); done < "$cut_file" | netcat localhost 10029
rm "$cut_file"
Особенности данного скрипта:
- Отправлять данные через netcat следует с задержками после каждой строки этапа установки соединения. Для этого мы делаем задержки между отдачей строк.
- Из исходного письма следует вырезать заголовок "Delivered-To", который добавляет opensmtpd. Иначе, opensmtpd посчитает данное письмо зациклившимся.
Решение проблемы совмещения opensmtpd и rspamd в одном контейнере: использование Docker образа на основе Debian sid, куда подключается репозиторий rspamd и из него устанавливается свежая версия. Пример установки:
wget -O- https://rspamd.com/apt-stable/gpg.key | apt-key add - \
&& echo "deb http://rspamd.com/apt-stable/ sid main" > /etc/apt/sources.list.d/rspamd.list \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
rspamd
Итоги
В результате перехода с postfix+amavis на opensmtpd+rspamd я получил простоту конфигурации сервиса, малое потребление памяти и меньше спама на почту.
Реализацию итогового Docker контейнера можно посмотреть здесь.