Не смотря на существование официального how-to по настройке автоматического ответа в самом exim и наличие различных обсуждений по этой же проблеме, но уже в связке с LDAP, собранной в одном месте всей необходимой информации мне не попалось, что и стало причиной написания данного how-to.
Будем исходить их предположения, что LDAP сервер уже имеется, а exim собран тем или иным способом и большей частью настроен.
Для начала потребуются некоторые глобальные настройки:
ldap_default_servers = ds.tune-it.ru::636
headers_charset = UTF-8
addresslist noautoreply_senders = /usr/local/etc/exim/noanswer
Если Вы используюете незащищённый LDAP, то с переменной lda_default_servers всё просто и очевидно, но если используется LDAPS и exim слинкован с openldap, то при запуске exim потребуется установить переменную:
LDAPCONF=/etc/ssl/ldaprc
И в соответствующем файле указать путь к корневому сертификату, которым подписан сертификат LDAP сервера:
URI ldaps://ds.tune-it.ru
TLS_CACERT /etc/ssl/ca.crt
Переменная headers_charset
почему-то не попалась мне ни в одном из прочитанных how-to, но именно она задаёт кодировку, которая будет указана для текста при использовании функции rfc2047. По умолчанию значение этой переменной равно ISO8859-1, и, соответственно, если её не указать, то русский текст волшебным образом превратится в тыкву.
noautoreply_senders содержит список регулярных выражений адресов отправителей, которым не стоит отвечать, и его целиком и полностью можно взять из оригинальной документации.
Также полезным будет описать два макроса, которые пригодятся нам в дальнейшем:
PEOPLE = ldaps:///ou=people,dc=tune-it,dc=ru
USERFILTER = (| (mail=${quote_ldap:$local_part@$domain}) \
(mailAlternateAddress=${quote_ldap:$local_part@$domain}))
где:
PEOPLE указывает на то, что будем использовать LDAP сервер по умолчанию и искать записи будем ветке people домена tune-it.ru,
а USERFILTER задаёт фильтр, по которому искать записи в нужной ветке.
Далее нам потребуется маршрутизатор, который надо будет разместить после всех маршрутизаторов, описывающих пересылки, но перед маршрутизатором, направляющим почту непосредственно в почтовый ящик получателя:
ldap_vacation:
driver = accept
transport = vacation_autoreply
senders = !+noautoreply_senders
condition = ${if and \
{ \
{eq \
{${lookup ldap{PEOPLE?mailAutoReplyMode?sub?USERFILTER}{$value}fail}} \
{reply} \
} \
{!match {$h_precedence:} {(?i)junk|bulk|list} } \
{!eq {$sender_address} {} } \
{!def:header_X-Cron-Env: } \
{!def:header_Auto-Submitted: } \
{!def:header_List-Id: } \
{!def:header_List-Help: } \
{!def:header_List-Unsubscribe:} \
{!def:header_List-Subscribe: } \
{!def:header_List-Owner: } \
{!def:header_List-Post: } \
{!def:header_List-Archive: } \
{!def:header_Autorespond: } \
{!def:header_X-Autoresponse: } \
{!def:header_X-Autoreply-From: } \
{!def:header_X-eBay-MailTracker: } \
{!def:header_X-MaxCode-Template: } \
{!match {$h_Subject:} {\N^Out of Office\N} } \
{!match {$h_Subject:} {\N^Auto-Reply:\N} } \
{!match {$h_Subject:} {\N^Autoresponse:\N} } \
{!match {$h_Subject:} {\N(Auto Reply)$\N} } \
{!match {$h_Subject:} {\N(Out of Office)$\N} } \
{!match {$h_Subject:} {\Nis out of the office.$\N} } \
{!> {$spam_score_int}{49}} \
{!> {$header_x-spam_score_int:}{49}} \
} \
}
no_expn
unseen
no_verify
Признаком необходимости использовать автоматический ответ является значение атрибута mailAutoReplyMode
равное reply
. Этот признак и проверяется в первую очередь. Далее мы пытаемся не отвечать на письма от крона, из списков рассылки, отлупы других почтовых систем и другие автоматические ответы. В заключение проверяется, не посчитал ли спамом используемый у нас SpamAssassin. Обращу внимание, что условие !>
не является эквивалентом более очевидного <=
, так как все условия должны быть выполнены. А если в заголовоках письма нет нужного нам поля, что возможно для, например, локальных пользователей, почту которых мы можем не проверять, или если письмо проверялось другим процессом exim и не установлена переменная spam_score_int
, то проверка вернёт ложь, и автоматический ответ сформирован не будет.
И, в заключение, нам потребуется описать соответствующий транспорт:
vacation_autoreply:
driver = autoreply
once = /var/spool/exim/db/vacation/${lookup ldap{PEOPLE?uid?sub?USERFILTER}{$value}fail}.db
once_repeat = 5d
from = ${lookup ldap{PEOPLE?gecos?sub?USERFILTER}{$value}fail} <$local_part@$domain>
to = $sender_address
headers = "Content-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit"
subject = ${rfc2047:Auto-Reply: $h_subject:}
text = ${lookup ldap{PEOPLE?mailAutoReplyText?sub?USERFILTER}{$value}fail}
body_only
В транспорте используется встроенный драйвер автоматического ответа, для которого мы указываем необходимые дополнительные заголовки письма, и, используя информацию из LDAP, формируем правильного отправителя. В качестве тела письма подставляется содержимое аттрибута mailAutoReplyText
из LDAP.
Тема письма формируется склеиванием строки Auto-Reply: и темы исходного письма. При этом тема исходного письма должна была быть правильно декодирована в UTF-8, для чего exim должен быть слинкован с libiconv, а кодируется тема с указанием кодировки из той самой переменной headers_charset
.
Одной из полезных особенностей этого драйвера является возможность отправлять уведомление отправителю только один раз в определённое время, указанное в опции once_repeat
. Для отслеживания сформированных ответов ведутся базы, уникальные для каждого пользователя, формируемая в опции once
. И, кстати говоря, не самой плохой мыслью является периодическая чистка этих баз с использованием команды exim_tidydb
.
После этого остаётся обеспечить пользователям возможность изменять для своих записей аттрибуты mailAutoReplyMode
и mailAutoReplyText
, и можно считать настройку автоматического ответа законченной.