Многодоменная почтовая система на базе postfix + cyrus-imapd + MySQL

Thanks: 195
Say if this page is useful for you
Author: Sergey Popov (aka azure)
Published on 2009.08.11
Last modified on 2010.10.08
  1. Введение
  2. Как вообще работает электронная почта?
  3. Создание базы данных пользователей в MySQL
  4. Настройка аутентификации пользователей в cyrus-imapd
  5. Настройка аутентификации пользователей в postifx
  6. postfix local_recipient_maps + MySQL
  7. Как этим пользоваться?
  8. Заключение

Введение

Эта статья поможет в создании многодоменной почтовой системы. В своё время мне пришлось немало прочитать документации и HOWTO, чтобы это реализовать. Так что целью публикации этой статьи будет простое (на сколько это возможно) изложение необходимых процедур для введения в строй почтовой системы.

Как вообще работает электронная почта?

Этот раздел можно пропустить, если Вы понимаете, как происходит отправление, доставка и получение электронных писем. Если нет, то в этом разделе я попытаюсь понятным языком об этом рассказать.

Скорее всего читатель знает, что получение писем производится как правило по протоколу pop3 или imap, а отправление — по протоколу smtp. И это не обязательно должен быть один почтовый сервер для вашего ящика. Да и что такое вообще Ваш адрес электронной почты? Сейчас постараюсь прояснить.

Адрес электронной почты — это аналог вашего места жительства для обычной почты. Записывается он в формате username@example.org, где username — имя адресата, а example.org — домен, которому принадлежит этот адресат. Когда кто-то отправляет Вам письмо, его почтовая система (smtp сервер, если быть точнее) принимает у него письмо и выполняет следующие действия (упрощенно):

  1. помещает письмо в очередь доставки с присвоением ему уникального идентификатора;
  2. запрашивает через систему DNS адрес узла, обслуживающего электронную почту домена example.org (т.н. MX запись). При отсутствии MX-записи, запрашивает адрес непосредственно домена;
  3. пытается соединиться с smtp-сервером, обслуживающим домен example.org и передать ему письмо. В зависимости от успешности этого процесса письмо может быть доставлено как за пару минут так и за несколько дней. В случае возникновения ошибок отправителю будет об этом сообщено.

На этом, собственно, работа smtp-сервера отправителя заканчивается, и начинается работа почтовой системы получателя (а это уже будем мы). Вот что должно происходить на стороне получателя:

  1. smtp-сервер принимает входящее письмо;
  2. проверяет по базе алиасов, не нужно ли это письмо отправить по другому адресу;
  3. проверяет, есть ли такой получатель;
  4. если есть - кладет письмо ему в ящик, если нет, сообщает smtp-серверу отправителя сообщение о том, что такого пользователя в этой почтовой системе нет.

Необходимо понимать, что использование почтового ящика vasya@mail.ru не обязывает Вас использовать smtp-сервера mail.ru для отправки писем. Тут все точно так же, как и в обычной почте: какой адрес отправителя укажите, тот и прочитают, а отправить письмо можете через любое отделение связи (любой доступный вам smtp-сервер, который будет пересылать письма от Вас), либо можете адресату лично в почтовый ящик положить (напрямую соединяться с smtp-сервером получателя и передавать ему письмо, если это поддерживается Вашим почтовым клиентом). Как правило используют промежуточные smtp-сервера - MTA.

postfix — это MTA. Он отвечает за прием входящих писем и передачу их MDA для дальнейшего хранения и доствки пользователю по протоколу pop3/imap. postfix так же отвечает за прием и передачу исходящих от локальных пользователей писем другим MTA, обслуживающим почтовые домены получателей.

cyrus — это MDA. Он получает от MTA письма по протоколу LMTP или иными механизмами, складывает их в папки пользователей и позволяет пользователям получать к ним доступ по одному из поддерживаемых протоколов (pop3 или imap).

MySQL — это сервер баз данных, который мы будем использовать для хранения учетных данных пользователей.

Создание базы данных пользователей в MySQL

После того, как Вы установили сервер баз данных MySQL, используя консольную утилиту mysql залогинтесь как суперпользователь:

$ mysql -u root -p

Введите пароль пользователя root (не системный суперпользователь, а пользователь root в MySQL!!!). По умолчанию этот пароль пустой. Создайте базу данных с именем mail и пользователя mailadm с паролем mailpass, который имеет полный доступ к этой базе данных:

mysql> create database mail;
mysql> grant all on mail.* to mailadm identified by 'mailpass';

Далее, сделайте активной вновь созданную базу данных и создайте там таблицу по следующему шаблону:

