Вы читаете статью, размещённую в Wiki-разделе. Если вам есть что добавить, вы хотите что-нибудь улучшить или уточнить - можете свободно это делать. Только предварительно ознакомтесь с правилами ресурса.

Часто нет возможности отказаться от использования домена Windows с AD. Например из-за групповых политик. Но при этом нет никакой необходимости держать громоздкий негибкий и по слухам небозопастный Exchange для обработки почты. Что ж - выход есть, простой и элегантный. Вам всего лишь нужно организовать почтовый сервер на базе Linux, который брал бы всю информацию о пользователях и их почтовых ящиках из Active Directory.

Сервер на Postfix и Dovecot

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

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

Логин и пароль пользователя для доступа к Postfix и к Dovecot будут совпадать с доменными логином и паролем.

Итак, все подробности по мере описания настройки.

Настройка AD

Вам потребуется пользователь в AD, через которого Postfix и Dovecot будут получать информацию из дерева каталогов. Я назвал этого пользователя ldap-user, вы же можете выбрать любое другое имя. Кстати, в целях безопастности лучше запретите созданному пользователю вход на все компьютеры.

К сожалению в отсутствии Exchange через окно редактирования свойст пользователя нельзя изменить значение поля proxyAddresses. Поэтому для назначение почтовых ящиков пользователям вам понадобится Редактор ADSI, хотя конечно вы можете добавить необходимые поля редактирования в редактор свойств вручную, но делается это далеко не тривиально.

В любом случае особенности настройки AD - не тема этой статьи. Для работы системы вам потребуется пользователь для доступа к AD, а за всей другой иформацией обращайтесь в справку Microsoft или на специализированные ресурсы.

Настройка Postfix

Postfix настроим следующим образом:

  1. Он будет принимать почту для всех пользователей вашего Windows-домена, имеющих почтовые ящики.
  2. У каждого пользователя будет один ящик, на который будет приходить почта со всех его адресов.
  3. Кроме того Postfix будет автоматически рассылать почту для групповых1) ящиков на ящики пользователей.
  4. Пользователи смогут отсылать через Postfix письма только с тех ящиков, которые им принадлежат - напрямую или через группы.
  5. Авторизация и локальная доставка почты будет обеспечиваться Dovecot.

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

Сразу хочу обратить внимание: вам необходимо настроить SASL авторизацию и TLS шифрование для Postfix, поскольку мы будем позволять отсылать письма только зарегистрировавшимся пользователям, а механизм авторизации у нас будет PLAIN LOGIN2).

Авторизацию будет проводить Dovecot, поэтому в файле main.cf основных настроек Postfix у вас должны быть примерно следующие параметры:

smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth-client


smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
# Можно так вместо предыдущих двух опций
#smtpd_tls_auth_only = yes

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

Для начала вам потребуется задать основной почтовый домен для вашей организации в параметре $myorigin, без этого работать ничего не будет. Вот примерно как должны выглядеть основные настройки адресов в Postfix:

myhostname = mail.example.com
mydestination = $myhostname
myorigin = $mydomain
mynetworks = 127.0.0.0/8 
Обратите внимание: не нужно указывать локальные IP вашей сети в $mynetworks и обязательно нужно задать значение $myorigin3)!

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

virtual_mailbox_domains =
 $mydomain
 example.ru
virtual_mailbox_maps =
 hash:/etc/postfix/local_mailboxes/valid_users
virtual_alias_maps =
 hash:/etc/postfix/local_mailboxes/aliases

Назначение первого, я надеюсь, понятно. В нём содержится список всех доменов, для которых будет принимать почту Postfix. В этом списке обязательно должен быть $mydomain, иначе работать ничего не будет.

Второй парамерт указывает на карту, в которой содержится список пользователей AD, имеющих почтовые ящики. Он нужен для того, чтобы сообщить Postfix для каких пользователей он должен получать почту, а для каких - нет. Сам список должен состоять из имён аккаунтов (без домена!) пользователей, но поскольку карты Postfix должны обязательно содержать два поля на запись (см. статью про Postfix), то файл мог бы выглядеть так:

