Pull to refresh

Легко и непринуждённо: почта и Jabber для пользователей вашего сайта

Reading time 12 min
Views 14K
Допустим, вы развиваете какой-нибудь сайт в интернете. У вас есть постоянные пользователи, кто-то заходит к вам от случая к случаю. И вы конечно же ищете способы удержания аудитории на вашем сайте, придумывая всякие оригинальные функции и оттачивая до блеска интерфейс. Почему бы тогда не использовать имя вашего сайта в качестве экспортной монеты? Можно ведь предоставить вашим пользователям в качестве дополнительного бонуса почтовый адрес и соответствующий адрес Jabber в вашем домене.

Конечно, я не предлагаю становится очередным провайдером почты. Это глупо, когда есть такие игроки, как gmail.com и yandex.ru. Кроме того, вы естественно можете использоваться услуги вышеупомянутых Google и Yandex для подключения своего домена к их сервисам. Но тогда вы не получите никакой интеграции с вашей базой пользователей и никакого контроля над доступной функциональностью.

Но есть простой и элегантный способ прозрачно совместить почту и Jabber в вашем домене с профилями пользователей на сайте. У пользователя при этом везде будет единый логин и единый пароль и не будет необходимости в совершении никаких дополнительных действий для подключения почты и Jabber аккаунта. Просто дополнительный бонус, идущий в комплекте с регистрацией на вашем портале.

Начнём с описания того, как это будет работать. Если у вас есть сайт, на котором можно регистрироваться, значит у вас конечно же есть БД пользователей с логином, email адресом и паролем для каждого (если вы хоть чуточку цените своих пользователей, то вместо паролей у вас будут необратимые хеши, но для данной статьи это не важно). Кроме того, логины у всех пользователей конечно же различны. Тогда можно настроить почтовый сервер, который почту для user@yourdomain.com будет перенаправлять на основной ящик пользователя из профиля, а так же Jabber сервер, который будет авторизовывать по JID user@yourdomain.com через вашу БД. Таким образом пользователи вашего сайта получат почтовый и Jabber адрес в вашем домене, совмещённые с их профилем. При этом вы не будете предоставлять почтовый ящик, только адрес. Это очень удобно для конечного пользователя, поскольку ящик у него всегда где-то уже есть и обычно нету смысла заводить ещё один. А вот красивый адрес, которым можно похвастаться, никогда не помешает. Единственное, нужно обязательно предусмотреть возможность отключения данной функциональности через профиль пользователя. Иначе такую систему можно будет очень эффективно использовать для рассылки спама.

Практическая сторона вопроса


Перейдём от теории к практике. Для поднятия почтового и Jabber сервисов вам потребуется любой Linux-сервер, однако в данной статье описывается Ubuntu Server 10.04. Кроме того, все инструкции будут аналогичны и для более старых версий Ubuntu, а так же для Debain.

В качестве почтового сервера имеет смысл использовать Postfix, как один из самых надёжных и при этом нетребовательных к ресурсам. В качестве XMPP (он же в просторечии Jabber) сервера — ejabberd. Эти два сервера практически никак не будут связаны между собой, так что начнём с настройки почты.

Настройка почтовой системы


Для установки Postfix в Ubuntu достаточно выполнить

sudo apt-get install postfix

Когда у вас спросят, какой тип настройки использовать — выберите интернет-сайт и введите какие-нибудь данные в ответ на дальнейшие вопросы от балды, чуть ниже будет готовый конфиг со всеми необходимыми настройками.

После установки необходимо настроить Postfix так, чтобы при запросе доставки письма на адрес someuser@yourdomain.com, Postfix смотрел бы в вашу БД, узнавал бы, есть ли там искомый пользователь и активирован ли для него почтовый адрес, и, если всё в порядке, — на какой адрес перенаправить письмо. После чего Postifx должен принять письмо и перенаправить его на основной почтовый адрес указанного пользователя.

