Легковесный почтовый сервер с хранением учетных данных в базе Postgresql

До этого я рассказывал исключительно о монстроидальном комплексе совместной работы под названием Zimbra и вы наверное поняли, что запустить его на небольшом VPS у вас не получится, так как он потребляет просто огромное количество системных ресурсов. Но, что же делать, если требуется собственный почтовый сервер и вы сильно ограничены в системных ресурсах? Правильный ответ, это собрать его самому из Opensource-компонентов и полученный результат удивит вас своей легковесностью.

 
 
Логотип GITA-DEV

Автор: Черноусов Антон aka Gita-Dev
Опубликовано: 25 Июл 2018 (последние правки 1 месяц)

acl aptitude debian dev git install live mta pattern pgsql postgres postgresql smtp tar ubuntu

Начнем с создания хранилища для учетных данных пользователей почтового сервера и так как у меня планируется интеграция почтового сервера с моим сайтом на Django, то и схему данных для почтового сервера я опишу в виде модели данных для приложения core:

# Домены обслуживаемые сервером
class MailDomains(models.Model):
  domain = models.CharField(max_length=255,verbose_name="Обслуживаемый почтовый домен", unique = True)

  class Meta:
    verbose_name = "Почтовые домены обслуживаемые сервером"
    verbose_name_plural = "Почтовые домены обслуживаемые сервером"
    ordering = ("domain",)

# Почтовые аккаунты
class MailAccount(models.Model):
  login = models.CharField(max_length=255,verbose_name="Логин пользователя", unique = False)
  password = models.CharField(max_length=255,verbose_name="Пароль пользователя", unique = False)
  domain = models.ForeignKey(MailDomains,on_delete=models.CASCADE,verbose_name="Почтовый домен")   

  class Meta:
    verbose_name = "Почтовые домены обслуживаемые сервером"
    verbose_name_plural = "Почтовые домены обслуживаемые сервером"
    ordering = ("login",)
    unique_together = (("login", "domain"),)

На первом этапе такой структуры вполне достаточно, а после миграции в базе создаются две таблицы:

CREATE TABLE public.core_maildomains
(
  id integer NOT NULL DEFAULT nextval('core_maildomains_id_seq'::regclass),
  domain character varying(255) COLLATE pg_catalog."default" NOT NULL,
  CONSTRAINT core_maildomains_pkey PRIMARY KEY (id),
  CONSTRAINT core_maildomains_domain_key UNIQUE (domain)
)
WITH (
  OIDS = FALSE
)
TABLESPACE pg_default;

ALTER TABLE public.core_maildomains
  OWNER to web_portal;

-- Index: core_maildomains_domain_1deec3a4_like

-- DROP INDEX public.core_maildomains_domain_1deec3a4_like;

CREATE INDEX core_maildomains_domain_1deec3a4_like
  ON public.core_maildomains USING btree
  (domain COLLATE pg_catalog."default" varchar_pattern_ops)
  TABLESPACE pg_default;