user1  OK
user2  OK
admin  OK

Вместо OK можно написать что угодно. Эта карта будет автоматически создаваться скриптом, запрашивающим данные из AD (см. ниже).

Третий параметр, $virtual_alias_maps, отвечает за установление соответствия имён почтовых ящиков пользовательским аккаунтам. Он мог бы выглядеть так:

user1@example.com  user1
user1@example.com  user1
user2@example.com  user2
abuse@example.com  user1, user2, admin

Формат файла я думаю понятен. В левой колонке адрес, а в правой - все его владельцы. Если владельцев много - значит ящик принадлежит группе пользователей в AD. Этот файл естественно тоже будет создаваться скриптом.

Ну и наконец нам нужно настроить локальную доставку. Для этого, как я уже говорил, будет использоваться Dovecot. Поэтому все параметры доставки мы будет настраивать именно в нём, а Postfix лишь должен правильно пересылать письма. Для решения этой задачи в main.cf нужно указать транспорт на Dovecot:

dovecot_destination_recipient_limit = 1
virtual_transport = dovecot

Ну и собственно описать сам транспорт в файле master.cf:

dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=postfix-vuser:postfix-vuser argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}

Обратите внимание, что в параматре -d я указал ${user}, то есть в Dovecot будет отправлено только имя пользователя без домена. Тут надо обратить внимание на одну особенность: Postfix не умеет оперировать именами пользователей, он работает только с полными адресами почты. Поэтому если указывать в описанных выше картах Postfix только имя пользователя (как мы и делали), то к нему будет автоматически добавлен домен из $myorigin. Это не доставляет никаких неудобств, потому что мы-то оперируем только именами пользователей во всех конфигах, но об этом нужно помнить, в частности, при отправке писем на Dovecot.

Итак, доставку писем пользователям мы организовали. Теперь нужно проконтролировать отправку. Тут всё проще. Поскольку наш Postfix использует SASL авторизацию, то мы просто можем ему сообщить, каким адресам отправителя соответствует какой аккаунт. Делается это с помощью аналогичной по структуре описанному выше файлу alias карты. Собственно и содержание должно быть тоже аналогично, поэтому мы будем использовать ту же карту.

Для привязки SASL логинов к адресам отправителей служит следующий парамерт в файле main.cf:

smtpd_sender_login_maps =
 hash:/etc/postfix/local_mailboxes/aliases

Однако указание этого параметра не заставит Postfix отклонять почту при несоответствии логина и адреса отправителя. Нужно ещё правильно расставить запреты в параметрах smtpd_*_restrictions. Например это можно сделать так:

smtpd_recipient_restrictions =
 ...
 permit_mynetworks
 permit_sasl_authenticated
 reject_unauth_destination
 ...

smtpd_sender_restrictions =
 ...
 reject_authenticated_sender_login_mismatch
 permit_mynetworks
 permit_sasl_authenticated
 ...

Обратите внимание на ограничение reject_authenticated_sender_login_mismatch. Оно отклоняет все письма от авторизованных пользователей, если адрес отправителя не соответствует логину в параметре $smtpd_sender_login_maps.

Собственно на этом настройка самого Postfix закончена. Теперь нужно лишь разжиться скриптом, который будет создавать файлы alias_maps и mailbox_maps по данным из AD.

Скрипт генерации карт Postfix по данным из AD