Эту задачу можно решить встроенными средствами Postfix, однако заставлять почтовый сервер для каждого нового письма отправлять запрос в основную базу данных — это далеко не лучшая идея, поскольку это создаст лишнюю нагрузку на вашу БД пользователей, благо спама сейчас в интернете гуляет очень и очень много. Гораздо лучше пару раз в сутки выгружать нужную информацию из вашей основной БД в Postfix, благо вся эта информация — это таблица из двух полей: имя пользователя и его основной адрес почты.

Для перенаправления почты в Postfix служат различные alias_maps. Поскольку описываемая система никак не привязана к системным пользователям сервера, то будем использовать механизм виртуальных доменов и параметр virtual_alias_maps для перенаправления почты на основные адреса пользователей. В этом параметре как раз надо указать таблицу соответствия адреса пользователя адресу, на который нужно перенаправить корреспонденцию. Эта таблица будет создаваться на основании содержимого вашей основной БД пользователей специальным скриптом, о котором чуть ниже.

Пока же давайте окончательно разберёмся с необходимым опциями Postfix. Вся настройка Postfix осуществляется путём редактирования конфигурационного файла /etc/postfix/main.cf. Итак, для решения поставленной задачи, как я уже говорил, будет использоваться механизм виртуальных доменов, поскольку реальным пользователям почты на сервере не от куда взяться. Поэтому необходимо полностью отключить локальную доставку. Кроме того, пользователи не должны иметь никакого доступа к вашему SMTP серверу, поэтому стоит отключить авторизацию и SSL шифрование.

Вот содержание конфигурационного файла, который используется на одном из моих серверов:

# See /usr/share/postfix/main.cf.dist for a commented, more complete version
 
########################################################
## Основные параметры
 
smtpd_banner = $myhostname ESMTP server
biff = no
 
# Максимальный размер письма. По умолчанию всего 10Mb
message_size_limit = 204800000
 
# Основные параметры хоста
myhostname = mail.yourdomain.com
myorigin = $mydomain
mynetworks =
 127.0.0.0/8
 
mailbox_size_limit = 0
virtual_mailbox_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4 
append_dot_mydomain = no
readme_directory = no
 
##########################################################
## Настройки адресов и доставки
 
# Используем только виртуальных пользователей, поэтому всё локальное отключаем
mydestination =
local_recipient_maps =
relay_domains =
relay_recipient_maps =
transport_maps =
 
# Настройки виртуальных ящиков
virtual_mailbox_domains =
 $mydomain
# Локальных пользователей нет (если нужны - нужно ставить Dovecot)
# Однако карта обязательно должна существовать (пусть и пустая)
virtual_mailbox_maps =
 hash:/etc/postfix/mailboxes/local-mailboxes
# Собственно карты перенаправлений
virtual_alias_maps =
 hash:/etc/postfix/mailboxes/db-aliases
 hash:/etc/postfix/mailboxes/local-aliases
# Параметры локальной доставки. Если она нужна, то надо ставить Dovecot
virtual_mailbox_base = /var/mail
virtual_uid_maps = static:900
virtual_gid_maps = static:900
 
###########################################################
## Авторизация и шифрование - отключены
 
# SASL авторизация отключена
smtpd_sasl_auth_enable = no
 
# TLS отключён
smtpd_use_tls = no
 
###########################################################
## Ограничения и запреты
 
# Запретить ETRN команду
smtpd_etrn_restrictions = reject
 
# Запретить VRFY команду
disable_vrfy_command = yes
 
# Требовать наличие EHLO (HELO) команды
smtpd_helo_required = yes
 
# Всегда отклонять письма для всех неизвестных ящиков
smtpd_reject_unlisted_recipient = yes
 
# Ограничения на клиента - принимаем только если есть PTR (не жёсткий запрет)
smtpd_client_restrictions =
 permit_mynetworks
 reject_unknown_reverse_client_hostname
 permit
 
# Ограничения на HELO. Отклоняем письма от всех хостов, которые даже представиться не могут
smtpd_helo_restrictions =
 permit_mynetworks
 reject_invalid_helo_hostname
 reject_non_fqdn_helo_hostname
 reject_unknown_helo_hostname
 permit
 
# Ограничения на MAIL FROM. Отклоняем все письма, отправитель которых предоставил невалидный адрес
smtpd_sender_restrictions =
 reject_non_fqdn_sender
 reject_unknown_sender_domain
 permit
 