CREATE TABLE public.core_mailaccount
(
  id integer NOT NULL DEFAULT nextval('core_mailaccount_id_seq'::regclass),
  login character varying(255) COLLATE pg_catalog."default" NOT NULL,
  password character varying(255) COLLATE pg_catalog."default" NOT NULL,
  domain_id integer NOT NULL,
  CONSTRAINT core_mailaccount_pkey PRIMARY KEY (id),
  CONSTRAINT core_mailaccount_login_domain_id_32957517_uniq UNIQUE (login, domain_id),
  CONSTRAINT core_mailaccount_domain_id_11dd627f_fk_core_maildomains_id FOREIGN KEY (domain_id)
    REFERENCES public.core_maildomains (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE NO ACTION
    DEFERRABLE INITIALLY DEFERRED
)
WITH (
  OIDS = FALSE
)
TABLESPACE pg_default;

ALTER TABLE public.core_mailaccount
  OWNER to web_portal;

-- Index: core_mailaccount_domain_id_11dd627f

-- DROP INDEX public.core_mailaccount_domain_id_11dd627f;

CREATE INDEX core_mailaccount_domain_id_11dd627f
  ON public.core_mailaccount USING btree
  (domain_id)
  TABLESPACE pg_default;

Эти таблицы будут использоваться компонентами почтового сервера для авторизации пользователей и начнем мы с IMAP-сервера Dovecot.

Настройка IMAP-сервера Dovecot

Устанавливаем необходимые для работы пакеты:

# aptitude install dovecot-imapd dovecot-pgsql dovecot-sieve

Настройка IMAP-сервера Dovecot в простейшем случае сводится к следующей последовательности:

  • Отключаем PAM-авторизацию
  • Настраиваем авторизацию с использованием Postgresq
  • Настраиваем шифрование трафика

Sieve-фильтры и подобное украшательство мы рассмотрим чуть позже, а сейчас наша задача просто настроить работоспособный IMAP-сервер. Для работы IMAP-сервера нам потребуется знать id и git пользователя dovecot и создать каталог для хранения почты в формате maildir, а начнем мы с получения сведений о системном пользователе dovecot (используется команда id):

# id dovecot 
uid=109(dovecot) gid=115(dovecot) группы=115(dovecot)

Я предпочитаю хранить почту пользователей в каталоге /home/mailboxes (ну тут на вкус и цвет как говорится):

# mkdir /home/mailboxes
# chown -R dovecot:dovecot /home/mailboxes
# usermod -G mail dovecot

Не забывайте устанавливать владельца на каталоги. После того как мы подготовили инфраструктуру, мы начинаем править конфигурационные файлы Dovecot.

В файле /conf.d/10-auth.conf измените следующие параметры:

disable_plaintext_auth = no
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
auth_username_format = %Lu
auth_mechanisms = plain login digest-md5 cram-md5
##!include auth-master.conf.ext
#!include auth-system.conf.ext
!include auth-sql.conf.ext

Мы настроили базовые параметры авторизации, разрешили передачу паролей в виде PLAIN-TEXT (на этапе тестирования), отключили системную авторизацию и подключили конфигурационный файл в котором опишем использование SQL-базы данных для авторизации.

В файле /conf.d/10-mail.conf соответственно изменяем:

mail_location = maildir:~/Maildir
first_valid_uid = 109 
last_valid_uid = 109
first_valid_gid = 115 
last_valid_gid = 115

Мы настроили ограничения по uid и gid с которыми может работать наш IMAP-сервер и задали расположение каталогов maildir.

Файл /dovecot-sql.conf.ext содержит основную информацию о взаимодействии с базой данных:

driver = pgsql
connect = host=127.0.0.1 dbname=web_portal_db user=web_portal password=xxxPasswordxxx
default_pass_scheme = PLAIN
user_query = \ 
   SELECT '/home/mailboxes/' || login || '@' ||domain AS home, '109' AS uid, '115' AS gid\ 
      FROM core_mailaccount INNER JOIN core_maildomains ON (core_mailaccount.domain_id = core_maildomains.id) \ 
      WHERE login='%n' AND domain='%d';
password_query = \ 
   SELECT login || '@' || domain AS user, password, '/home/mailboxes/' || login || '@' ||domain AS userdb_home,\ 
      '109' AS userdb_uid, '115' AS userdb_gid FROM core_mailaccount \ 
      INNER JOIN core_maildomains ON (core_mailaccount.domain_id = core_maildomains.id) \ 
      WHERE login='%n' AND domain='%d';

И в конце концов разрешаем нашему IMAP-серверу принимать входящие подключения /dovecot.conf:

listen = *, ::

Теперь вы можете подключаться к IMAP-серверу при помощи почтового клиента.

Средства диагностики и отладки

Запрос системной информации о IMAP-пользователе:

# doveadm user anton@gita-dev.ru 
field  value 
uid    109 
gid    115 
home   /home/mailboxes/anton@gita-dev.ru 
mail   mbox:~/mail:INBOX=/var/mail/anton@gita-dev.ru

Тестирование авторизации:

# doveadm auth test -x service=imap -x rip=127.0.0.1 anton@gita-dev.ru 
Password: 
passdb: anton@gita-dev.ru auth succeeded 
extra fields: 
 user=anton 
 original_user=anton@gita-dev.ru

Ну и естественно по всем нестандартным ситуациям смотрите логи системы и IMAP-сервера.

Настройка почтового MTA сервера EXIM

Можно конечно использовать любой другой почтовый MTA сервер, но на мой взгляд единственным конкурентом EXIM является Postfix и он не намного легче в конфигурировании, а может даже и сложнее.

Начинаем как обычно с установки необходимых пакетов:

# aptitude install exim4-daemon-heavy

Сконфигурировать его необходимо аналогично EXIM, с использованием авторизации и запретом всех возможных RELAY (только авторизованные пользователи могут отправлять почту на сторонние необслуживаемые домены). В Debian и в Ubuntu настройка Exim реализована довольно необычно и в самом простом случае вы можете использовать псевдографический конфигуратор:

# dpkg-reconfigure exim4-config

Все, что вы настроили в графическом режиме (можно построить довольно тривиальные почтовые решения) будет записано в файл параметров update-exim4.conf.conf, но я честно говоря вообще никогда не встречал людей которые бы использовали такой подход к конфигурированию exim и обычно поддерживается один обычный конфигурационный файл, поэтому я смело удаляю все содержимое conf.d и переписываю демон запускающий exim, так как он при каждом запуске перестраивает конфигурацию согласно /etc/exim4/update-exim4.conf.conf (Exim в Debian 9 это старый init-скрипт и поэтому будьте аккуратнее).

В файле /etc/init.d/exim4 убираем все, что связано с regenerate exim4.conf:

case "$1" in 
 start) 
   log_daemon_msg "Starting MTA" 
   # regenerate exim4.conf 
   start_exim 
   log_end_msg 0 
   warn_paniclog 
   ;; 
 stop) 
   log_daemon_msg "Stopping MTA" 
   stop_exim 
   log_end_msg 0 
   warn_paniclog 
   ;; 
 restart) 
   # check whether newly generated config would work 
   log_daemon_msg "Stopping MTA for restart" 
   stop_exim 
   isconfigvalid 
   log_end_msg 0 
   sleep 2 
   log_daemon_msg "Restarting MTA" 
   start_exim 
   log_end_msg 0 
   warn_paniclog 
   ;; 
 reload|force-reload) 
   log_daemon_msg "Reloading $NAME configuration files" 
   log_end_msg 0 
   warn_paniclog 
   ;; 
 status) 
   status 
   ;; 
 force-stop) 
   kill_all_exims $2 
   ;; 
 *) 
   echo "Usage: $0 {start|stop|restart|reload|status|force-stop}" 
   exit 1 
   ;; 