Скрипт написан на Perl и требует модуля Net::LDAP и Perl версии 5.104). Что делает скрипт:

  1. Он опрашивает AD и выбирает всех пользователей, у которых есть записи в поле proxyAddresses.
  2. Все эти пользователи добавляются в mailbox_maps, и все их ящики прописываются в alias_maps.
  3. Дальше скрипт опрашивает AD и выбирает все группы, для которых задан парамерт proxyAddresses или5) параметр mail.
  4. Для каждой полученной группы скрипт вычисляет всех пользователей, входящих в эту группу напрямую или опосредованно (через другие группы), и добавляет все необходимые псевдонимы в alias_maps. Кроме того скрипт так же добавляет пользователей, у которых нет своего ящика, но на которых ссылается ящик группы, в mailbox_maps. Таким образом все входящие в группу с прописанным почтовым ящиком пользвоатели будут получать коррекпонденцию для этой группы и смогут отправлять ответы.
  5. Кроме того скрипт попутно выявляет дубликаты адресов (когда один адрес почты задан для нескольких объектов) и адреса, не соответствующие домену. В первом случае обрабатывается только первый найденный объект с указанным адресом, а во втором просто выдаётся предупреждение, но адрес тем не менее обрабатывается.

Собственно сам скрипт:

#!/usr/bin/perl
 
# Автор: Неворотин Вадим (malamut@ubuntu.ru)
# Лицензия: GPLv3
 
use 5.010;
use Net::LDAP;
 
#
## Параметры
#
 
$AD_IP = 'ldap://192.168.0.1';		# AD сервер.
# AD пользователь для коннекта. Указывать в большинстве случаев надо именно с именем Windows-домена
$AD_user = 'ldap@domain.com';		
$AD_passwd = 'Password';		# Пароль
$AD_base = 'OU=var,DC=domain,DC=com';	# Исходная точка поиска в дереве AD
 
# Разрешённые домены - для проверки адресов
@domains = ('example.com','example.ru');
 
# Выходные файлы
$aliases_file = '/etc/postfix/local_mailboxes/aliases';
$users_file = '/etc/postfix/local_mailboxes/valid_users';
 
# Хеш псевдонимов. Ключ - адрес почты, значение - список пользователей через запятую
%aliases = ();
# Список пользователей, имеющих почтовые ящики
@valid_users = ();
 
# Соединяемся с сервером...
$ldap = Net::LDAP->new($AD_IP) or die "Couldn't connect to $AD_IP!\n";
$msg = $ldap->bind($AD_user, password => $AD_passwd);
$msg->code && die "Connection problem...\n";
 
# Открываем файл псевдонимов
open ALIASES, ">$aliases_file" or die "Can't open $alaises_file\n";
 
#
## Берём всех пользователей и все группы
#
 
# Процедура проверяет свой аргумент (адрес почты) на соответствие разрешённым доменам.
sub check_domain {
	@_ == 1 or die "check_domain - OOPS!\n";
	my $flag = 0;
	foreach $domain (@domains) {
		if ($_[0] =~ /@($domain)/) {
			$flag = 1;
		}
	}
	if (!$flag) {
		say "WARNING: incorrect domain in $_[0]";
	}
}
 
# Процедура проверяет вхождение первого элемента (адрес) в качестве ключа для хеша (третий аргумент)
# Второй аргумент - это предполагаемое значение ключа (имя пользователя или группы)
sub check_mail {
	@_ > 1 or die "check_mail - OOPS!\n";
	$mail = shift @_;
	$obj = shift @_;
	%hash = @_;
	if (exists($hash{$mail})) {
		say "WARNING: address $mail is used more than for one object:";
		say "\t",$hash{$mail};
		say "\t",$obj;
		say "I'll add it only for '$hash{$mail}'";
		return 1;
	}
	return 0;
}
 
#
## Пользователи
#
 
# emails - из proxyAddresses, login - из sAMAccountName
 
# Параметры поиска в AD для пользователей
$filter = '(&(objectClass=user)(proxyAddresses=*))';
$attrs = ['CN','sAMAccountName','proxyAddresses'];
 
# LDAP запрос
$result = $ldap->search(
	base => $AD_base,
	filter => $filter,
	attrs => $attrs);
 
# Выделяем необходимую инфу из результатов
foreach $entry ($result->entries) {
	@addrs = $entry->get_value('proxyAddresses');
	$obj = $entry->get_value('sAMAccountName');
	foreach $mail (@addrs) {
		check_domain($mail);
		unless (check_mail($mail,$obj,%aliases)) {
			$aliases{$mail} = $obj;
			say ALIASES "$mail\t$obj";
		}
	}
}
 