# Ограничения на RCPT TO. Принимаем только для известных нам адресов
smtpd_recipient_restrictions =
 reject_non_fqdn_recipient
 reject_unlisted_recipient
 permit_mynetworks
 reject_unauth_destination
 permit
 
# Ограничения на данные. Не принимаем в случае некорректной передачи
smtpd_data_restrictions =
 reject_unauth_pipelining

Подробней про настройку и различные опции Postfix можно почитать в интернете, для нашей же задачи важны три момента:
  1. Не стоит ставить слишком жёсткие ограничения с целью фильтрации спама. Оставьте эту прерогативу почтовым серверам, которые используют ваши пользователи. Ваша задача — просто переслать почту и отсеять только то, что спамом является 100%. Подробнее про правильную настройку Postfix с целью минимизации потока спама можно почитать в одной из моих прошлых статей. Обратите внимание, для нормальной пересылки вам надо не забыть корректно настроить DNS записи для вашего сервера, об этом также можно почитать в статье по ссылке.
  2. Обязательно укажите пустую карту локальных пользователей в параметре virtual_mailbox_maps, иначе Postfix будет принимать почту для всех адресов в вашем домене и выдавать кучу ошибок о невозможности доставки. В моём случае файл карты называется /etc/postfix/mailboxes/local-mailboxes. Это обычный пустой текстовый файл. После его создания необходимо выполнить команду

    sudo postmap /etc/postfix/mailboxes/local-mailboxes

    иначе Postfix не сможет работать с вашим файлом.
  3. Ну и наконец все правила перенаправления будут задаваться в двух файлах, указанных в параметре virtual_alias_maps. Один будет создаваться автоматически скриптом на основании содержимого вашей базы пользователей, а второй нужен для ручного создания дополнительных вспомогательных адресов. После редактирования файла дополнительных адресов не забудьте выполнить для него команду postmap. Подробней про использование псевдонимов для перенаправления почты в Postfix можно почитать в официальной документации.
Теперь, собственно, сам скрипт, который создаёт карту псевдонимов на основе содержимого базы пользователей. Приведённый ниже скрипт предназначен для работы с БД движка SMF, вам потребуется модифицировать его под вашу БД:

#!/usr/bin/perl

# Скрипт выбирает из SMF пользователей из определённой группы,
# и создаёт на основе выборки карты для postfix. Должен запускаться по
# cron. Предположительно отсюда  /etc/cron.d/postfix-smf-sync 

use 5.010;
use DBI;
 
#############################################################
 
# Настройки базы данных (MySQL)
$MYSQL_host = 'bd.yourdomain.com';
$MYSQL_port = '3306';
$MYSQL_user = 'postfix';
$MYSQL_pass = 'PASSW0RD';
$MYSQL_db = 'my_smf';

# Параметры SMF - группа пользователей с доступом к почте и префикс таблиц
$SMF_GID = '15';
$SMF_prefix = 'smf_';
 
# Обслуживаемый домен
$domain = "yourdomain.com";
 
# Файл псевдонимов
$postfix_file = "/etc/postfix/mailboxes/db-aliases";
 
#############################################################
 
$dbh = DBI->connect("DBI:mysql:$MYSQL_db:$MYSQL_host:$MYSQL_port",$MYSQL_user,$MYSQL_pass) or die "Hey guys! We've got a big error here...\n";
 