mysql> use mail;

mysql> DROP TABLE IF EXISTS `users`;
mysql> CREATE TABLE `users` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL default '',
  `password` varchar(255) NOT NULL default '',
  `active` tinyint(1) NOT NULL default '1',
  PRIMARY KEY  (`id`),
  index (name),
  index (domain),
  index (active)
);

Давайте добавим пользователя cyrus для администрирования почты. Аналогично будут добавляться и другие пользователи:

mysql> insert into users (name,domain,password) values ('cyrus','my.host.name','cyruspass');

В общем случае my.host.name — это полное сетевое имя хоста. Посмотреть его можно в выводе программы uname:

$ uname -n

На этом оставим MySQL в покое, и перейдем к настройке других компонентов почтовой системы. Следует отметить, что пароли в БД, к сожалению, будут храниться простым текстом, что, безусловно, является потенциальной дырой в безопасности. К сожалению, это необходимое условия для использования надежных механизмов аутентификации пользователей с pop3/imap и smtp сервером.

Настройка cyrus-imapd

Установка

cyrus-sasl должен быть установлен с поддержкой базы данных MySQL. Впрочем, я думаю, что описанное здесь с минимальными изменениями может быть применено к использованию других средств хранения пользовательских учетных данных, например, SQLite или PostgreSQL.

Для управления ящиками пользователей должен быть установлен cyrus-imap-admin.

Настройка