@valid_users = values %aliases;
 
#
## Группы 
#
 
# Проверяет вхождение элемента в массив. Короче - оператор in)))
sub check_element {
	@_ > 1 or die "check_element - OOPS!\n";
	$element = shift @_;
	@arr = @_;
	foreach (@arr) {
		if ($_ eq $element) { return 1 }
	}
	return 0;
}
 
# Удаляет из массива повторяющиеся элементы
sub delete_duplicates {
	my $el = undef;
	my @result;
	foreach (sort @_) {
		if ($_ ne $el) { push @result, $_ }
		$el = $_;
	}
	return @result;
}
 
# Рекурсивно выбиряет по предоставленному DN всех пользователей.
# Нужно для выборки по DN группы всех входящих в неё пользвоателей
sub get_users {
	@_ == 1 or die "get_users - OOPS!\n";
 
	my $base = $_[0];
	my $result = $ldap->search(
		base => $base,
		scope => "base",
		filter => "objectClass=*" );
	my $ent = $result->entry(0);
 
	my @users = ();
	if ( check_element("user", $ent->get_value("objectClass")) ) {
		push @users,  $ent->get_value("sAMAccountName");
	} elsif ( check_element("group", $ent->get_value("objectClass")) ) {
		foreach my $member ($ent->get_value("member")) {
			push @users, get_users($member);
		}
	}
	return @users;
}
 
# Параметры поиска для групп
$filter = '(&(objectClass=group)(|(proxyAddresses=*)(mail=*)))';
$attrs = ['member','proxyAddresses','mail','sAMAccountName'];
 
# LDAP запрос
$result = $ldap->search(
	base => $AD_base,
	filter => $filter,
	attrs => $attrs);
 
foreach $entry ($result->entries) {
	@mails = ();
	push @mails, $entry->get_value("mail"), $entry->get_value("proxyAddresses");
	$obj = $entry->get_value('sAMAccountName');
 
	@users = ();
	foreach $member ($entry->get_value("member")) {
		push @users, get_users($member);
	}
	@users = delete_duplicates(@users);
	push @valid_users, @users;
	if (! @users) { 
		say "WARNING: empty group ",$entry->dn;
		next;
	}
 
	foreach $mail (@mails) {
		check_domain($mail);
		unless (check_mail($mail,$obj,%aliases)) {
			$aliases{$mail} = $obj;
			say ALIASES "$mail\t", join(", ",@users); 
 
		}
	}
}
 
# Открываем файл пользователей и пишем туда всех пользователей с почтовыми ящиками
open USERS, ">$users_file" or die "Can't open $users_file\n";
 
@valid_users = delete_duplicates(@valid_users);
foreach (@valid_users) {
	say USERS "$_\tdovecot";
}
 
close USERS;
 
# Close aliases file
close ALIASES;
 
# Close connection to LDAP
$msg = $ldap->unbind;
 
`postmap hash:$aliases_file`;
`postmap hash:$users_file`;

Поменяйте в скрипте необходимые параметры (все они собраны в начале) и поставьте его выполняться через cron. В принципе, Postfix трогать больше не потребуется.

Настройка Dovecot

При настройке Dovecot в первую очередь нужно не забыть выставить сокет авторизации для Postfix, как это делать описано в статье про Dovecot.

Теперь нужно указать механизмы авторизации. Мы будем использовать только PLAIN LOGIN, то есть механизмы с открытой передачей пароля. Правда при этом потребуем обязательного TLS шифрования, что обезопасит пользователей от кражи их аккаунтов. Некоторые говорят, что использование PLAIN небезопастно, однако тут надо учесть один немаловажный факт: при использовании PLAIN авторизации вы можете хранить пароли в виде хешей, а при использовании «безопасных» методов вам придётся хранить все данные о пользователях открытым текстом. При этом PLAIN авторизацию можно обернуть в TLS шифрование, что обезопасит этот механизм, а гарантированно защитить базу паролей на сервере вы не сможете. В общем и целом - всегда используйте PLAIN с TLS шифрованием и храните все пароли в виде хешей.