# Выполняем запрос
my $sth = $dbh->prepare("SELECT memberName, realName, emailAddress
	FROM ${SMF_prefix}members
	WHERE (ID_GROUP = $SMF_GID OR FIND_IN_SET($SMF_GID, additionalGroups))") or die "Hey guys! We've got  big error here!\n";
$sth->execute() or die "Hey guys! We've got a big error here...\n";
 
# Открываем файл псевдонимов для Postfix
open ALIASES, ">$postfix_file" or die "Can't open $postfix_file\n";

say ALIASES <<INTRO;
#################################################
# Карта соответствия адресов yourdomain.com основным #
# адресам пользователей.                        #
#                                               #
# Этот файл создаётся автоматически скриптом    #
# /etc/postfix/scripts/postfix-sync.pl      #
# на основе данных из БД SMF. Редактировать     #
# вручную данный файл бесполезно.               #
#################################################
INTRO

# Для проверки адреса на корректность:
# разрешённые буквы 
$w = '[a-zA-Z0-9]';
# разрешённые символы-разделители:
$s = '[._\-]';

# Анализируем ответ
while (my $row = $sth->fetchrow_hashref()) {
	# Делаем lower case, но нужно это только для красоты
	my $user = lc $row->{realName};
	# Если имя пользователя содержит неразрешённые символы - пропускаем его
	# Разрешены любые буквы, цифры и символы ".", "-" и "_"
	next unless $user =~ /^($w+$s)*$w+$/;
	# Убеждаемся, что пользователь подумал головой
	my $email = lc $row->{emailAddress};
	next if "$user\@$domain" eq $email;
	# Пишем псевдоним в файл
	say ALIASES "$user\@$domain\t$row->{emailAddress}";
}
 
# Закрываем файл псевдонимов и создаём хеш для postfix'а
close ALIASES;
`/usr/sbin/postmap hash:$postfix_file`;

В результате работы скрипта будет создан файл псевдонимов /etc/postfix/mailboxes/db-aliases, в котором будут строчки вида

someuser@yourdomain.com	usermail@anotherdomain.com

По одной строчке для каждого пользователя, состоящего в группе пользователей почты. Скрипт содержит проверку на дурака: во-первых, никакие пользователи с спецсимволами в имени не будут пропущены в файл псевдонимов Postfix. Ну и во-вторых пользователь не сможет указать в качестве основного адреса в своём профиле адрес на вашем же сайте, дабы не получился цикл почтовый системы.

Всё, что осталось сделать, это добавить запуск скрипта в планировщик, например, создать задание cron. Для этого создайте файл /etc/cron.d/postfix-sync с примерно следующим содержанием:

# Synchronize Postfix with BD
18 04 * * *      root    /etc/postfix/scripts/postfix-sync.pl

Как видно, у меня скрипт запускает каждый день в 04:18.

На этом настройка почтовой системы заканчивается.

Настройка XMPP сервера


В качестве XMPP сервера будем использовать ejabberd. Для начала его нужно поставить:

sudo apt-get install ejabberd

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

{auth_method, external}.
{extauth_program, "/etc/ejabberd/auth.pl"}.

Ну и собственно сам модуль авторизации для всё той же БД SMF, который вам потребуется изменить под вашу базу:

#!/usr/bin/perl

use 5.010;
use Digest::SHA1 qw(sha1_hex);
use DBI;

#############################################################

# Настройки базы данных (MySQL)
$MYSQL_host = 'bd.yourdomain.com';
$MYSQL_port = '3306';
$MYSQL_user = 'postfix';
$MYSQL_pass = 'PASSW0RD';
$MYSQL_db = 'my_smf';

# Параметры SMF - группа пользователей с доступом к почте и префикс таблиц
$SMF_GID = '15';
$SMF_prefix = 'smf_';

# Обслуживаемый домен
$valid_domain = "yourdomain.com";

#############################################################

# Просто соединяемся с БД
sub db_connect {
	my $dbh = DBI->connect("DBI:mysql:$MYSQL_db:$MYSQL_host:$MYSQL_port",$MYSQL_user,$MYSQL_pass);
	return $dbh;
}

$dbh = db_connect;

# Это именно то, что вы видите. Бесконечный цикл, ага.
while(1) {
	# Получаем запрос от ejabberd
	my $nread = sysread STDIN, my $buf, 2;
	unless ($nread == 2) { exit }
	my $len = unpack "n", $buf;
	$nread = sysread STDIN, $buf, $len;
    
	my ($op,$user,$domain,$passwd) = split /:/, $buf;

	# Сложно сказать зачем, но фильтруем символы
	$passwd =~ s/[\n\r]//g;
    
	# Домен проверяет ejabberd, но на всякий случай
	die "о_О" if $valid_domain ne $domain;
    
	my $result = 0;
	## Для идентичности поведения с почтовой системой:
	# если имя пользователя содержит неразрешённые символы - пропускаем его
	# Разрешены любые буквы, цифры и символы ".", "-" и "_"
	if ($user =~ /^\w+[\w.\-_]*\w+$/) {
		# Пытаемся восстановить соединение, если оно сброшено
		unless ($dbh) { $dbh = db_connect }
		# Подготавливаем запрос если соединение установлено. При неудаче - сбрасываем соединение
		my $sth = $dbh->prepare("SELECT memberName, realName, passwd
				FROM ${SMF_prefix}members
				WHERE (ID_GROUP = $SMF_GID OR FIND_IN_SET($SMF_GID, additionalGroups)) AND realName = '$user'")
			or $dbh = undef if $dbh;
		# Выполняем запрос если соединение установлено. При неудаче - сбрасываем соединение
		$sth->execute() or $dbh = undef if $dbh;
	
		# Если соединение не сброшено
		if ($dbh) {
			# Получаем количество найденных строк
			my $num = $sth->rows();
			# Получаем ответ если он состоит ровно из одной строки
			my $row = $sth->fetchrow_hashref() if $num == 1;

			# Проверяем пароль или пользователя
			if ($op =~ /auth/i and $num == 1) {
				my $epass = sha1_hex(lc($row->{memberName}) . $passwd);
	   			$result = $epass eq $row->{passwd} ? 1 : 0;
	   		} elsif ($op =~ /isuser/i and $num == 1) {
	   			$result = exists $row->{memberName} ? 1 : 0;
	   		}
		}
	} else {
		$result = 0;
	}
    
	# Отправляем ответ для ejabberd
	my $out = pack "nn", 2, $result;
	syswrite STDOUT, $out;
}

Кроме этого стоит убрать из секции modules конфигурационного файла ejabberd модуль mod_register, поскольку зарегистрироваться на вашем Jabber сервере невозможно.

Вся остальная настройка Jabber сервера никак не привязана к авторизации. Соответственно, вы можете свободно настраивать конференции, логгирование и прочие возможности, в изобилие доступные в ejabberd, чтобы в итоге получить максимально соответствующий вашим задачам XMPP сервер.

В качестве резюме


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

При этом пользователь получает полнофункциональный XMPP аккаунт со всеми дополнительными сервисами, которые вы только захотите ему предоставить. И почтовый адрес, который не привязан к какому-то конкретному физическому ящику. Кстати, обращаю внимание: отправлять почту с выданного адреса пользователь сможет без проблем, хоть и не через ваш SMTP сервер. Например, отправка от имени стороннего ящика реализована в Gmail, либо же можно использовать SMTP сервер провайдера.

С технической стороны выгрузка адресов для Postfix из вашей основной базы сводит на нет опасность сбоя основного сайта из-за проблем с почтой. Проблем с XMPP также не должно возникнуть, поскольку по сути XMPP сервер практически не связан с другими службами. Модуль авторизации просто проверяет присланный по зашифрованному соединению (особенность XMPP) пароль через базу, ничего не изменяя и не добавляя. Так что через него что-то плохое сделать тоже не получится.

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

Вот так, легко и непринуждённо, можно попытаться догнать и перегнать Вконтакте и Фейсбук. Описанная схема реально работает на нескольких сайтах достаточно продолжительное время. Любые комментарии, вопросы и предложения всячески приветствуются!

Актуальная версия статьи


Самая последняя и наиболее актуальная версия этой статьи, разбитая на две части — для почты и Jabber, находится на официальном русскоязычном Wiki-ресурсе документации по Ubuntu. Там вы можете свободно улучшать и дополнять выложенные справочные и обучающие материалы, а также добавлять свои собственные. Если вам есть, что рассказать другим пользователям Ubuntu, то огромная просьба — напишите или отредактируйте соответствующую статью на help.ubuntu.ru. Даже небольшими улучшениями и дополнениями вы поможете тысячам людей, а кто-то из них, в свою очередь, опишет что-нибудь полезное и интересное для вас.
Tags:
Hubs:
+90
Comments 66
Comments Comments 66

Articles