null

Настройка автоответа в Exim с LDAP

Не смотря на существование официального 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, и можно считать настройку автоматического ответа законченной.

Коротко о себе:

Работаю в компании Tune-IT и тьютором кафедры Вычислительной техники в СПбГУИТМО.

Очень люблю команду cat, core solaris и IPv6.