Тем более нам надо соединяться с AD и предоставлять ему определять правильность пароля, так что нам в любом случае нужен PLAIN механизм авторизации.

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

# Эта опция обязательна! Она требует TLS шифрования для PLAIN авторизации, без неё безопастность будет на 0
disable_plaintext_auth = yes
# Включаем шифрование
ssl_disable = no
# Собственно описание механизма авторизации
auth default {
  # Поддерживаемые типы. Тут не должно быть ничего кроме plain и login
  mechanisms = plain login

  # База паролей
  passdb ldap {
    args = /etc/dovecot/dovecot-ldap-passdb.conf
  }
  
  # База пользователей
  userdb ldap {
    args = /etc/dovecot/dovecot-ldap-userdb.conf
  }

  # Пользователь, имеющий доступ к ящикам и сокету авторизации
  user = postfix-vuser
  
  # Сокеты
  socket listen {
    master {
      path = /var/run/dovecot/auth-master
      mode = 0600
      user = postfix-vuser 
      group = postfix-vuser
    }
    client {
      path = /var/spool/postfix/private/auth-client
      mode = 0660
      user = postfix
      group = postfix
    }
  }
}

Подробнее можно почитать с статье про Dovecot. Нас тут интересует использование LDAP в качестве базы пользователей и паролей. Кстати, сразу хочу сказать, файл dovecot-ldap-userdb.conf должен быть симлинком на dovecot-ldap-passdb.conf. То есть фактически это один и тот же файл. Такое разделение связано с механизмами обращения к LDAP в Dovecot, подробней см. в документации.

Теперь содержимое dovecot-ldap-userdb.conf:

uris = ldap://192.168.0.1

# LDAP account
dn = ldap@domain.com
dnpass = Password

# LDAP settings
ldap_version = 3
base = ou=var, dc=domain, dc=com
scope = subtree

# Password check 
auth_bind = yes
auth_bind_userdn = %n@domain.com
pass_filter = (&(objectCategory=Person)(sAMAccountName=%n))

# Users check
user_filter = (&(objectCategory=Person)(sAMAccountName=%n))
user_global_uid = 900
user_global_gid = 900

Дополнительные настройки

У приведенной выше конфигурации есть один недостаток. Дело в том, что Postfix предоставляет возможность SASL авторизации всем клиентам. Что открывает доступ к простому перебору паролей методом брутфорса. А так как пароли у пользвоателей такие же, как и от доменных аккаунтов, то всё это может быть весьма нехорошо.

Поэтому возможно неплохим решением было бы ограничить возможность SASL авторизации только в рамках внутренней сети. Но сразу скажу - Postfix так делать не умеет. То есть вне зависимости от своих настроек он будет предоставлять авторизацию всем. Но не надо отчаиваться, можно сделать так, чтобы в авторизации клиентам не из разрешённой сети отказывал Dovecot. Хотя тут есть некоторые трудности. Технически это осуществялется с помощью параметра allow_nets, который является необязательным полем passdb. И для IMAP и POP этот параметр прекрасно работает. Но Postfix до версии 2.7 не посылает в сокет авторизации Dovecot информацию о клиенте, поэтому Dovecot с установленным параметром allow_nets благополучно отказывает всем.

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

1)
То есть да, вы сможете создавать в AD группы пользователей и назначать им адреса почты, и всем участникам этой группы будут приходить пистма.
2)
То есть открытым текстом. Именно поэтому всё нужно шифровать. Подробнее о причинах выбора PLAIN авторизации ниже.
3)
Если вы ещё не освоились с Postfix, то напомню, что значение $mydomain если не указано явно береться отсечением первой части до точки значения $myhostname.
4)
Последнее используется только для say, если вы понимаете что это такое.
5)
На всяких документах тут принято писать и/или, но мы же с вами понимаем, что и - это своего рода подмножество или.