esac 
 
exit 0

И теперь мы можем приступать к созданию нашей собственной конфигурации (при обновлении пакета имейте в виду, что мы внесли кардинальные изменения в систему запуска и конфигурирования MTA).

Инструкций по настройке EXIM в интернете полно и наша установка довольно типовая, а главное максимально простая, так как она пока без особых наворотов (алиасы, антивирус, антиспам и т.п.), просто представляю вашему вниманию работающий конфигурационный файл /etc/exim4/exim4.conf:

hide pgsql_servers = 127.0.0.1/web_portal_db/web_portal/xxxPasswordxxx 
 
domainlist local_domains = ${lookup pgsql{SELECT domain FROM public.core_maildomains WHERE domain='${domain}';}} 
domainlist relay_to_domains = ${lookup pgsql{SELECT domain FROM public.core_maildomains;}} 
hostlist relay_from_hosts = 127.0.0.1/8 
 
host_lookup = * 
rfc1413_hosts = * 
rfc1413_query_timeout = 0s 
allow_domain_literals = false 
 
smtp_banner = mail.gita-dev.ru ESMTP EXIM 
local_interfaces = 127.0.0.1:94.177.204.179 
qualify_domain = mail.gita-dev.ru 
qualify_recipient = mail.gita-dev.ru 
primary_hostname = mail.gita-dev.ru 
 
exim_user = dovecot 
exim_group = dovecot 
dsn_from = Mail Delivery System <Mailer-Daemon@$qualify_domain> 
 
