Рубрика:
Безопасность /
Электронная почта
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
Яков Коваленко
Корпоративный MAIL RELAY-сервис
E-mail + UNIX + Exchange
Ваша почтовая система основана на MS Exchange, и вы опасаетесь подключать его напрямую в Интернет? Или вам требуется большая гибкость в маршрутизации и фильтрации почты, чего не может обеспечить упомянутый продукт? Так или иначе – UNIX-сервер и Sendmail с правильной настройкой избавят вас от головной боли и дадут мощные инструменты для работы с почтой.
В российских компаниях в большинстве случаев главным почтовым сервером является MS Exchange. Я не буду говорить ни плохих, ни хороших слов в его адрес, возьмем за данность, что он доминирует.
Как известно, продукты от MS часто подвергаются нападениям и становятся жертвами взломов, если они выставлены напрямую в Интернет. В связи с этим позиции UNIX-систем выглядят более чем привлекательно, и крупные компании стараются сделать front-end именно из UNIX-систем.
Как это реализуется?
Схема очень проста. Exchange-сервер устанавливается в изолированной от внешнего Интернета локальной сети. Обычно Exchange устанавливают на Высоконадежный Кластер, во избежание простоев из-за аппаратных сбоев. Поток почты поступает на Exchange через MX-серверы (Mail Exchangers, от названия DNS-записи, указывающей, какому серверу следует передавать почту), которые находятся под управлением UNIX-систем, не важно каких, Solaris, Linux, FreeBSD. Я советую ставить 3 MX-сервера для средней нагрузки в 20 000 – 50 000 сообщений в день.
Сломать UNIX-сервер в общем случае сложнее, чем Windows-сервер, при должном администрировании задача взломщика усложняется в разы.
В описываемом случае на UNIX-сервере работает MTA Sendmail, хотя общие принципы применимы к Postfix, Exim или другому MTA. Sendmail является на сегодняшний день одним из наиболее популярных агентов передачи сообщений в UNIX-подобных операционных системах. Кроме того, многие авторитетные люди признают его наиболее надежным и универсальным MTA из доступных в настоящее время.
MX-серверы устанавливаются либо в DMZ, либо одним сетевым интерфейсом подключены к Интернету, а другим – в приватную сеть. Так или иначе, у сервера должно быть соединение с Интернетом и с приватной сетью.
Конфигурирование Sendmail
В нашем конфигурационном файле sendmail.mc прописываем строку:
FEATURE(`mailertable',`hash -o /etc/mail/mailertable.db')dnl
В файле mailertable прописываем домен или домены, почту для которых мы принимаем, и адрес сервера, куда мы переправляем почту.
mydomain.ru smtp8:[XXX.XXX.XXX.XXX]
mydomain.com smtp8:[XXX.XXX.XXX.XXX]
На место XXX.XXX.XXX.XXX требуется поставить IP-адрес Exchange-сервера, тогда весь поток почты будет передаваться на него. В access-файле нужно прописать:
To:mydomain.ru RELAY
To:mydomain.com RELAY
XXX.XXX.XXX.XXX RELAY
На место XXX.XXX.XXX.XXX требуется поставить IP-адрес Exchange-сервера, тогда мы сможем принимать и пересылать почту от него. После всех этих манипуляций нужно пересобрать sendmail.cf, access и mailertable.
Это можно сделать либо отдельно для каждого файла, как инструкции к Sendmail, а можно создать в директории, где эти файлы расположены (в данном случае /etc/mail), Makefile с содержанием:
POSSIBLE += $(shell test -f bitdomain && echo bitdomain.db)
POSSIBLE += $(shell test -f uudomain && echo uudomain.db)
POSSIBLE += $(shell test -f genericstable && echo genericstable.db)
POSSIBLE += $(shell test -f userdb && echo userdb.db)
CFFILES = sendmail.cf submit.cf
all: ${CFFILES} ${POSSIBLE} virtusertable.db access.db ?
domaintable.db mailertable.db
userdb.db : userdb
@makemap btree $@ < $<
%.db : %
@makemap hash $@ < $<
%.cf : %.mc
@if test -f /usr/share/sendmail-cf/m4/cf.m4; then \
mv -f $@ $@.bak; \
m4 $< > $@; \
fi;
clean:
rm -f *.db *~
(взято с RHEL, во FreeBSD есть аналогичный метод).
Тогда можно будет дать команду make и перезапустить Sendmail. Все, сервер принимает почту для наших доменов и пересылает ее Exchange-серверу. Указанные операции необходимо проделать на всех MX-серверах. На Exchange нужно указать UNIX-серверы в качестве смарт-хостов для пересылки почты. Как это конкретно делается – несложно найти в руководстве к Exchange.
Теперь много работы перекладывается на UNIX-серверы. Например, можно на этапе приема проверять почту в авторитетных DNSBL, фильтровать на вирусы и спам. Теперь, даже если Exchange-сервер выйдет из строя, почта будет накапливаться на MX-сервере и, как только проблема решится, он передаст ее по назначению. Ну и самое главное – наша почтовая система защищена и производительна, в Интернет смотрят только UNIX-серверы, и даже если один из них выйдет из строя – никто (надеюсь, кроме системного администратора) этого не заметит, так как рабочие MX-серверы примут на себя часть нагрузки от вышедшего из строя сервера.
Проблемы с мусорным трафиком
Сразу встает следующий вопрос – «Если почтовые ящики не хранятся локально на UNIX-сервере, то это значит, что Sendmail не знает, какой адрес существует, а какой нет, и будет принимать все письма с адресом, содержащим наш домен. Как быть с мусорным спам-трафиком, который отправляется зачастую на несуществующие адреса и вообще с бессмысленным набором символов?».
Действительно, спамеры так и делают – отправляют письмо на совершенно немыслимые адреса в нашем домене, например eokrjglskjg@mydomain.com. Или, например, некоторые сотрудники уволились, а почта им идет. Если бы отправляющий сервер соединялся непосредственно с Exchange-сервером, то Exchange сказал бы, что не знает такого адресата и почту не примет. А в нашем случае ему приходится отвечать на попытки отправить почту на несуществующие адреса уже нашим MX-серверам, которые приняли эту почту, потратили на нее трафик и, самое обидное, в большинстве случаев они не смогут отправить уведомление о недоставке, так как отправитель тоже, скорее всего, вымышленный. Доля таких писем составляет порядка 75%, что в масштабе больших предприятий выливается в очень солидные затраты на интернет-трафик.
Проведя многие часы в поисках, я так и не нашел готового удобоваримого способа решить эту проблему. Но выход есть! С помощью Perl можно написать Milter, – программу фильтрации почты на этапе SMTP-сессии. Сразу хочу сказать, что я не такой хороший программист на Perl, как этого хотелось бы, но написанный мной скрипт замечательно работает на загруженных серверах и экономит до 60-75% почтового трафика. Итак, схема работы сервиса.
Perl-скрипт принимает от Sendmail содержание RCPT TO, что является адресом получателя. Через модуль Net::Telnet генерирует telnet-сессию, через которую опрашивает Exchange-сервер на предмет наличия или отсутствия адреса. На основании полученного ответа или пропускает письмо, или отказывает в приеме. Для более производительной работы и для того чтобы по каждому письму не беспокоить Exchange-сервер, создается база существующих адресов, куда заносятся на сутки нормальные адреса и по их поводу Exchange уже не опрашивается. Просто и логично.
Для этого потребуется – сам Perl, модули Sendmail::Milter и Net::Telnet. Sendmail должен быть собран с libmilter. В последних версиях это делается по умолчанию. Устанавливать модули для Perl удобнее всего так:
perl -MCPAN -e shell
Если вы делаете это в первый раз, скрипт задаст кучу вопросов. Ответов по умолчанию, в общем случае, должно хватить, хотя возможны варианты. Когда появится приглашение cpan, введите:
install Net::Telnet
Аналогично с Sendmail::Milter.
Для установки Sendmail::Milter файл Makefile.pl требует наличия исходных кодов Sendmail. Если вы собирали из них, то проблем возникнуть не должно, но если у вас rpm или pkg, то потребуется несколько дополнительных телодвижений.
Установите devel-пакет Sendmail. Запустите скрипт:
#!/bin/sh
SRC_DIR=/usr/local/src/sendmail
mkdir -p ${SRC_DIR}/include
cp `rpm -ql sendmail-devel | grep '\.h$’` ${SRC_DIR}/include/
mkdir -p ${SRC_DIR}/libmilter
cp `rpm -ql sendmail-devel | grep '/lib’` ${SRC_DIR}/libmilter/
cd /root/.cpan/build/Sendmail-Milter-*
perl Makefile.PL ${SRC_DIR}/ ${SRC_DIR}/
Скрипт будет работать на RedHat-совместимых дистрибутивах Linux. Чтобы он заработал на других UNIX-подобных операционных системах, нужно найти аналог команды «rpm ‑ql» (вывести листинг файлов, относящихся к пакету). Например, в Solaris нужно с помощью утилит grep и awk отфильтровать файл /var/sadm/install/contents.
Далее в /root/.cpan/build/Sendmail-Milter-* дайте последовательно команды:
make
make test
make install
Должно заработать.
Итак, если установлены все необходимые модули, можно настраивать и запускать Perl-Milter.
Потребуется:
- Добавить в sendmail.mc описание Milter.
- Создать сам скрипт /usr/local/milters/mxcheck/mxcheck.pl.
- Создать init-скрипт /etc/init.d/mxcheck.
- Пересобрать sendmail.cf.
- Перезапустить sendmail.
- Запустить mxcheck.
В sendmail.mc добавляем:
INPUT_MAIL_FILTER(`mxcheck',`S=local:/var/run/mxcheck.socket')
define(`confINPUT_MAIL_FILTERS’,`mxcheck’)
Я не могу точно сказать, есть ли очередность мильтеров, но лучше поставить mxcheck до остальных мильтеров, если они у вас есть. Создаем скрипты и настраиваем их.
Листинг скрипта mxcheck.pl с комментариями:
#!/usr/bin/perl
#
# Любой продукт можно бесконечно оптимизировать и развивать.
# Скрипт должен послужить только отправной точкой.
# Здесь описаны лишь основные моменты, но они работают.
# Готовый продукт вы должны написать для себя сами.
#
#
# Определяем переменные и модули
#
use Sendmail::Milter;
use Net::Telnet ();
use Socket;
use AnyDBM_File;
use strict;
my ( $uncheck,
$scriptname,
$sendmail_cf,
$error_code,
$reply,
$exchangeip,
$debug,
$time,
$basedir,
%validsbase,
$validsbase,
$TTL,
$rcpt_to,
$line,
$smtp,
$msg,
$IP,
$enh_error_code
);
#
# Директория, в которую кладется база
#
$basedir = "/etc/mail";
#
# Название кэш-базы
#
$validsbase = "validmails";
#
# Имя скрипта для поиска коннект-информации в sendmail.cf
#
$scriptname = "mxcheck";
#
# Местоположение конфигурационного файла Sendmail
#
$sendmail_cf = "$basedir/sendmail.cf";
#
# Адрес Exchange-сервера, у которого мы будем спрашивать существование адресов
#
$exchangeip = "10.1.0.10";
#
# Адрес или подсеть, которая считается «своей», и почта от которой не проверяется
#
$uncheck = "10.1";
#
# Режим отладки. Если поставить 1 – будет выдавать на STDOUT отладочные сообщения
#
$debug = 0;
#
# Время жизни адреса в кэш-базе. Секунды
#
$TTL = 86400;
#
# Определяем, какие SMTP-запросы нам нужно принять и обработать в скрипте. Здесь мы используем только
# адрес получателя и информацию о подключении, хотя можно использовать и другие. Почитайте мануал, очень интересно
my %mx_callbacks =
(
'connect' => \&connect_callback,
'envrcpt' => \&callback_rcptto,
);
#
# Инициализируем Milter
#
BEGIN:
{
@AnyDBM_File::ISA = qw(DB_File);
my($conn) = Sendmail::Milter::auto_getconn("mxcheck", "$sendmail_cf");
print "Found connection info for milter: $conn\n";
if ($conn =~ /^local:(.+)$/) {
my $unix_socket = $1;
if (-e $unix_socket) {
print "Attempting to unlink UNIX socket '$conn'\n";
if (unlink($unix_socket) == 0) {
print "failed\n";
exit();
}
print "successful\n";
}
}
if (not Sendmail::Milter::auto_setconn("$scriptname", "$sendmail_cf")) {
print "Failed to detect connection information\n";
} elsif (not Sendmail::Milter::register("$scriptname", \%mx_callbacks, SMFI_CURR_ACTS)) {
print "Failed to register callbacks for milter\n";
} else {
print "Starting Sendmail::Milter$Sendmail::Milter::VERSION engine\n";
if (Sendmail::Milter::main()) {
print "Successful exit from the Sendmail::Milter \n";
} else {
print "Unsuccessful exit from the Sendmail::Milter \n";
}
}
}
#
# Получение информации о подключении
#
sub connect_callback
{
my $ctx = shift;
my $hostname = shift;
my $sockaddr_in = shift;
my ($port, $iaddr);
if ($debug == 1) {
print "connect:\n";
print " + hostname: ‘$hostname’\n";
}
#
# Если к нам подключился сервер из доверенной подсети – никаких проверок
#
if (defined $sockaddr_in)
{
($port, $iaddr) = sockaddr_in($sockaddr_in);
$IP = inet_ntoa($iaddr);
if ($IP =~ /^$uncheck/) {
if ($debug == 1) { print "$IP in ALLOWED list.\n";
}
return SMFIS_ACCEPT;
}
if ($debug == 1) {
print " port: '$port'\n";
print " iaddr: '" . inet_ntoa($iaddr) . "'\n";
}
}
return SMFIS_CONTINUE;
}
#
## Проверка адреса в Exchange
#
sub callback_rcptto {
#
# Получаем информацию об адресате
#
my $time=time();
my $ctx = shift;
my $rcpt_to = shift;
$rcpt_to = lc($rcpt_to);
$rcpt_to =~ s/\s*\<(.+?)\>\s*/$1/;
my($href) = $ctx->getpriv();
$ctx->setpriv($href);
#
# Посмотрим, есть ли этот адрес в кэш-базе
#
unless (dbmopen(%validsbase,"$basedir/$validsbase",0600))
{
return SMFIS_CONTINUE;
if ($debug == 1) { print "Cannot open base \n"; }
}
if ($validsbase{$rcpt_to} ) {
if ($debug == 1) { print "$rcpt_to FOUND in base \n"; }
#
# Похоже, что есть. Посмотрим TTL
#
$valid = ($time - $validsbase{$rcpt_to});
if ( $valid < $TTL) {
if ($debug == 1) { print "$rcpt_to
ALIVE TTL is ". ($valid - $TTL) ."
time left - ". (($valid - $TTL) / 3600) ." hours \n"; }
#
# Есть и TTL не истек. Пропускаем
#
return SMFIS_CONTINUE;
} else {
delete $validsbase{$rcpt_to};
#
# Есть, но TTL истек, удаляем из базы и продолжаем проверку
#
if ($debug == 1) { print "$rcpt_to DEAD. erasing \n"; }
}
}
dbmclose(%validsbase);
#
# Если адрес не найден в кэш-базе или TTL истек – проверяем на Exchange
#
if ($debug == 1) { print "Go to exch \n"; }
#
# Запускаем подпрограмму проверки на Exchange
#
&check_exch($rcpt_to);
$line =~ s/[\r\n]+$//;
#
# Возвращен ответ, начинающийся на 550, это означает, что нет такого адреса
#
if ($line =~ /^550/) {
$error_code = '554';
$enh_error_code = '5.1.1';
$reply = 'Unroutable address';
$ctx->setreply($error_code, $enh_error_code, $reply);
#
# Пишем сообщение и отказываем в приеме
#
return SMFIS_REJECT;
}
#
# Возвращен ответ, начинающийся на 250, это означает, что такой адрес есть
#
elsif ($line =~ /^250/) {
$reply = $rcpt_to;
$ctx->setreply('250', '2.1.5', $reply);
#
# Сохраняем его в кэш-базе на сутки
#
unless (dbmopen(%validsbase,"$basedir/$validsbase",0600)) {
return SMFIS_CONTINUE;
if ($debug == 1) { print "Cannot open basefile \n"; }
}
$validsbase{$rcpt_to} = $time;
dbmclose(%validsbase);
#
# Принимаем почту
#
return SMFIS_CONTINUE;
}
#
# Если ни то и ни другое – все равно принять, на всякий случай
#
else { return SMFIS_CONTINUE; }
}
#
# Проверка на Exchange
#
sub check_exch {
my($rcpt_to) = @_;
if ($debug == 1) { print "connecting to $exchangeip as $rcpt_to \n"; }
#
# Устанавливаем Telnet-соединение с Exchange-сервером
#
$smtp = new Net::Telnet (Telnetmode => 0);
$smtp->open(Host => $exchangeip, Port => 25, Errmode => "return");
if ($debug == 1) { print "Processing Telnet... please wait - $smtp \n"; }
#
# Обрабатываем возможные ошибки
#
$msg = $smtp->errmsg;
if ($debug == 1) { print "Debug info - $msg \n"; }
#
# Если ошибка подключения – принимаем почту, на всякий случай
#
if ($msg) {
if ($msg =~ /^problem/) {
$line = 250;
if ($debug == 1) { print "connecting to $exchangeip falled \n"; }
return $line;
exit;
} else {
$line = 250;
if ($debug == 1) { print "Unknown error with Telnet session \n"; }
return $line;
exit;
}
}
#
# Получаем первое SMTP-сообщение от сервера
#
$line = $smtp->getline;
if ($debug == 1) { print "$line \n"; }
#
# Отправляем HELO
#
$smtp->print("HELO mail.mydomain.ru");
$line = $smtp->getline;
if ($debug == 1) { print "$line \n"; }
#
# Отправляем MAIL FROM:<>
#
$smtp->print("MAIL FROM:<>");
$line = $smtp->getline;
if ($debug == 1) { print "$line \n"; }
#
# Отправляем RCPT TO с адресом получателя
#
$smtp->print("RCPT TO:<$rcpt_to>");
$line = $smtp->getline;
if ($debug == 1) { print "$line \n"; }
#
# Отправляем QUIT
#
$smtp->print("QUIT");
#
# Возвращаем результат – ответ сервера
#
return $line;
}
В общем случае этого скрипта должно хватить. Но если вам необходимо делать много исключений – придется кое-что изменить. Но это уже тема программирования на Perl, которая выходит за рамки этой статьи.
С помощью Milter с почтой можно делать почти все, что угодно.
Листинг init-скрипта:
#!/bin/sh
# Source function library.
. /etc/rc.d/init.d/functions
RETVAL=0
milter_path="/usr/local/milters/mxcheck"
milter_name="mxcheck.pl"
start() {
# Start daemons.
echo -n "Starting $milter_name: "
$milter_path/$milter_name &
sleep 2
RETVAL=$?
echo
return $RETVAL
}
stop() {
# Stop daemons.
echo -n "Shutdowning $milter_name: "
killproc $milter_name
RETVAL=$?
echo
PID=`ps axwu | grep $milter_name | grep perl | gawk '{print $2}'`
if [ ! -z "${PID}" ]; then
kill ${PID} 2>/dev/null
sleep 1
fi
return $RETVAL
}
# See how we were called
case "$1" in
start)
start
RETVAL=$?
;;
stop)
stop
RETVAL=$?
;;
restart)
stop
sleep 2
start
RETVAL=$?
;;
status)
status $milter_name
RETVAL=$?
;;
*)
echo "Usage: $0 { start | stop | restart | status }"
exit 1
esac
exit $RETVAL
Пересобираем sendmail.cf:
cd /etc/mail
make
Перезапускаем сервисы:
/etc/init.d/sendmail restart
/etc/init.d/mxcheck start
Если все настроено правильно и все запустилось – поздравляю, у вас правильная система, которая еще и трафик экономит.
Благодарности
Огромное спасибо Виктору Устюгову aka Сorvax за советы и помощь в борьбе со спамом на relay-серверах. Большое спасибо моим коллегам по работе Григорьеву Игорю Григорьевичу и Королеву Игорю Александровичу за помощь в Perl и UNIX.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|