В файле настройки /etc/imapd.conf должны присутствовать приведенные строки и не должно быть строк, закомментированных тройным шарпом (###, во всяком случае у меня их нет и система работает):

admins:                 cyrus
hashimapspool:          yes
allowanonymouslogin:    no

## set allowplaintext:  yes if you have problems with authentification
allowplaintext:         no

virtdomains:            yes
###defaultdomain:       exmaple.org
###loginrealms:         example.org localhost

sasl_pwcheck_method: auxprop
sasl_auxprop_plugin: sql
## possible values for sasl_auxprop_plugin 'mysql', 'pgsql', 'sqlite'.
sasl_sql_engine: mysql

# all possible values.
#sasl_mech_list: LOGIN PLAIN CRAM-MD5 DIGEST-MD5 NTLM
## or limit to CRAM-MD5 only
sasl_mech_list: CRAM-MD5

## change below to suit your setup.
sasl_sql_user: mailadm
sasl_sql_passwd: mailpass
sasl_sql_database: mail
sasl_sql_hostnames: localhost
sasl_sql_select: SELECT password FROM users WHERE name = '%u' and domain='%r' and active=1 limit 1
## you can try "WHERE name = '%u' and (domain='%r' or name='cyrus') and active=1 limit 1" if u have problems with logging in as user 'cyrus'

В системных логах должны показываться сообщения о ходе аутентификации. Так что всегда можно увидеть, какие параметры передаются в sasl. Помимо этого, в файле /etc/cyrus.conf должна быть подобная строка:

lmtpunix      cmd="lmtpd" listen="/var/imap/socket/lmtp" prefork=0

Настройка аутентификации пользователей в postfix

Базовая настройка postifx заключается в определении механизма доставки писем MDA. В файле /etc/postfix/main.cf должен быть корректно определен почтовый транспорт:

mailbox_transport = lmtp:unix:/var/imap/socket/lmtp

Также должны быть указаны следующие параметры (о назначении их читайте в документации postfix):

smtpd_sasl_auth_enable = yes

smtpd_recipient_restrictions =
        permit_mynetworks
        permit_sasl_authenticated
        reject_unauth_destination
smtpd_sasl_authenticated_header = yes

smtpd_sasl_path = smtpd

Т.к. мы указали smtpd_sasl_path = smtpd, нам необходимо создать файл smtpd с настройками параметров аутентификации в папке /etc/sasl2/ . Итак, vi (или какой там у вас любимый редактор) /etc/sasl2/smtpd и вписываем:

pwcheck_method: auxprop
auxprop_plugin: sql
sql_engine: mysql
sql_user: mailadm
sql_passwd: mailpass
sql_database: mail
sql_hostnames: localhost
sql_select: SELECT password FROM users WHERE name = '%u' and domain='%r' and active=1 limit 1

Ну и конечно же не забываем настроить myhostname, mydomain, myorigin, mydestination, alias_database и прочие параметры в файле /etc/postfix/main.cf, которые нужны Вам в работе.

postfix local_recipient_maps + MySQL

Представим себе такую ситуацию: удаленная почтовая система хочет передать нам письмо на некий адрес. Вначале удаленная почтовая система здоровается с нами (говорит: "helo i.am.a.remote.host", где вместо i.am.a.remote.host, согласно RFC, должно стоять FQDN. В зависимости от настроек, postfix может выполнить проверку на соответствие PTR записи для ip-адреса удаленного сервера сообщаемому в приветствии FQDN, а может и не делать никаких проверок. Поведение postfix при обработке helo/ehlo задается параметром smtpd_helo_restrictions (см. документацию postfix). Это первая стадия проверки.

Затем удаленная почтовая система говорит "mail from: it@example.com", т.е. почта отправляется с некого адреса. Наша почтовая система может проверить существование такого домена и\или ящика, но по умолчанию она этого не делает. За поведение postfix'а на этапе обработки поля "mail from" определяется параметром smtpd_sender_restrictions (см. документацию postfix)

И, наконец, идет проверка адреса назначения. Удаленная почтовая система говорит нам "rcpt to: info@example.com". Если example.com это мы, то хорошо бы знать, есть ли у нас такой ящик. Но наличие ящиков знает только cyrus. Поэтому, если не сделать дополнительных настроек, postfix будет принимать письмо, даже если такого ящика не существует. Получить сообщение от cyrus о том, что такого ящика не существует, postfix сможет лишь тогда, когда начнет класть в этот ящик письмо, но для того, чтобы положить, нужно вначале его принять.

Приняв письмо, и не доставив его адресату, postfix, согласно RFC, будет обязан выслать отправителю уведомление о том, что письмо не может быть доставлено. При рассылках спама обратный адрес обычно бывает сфабрикован. Если не проводится проверок адреса отправителя, и не проводится проводится проверка существования локального почтового ящика, наша почтовая система может тщетно пытаться отослать уведомление на поддельный (вероятно, не существующий адрес), порождая паразитный трафик. Бороться с этой проблемой надо, и первоочередным шагом будет сделать так, чтобы postfix знал своих адресатов "в лицо".

Табличка с учетными данными пользователей у нас храниться в MySQL, соответственно, по ней мы и будем определять наличие локальных почтовых ящиков. Для этого в main.cf нужно вписать

local_recipient_maps = $alias_maps mysql://etc/postfix/mysql-users.cf

А в файл /etc/postfix/mysql-users.cf вписать такие строки:

user = mailadm
password = mailpass
dbname = mail
query = select name from users where name='%u' and domain='%d' and active=1

Указание $alias_maps необходимо для того, чтобы принималась почта на алиасы (в первую очередь системные — root, postmaster etc. и алиасы локальных пользователей, конечно же). Ну а с MySQL, думаю, объяснений не надо. Postfix проинструктирован выполнить определенный запрос в базе данных и выдать ответ о наличии либо отсутствии такого почтового ящика.

Теперь при попытках отправить письмо на несуществующий локальный ящик postfix не будет принимать письмо в очередь доставки, а сообщит отправителю об ошибке:

rcpt to: it@example.com
550 5.1.1 : Recipient address rejected: User unknown in local recipient table

Как этим пользоваться?

Как же этой системой пользоваться? Как блокировать пользователей, как менять пароли, и как добавлять ящики, в конце концов? Об этом я и расскажу в этом разделе.

Добавление ящиков

Для добавления ящиков нужно использовать утилиту cyradm входящую в состав уже установленного вами cyrus-imap-admin:

$ cyradm --user cyrus localhost
Password: 
localhost> cm user.boss@example.org

cm — это сокращение от createmailbox. Вначале обязательно должно стоять 'user.' а затем уже название почтового ящика. Но это лишь создаст почтовый ящик в cyrus, а ведь нужно, чтоб пользователь мог получить туда доступ. Для этого нужно внести соответствующую запись в базу данных:

$ mysql -u mailadm -p mail
Enter password:

mysql> insert into users (name,domain,password) values ('boss','example.org','megapassword');

Этими действиями был создан почтовый ящик boss@example.org и учетная запись с логином boss@example.org и паролем megapassword. Эти учетные данные должны быть внесены в почтовый клиент как для получения писем по протоколам pop3 и/или imap так и для аутентификации с smtp-сервером для отправки писем.

Изменение паролей

Т.к. пароли хранятся в MySQL, изменять их следует обычными SQL-запросами. Для тех кто не в курсе, я приведу примеры, которые сэкономят пару десятков минут и избавят от чтения не всегда простой документации по SQL-запросам. Итак, изменим пароль пользователя boss@example.org на 'weakpassword':

$ mysql -u mailadm -p mail
Enter password:

mysql> update users set password='weakpassword' where name='boss' and domain='example.org';

Временная блокировка доступа к почтовой системе

В настройках cyrus-sasl мы везде использовали запросы SQL, которые возвращают данные только при active=1. Соответственно, чтоб пользователь не мог пройти аутентификацию для получения доступа к ящику, следует присвоить active значение 0. Делается это просто:

$ mysql -u mailadm -p mail
Enter password:

mysql> update users set active=0 where name='boss' and domain='example.org';

Или отключить целый домен от обслуживания:

mysql> update users set active=0 where domain='example.org';

Удаление пользователей и почтовых ящиков

Администрировать ящики необходимо уже известной Вам программой cyradm. Вот так будет выглядеть удаление почтового ящика boss@example.org:

$ cyradm --user cyrus localhost
localhost> sam user.boss@example.org cyrus all
localhost> dm user.boss@example.org
localhost> quit

$ mysql -u mailadm -p mail
Enter password:

mysql> delete from users where name='boss' and domain='example';

Обратите внимание, для того, чтобы удалить почтовый ящик в cyrus, вначале необходимо дать пользователю cyrus права командой sam (setaclmailbox)

Заключение

Надеюсь, это небольшое руководство поможет Вам выполнить свою нелегкую (но и не такую уж сложную) работу системного администратора. За подробностями настройки postfix и cyrus для своих конкретных нужд обращайтесь непосредственно к документации этих проектов.

Любые дополнения и замечания приветствуются. Пишите их в комментариях или свяжитесь со мной любым удобным для вас способом.

Comments:

shtorman: Название статьи исправте, postifx -> postfix
azure: спасибо, поправил
: Не щарю я пока ещё... Не ну то есть уже почти разбираюсь во всём, но cyrus не понял для чего... А вообще огромное спасибо!!!
Аноним: В первом запросе имя таблицы mail, а должно быть users
azure: В первом запросе база данных mail, а далее в базе данных mail таблица users. Или покажите какой запрос вы назвали первым, может, я недопонял.
Аноним №2 :): >В первом запросе имя таблицы mail, а должно быть users Он имеет в виду запрос: mysql> insert into mail (name,domain,password) values ('cyrus','my.host.name','cyruspass'); нужно: mysql> insert into USERS (name,domain,password) values ('cyrus','my.host.name','cyruspass');
azure: О! и правда! спасибо, поправил.
Antonio: А у меня нет файла :/var/imap/socket/lmtp что делать?
azure: Этот файл (а точнее - сокет) создает cyrus при запуске. Где именно создает - описано в файле конфигурации cyrus: /etc/cyrus.conf (параметр lmtp или lmtpunix). Убедитесь, что cyrus запущен!
Tim: Спасибо за статью! Было бы не плохо рассказать, как прицепить к postfix MailScanner, clamav, spamassassin.
azure: Tim, если Вы в состоянии описать это - я с удовольствием разместил бы эту статью здесь (с указанием авторства, естественно).
Tim: Нет, только учусь :-) Еще раз спасибо за статью.
key: А как в мускуле хранить MD5-хэши от паролей и работать непосредствнно с ними?
key: я так пологаю специально для этого ведь и есть пакет cyrus-sasl-md5
azure: key, там все непросто. Для аутентификации механизмами cram-md5 и digest-md5 почтовику требуется _знать_ исходный пароль. Очень приблизительно: клиент подключается к серверу, и сервер говорит, а ну ка, какой будет хеш пароля если к нему прибавить случайно сгенерированную строку "nS7dfe". Клиент, имея исходный пароль, добавляет к паролю строку, вычисляет хеш и передает на сервер. Сервер же, чтоб проверить правильность пароля, должен тоже знать исходный пароль. Так что тут проблема принципиального характера.
key: ясно, значит берется не хэш пароля, а хэк от пароль соль... странно кстати. Ведь обычно в БД не хранятся открытые пароли...
key: а скажем так Courier-IMAP умеет работать с мускулом? и хэшами, а не самими пролями?
azure: key, да и cyrus тоже может. Только в этом случае будут недоступны безопасные механизмы аутентификации (cram-md5, digest-md5). А с plain логином - можно хранить пароль, зашифрованные или PASSWORD() или хешем (на счет хеша не на 100% уверен). Принципиально возможно хранить в базе пароли не в чистом виде, а зашифрованные (не захешированные!). И имея ключ для расшифровки система аутентификации может узнать пароль в чистом виде и далее использовать для аутентификации клиента механизмами cram-md5 или digest-md5
Rage: Хорошая статья. Краткость явно Ваша сестра

Nickname:
Creative Commons License This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License