# Списки доступа 
acl_smtp_mail = acl_check_mail 
acl_smtp_rcpt = acl_check_rcpt 
acl_smtp_data = acl_check_data 
 
begin acl 
 
acl_check_mail: 
 
 deny   condition = ${if eq{$sender_helo_name}{} {1}} 
      message = Say HELO first 
  
 warn condition = ${if eq{$sender_host_name}{} {1}} 
      set acl_m_greylistreasons = Host $sender_host_address lacks reverse DNS\n$acl_m_greylistreasons 
  
 accept 
 
acl_check_rcpt: 
 
 deny   message      = Restricted characters in address 
         domains      = +local_domains 
         local_parts  = ^[.] : ^.*[@%!/|] 
 
 deny   message      = Restricted characters in address 
         domains      = !+local_domains 
         local_parts  = ^[./|] : ^.*[@%!] : ^.*/\\.\\./ 
 
 accept local_parts  = postmaster 
         domains      = +local_domains 
 
 require verify       = sender 
 
 accept hosts        = +relay_from_hosts 
         control      = submission 
 
 accept authenticated = * 
         control      = submission 
 
 require message = relay not permitted 
         domains = +local_domains : +relay_to_domains 
 
 require verify = recipient 
 
 deny   message      = "HELO/EHLO required by SMTP RFC" 
         condition    = ${if eq{$sender_helo_name}{}{yes}{no}} 
 
 deny   condition    = ${if match{$sender_helo_name}{\N^\d+$\N}{yes}{no}} 
         hosts        = !127.0.0.1:!localhost:* 
         message      = "There can not be only numbers in HELO!" 
 
 deny   condition    = ${if eq{$sender_address}{}{yes}{no}} 
         hosts        = +relay_from_hosts 
         message      = "Your message have not return address" 
 
 deny   message  = "The use of IP is forbidden in HELO!" 
         hosts    = *:!+relay_from_hosts 
         condition = ${if eq{$sender_helo_name}\ 
                     {$sender_host_address}{true}{false}} 
 
 deny   condition = ${if eq{$sender_helo_name}\ 
                     {$interface_address}{yes}{no}} 
         hosts    = !127.0.0.1 : !localhost : * 
         message  = "The use of my IP is forbidden!" 
 
 accept 
 
acl_check_data: 
 
 warn   condition = ${if !def:h_Message-ID: {1}} 
         set acl_m_greylistreasons = Message lacks Message-Id: header. Consult RFC2822.\n$acl_m_greylistreasons 
 
 accept condition = ${if >={$message_size}{100000} {1}} 
          add_header = X-Spam-Note: SpamAssassin run bypassed due to message size 
 
 warn   spam      = nobody/defer_ok 
          add_header = X-Spam-Flag: YES 
  
 accept condition = ${if !def:spam_score_int {1}} 
          add_header = X-Spam-Note: SpamAssassin invocation failed 
 
 warn   add_header = X-Spam-Score: $spam_score ($spam_bar)\n\ 
                       X-Spam-Report: $spam_report 
 
 deny      condition = ${if >{$spam_score_int}{100} {1}} 
          message  = Your message scored $spam_score SpamAssassin point. Report follows:\n\ 
                     $spam_report 
 
 accept 
 
begin routers 
 
dnslookup: 
  driver = dnslookup 
  domains = !+local_domains 
  transport = remote_smtp 
  ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 
  no_more 
 
dovecot_user: 
  driver = accept 
  condition = ${if eq{} {${lookup pgsql{SELECT login FROM core_mailaccount INNER JOIN core_maildomains ON (core_mailaccount.domain_id 
= core_maildomains.id) WHERE login='${local_part}' AND domain='${domain}';}}}{no}{yes}} 
  transport = dovecot_delivery 
 
begin transports 
 
remote_smtp: 
     driver = smtp 
     interface = 94.177.204.179 
 
dovecot_delivery: 
   driver = pipe 
   command = /usr/lib/dovecot/dovecot-lda -d $local_part@$domain -f $sender_address 
   message_prefix = 
   message_suffix = 
   delivery_date_add 
   envelope_to_add 
   return_path_add 
   log_output 
   user = dovecot 
 
address_pipe: 
   driver = pipe 
   return_output 
 
address_reply: 
   driver = autoreply 
 
begin retry 
 
*                     *          F,2h,15m; G,16h,1h,1.5; F,4d,6h 
 
begin rewrite 
 
begin authenticators 
 
auth_plain: 
 driver = dovecot 
 public_name = PLAIN 
 server_socket = /var/run/dovecot/auth-client 
 server_set_id = $auth1 
 
auth_login: 
 driver = dovecot 
 public_name = LOGIN 
 server_socket = /var/run/dovecot/auth-client 
 server_set_id = $auth1 
 
auth_cram_md5: 
 driver = dovecot 
 public_name = CRAM-MD5 
 server_socket = /var/run/dovecot/auth-client 
 server_set_id = $auth1

Единственное, что я кардинально поменял, это пользователя от имени которого работает Exim, и в моем случае они с Dovecot работают от имени одного и того же пользователя dovecot (в противном случае были проблемы с доставкой почты при помощи /usr/lib/dovecot/dovecot-lda).

Средства диагностики и отладки

Если вам потребуется вывести все текущие параметры с которыми работает EXIM, то вы можете использовать команду:

# exim -bP

На этом этапе простейший почтовый сервер собран и в дальнейшем мы его немного усовершенствуем.

Похожие статьи

Установка и обновление сертификата Let's encrypt для почтового сервера Zimbra

Установка и обновление сертификата Let's encrypt для почтового сервера Zimbra

Эта заметка не претендует на истину в первой инстанции и даже обязательно должна быть дополнена некоторыми механизмами авто-продления сертификатов раз в месяц, но так как последнее время на поддержке у меня не осталось Zimbra-серверов я дополню ее по по возможности. Представленный механизм полностью рабочий и многократно апробировался в реальной боевой среде.


Установка и настройка почтового сервера Zimbra (часть первая - основы установки и настройки)

Установка и настройка почтового сервера Zimbra (часть первая - основы установки и настройки)

Сразу предупреждаю, если вам нужен простой почтовый сервер для приема и отправки почты, то лучше собрать его из OpenSource компонентов и он будет отлично работать, но если вам нужна мощная система коллективной работы с поддержкой LDAP-авторизации, календарей, общих почтовых папок и прочих колабрэйшн плюшек которые имеются в проприетарном почтовом сервере MS Exchange, то вам прямая дорога к внедрению Zimbra


Создание почтового домена в Zimbra и подключение почтового клиента

Создание почтового домена в Zimbra и подключение почтового клиента

Как я уже говорил ранее, у меня опять подвернулся проект связанный с Zimbra Collabration Suite и я решил задокументировать типовые операции по управлению почтовым сервером и некоторые подводные камни и грабли с которыми я сталкиваюсь практически на каждом внедрении этого почтового сервера.


Настройка DKIM, SPF, DMARK. Повышаем уровень доверия к почтовому серверу.

Настройка DKIM, SPF, DMARK. Повышаем уровень доверия к почтовому серверу.

Задачи по настройке DKIM/SPF/DMARK и т.п. довольно распространены на фриланс-сайтах, но в большинстве случаев там все в итоге сводится к настройте мне почтовый сервер для спам рассылок чтобы мои уникальные предложения не попадали в спам. Такие товарищи мне не товарищи и сразу идут лесом, да и кстати сказать, из за них теперь для настройки банального почтового сервера требуется совершать столько приседаний. Чтобы не забыть последовательность действий по настройке почтового сервера письма от которого не будут сразу уходить в спам, я решил написать эту небольшую инструкцию.


Простой SMTP-сервер (с авторизацией) для почтовой рассылки в дополнение к Yandex почта для домена

Простой SMTP-сервер (с авторизацией) для почтовой рассылки в дополнение к Yandex почта для домена

Яндекс почта для домена, это неплохое решение для небольшой компании у которой нет технического специалиста для поддержки собственного почтового сервера. Вам не придется заботиться о мониторинге того, что ваш сервер может попасть в черный список и еще ряда факторов, но в этой бочке меда есть одна большая ложка дегтя которая перевешивает все положительные факторы.


Отзывы и комментарии