Почтовый сервер Exim SMTP

Официальное руководство для релиза 4

Филип Хейзел

Кембриджский университет, 2003

перевод В.Айсин

Предисловие

В 1995 году на центральных серверах Кембриджского университета работало множество агентов пересылки почты, включая Sendmail, Smail 3 и PP. За несколько лет до этого я преобразовал системы, почтой которых я управлял, из Sendmail в Smail, чтобы упростить выполнение особых требований начала 1990-х годов в академических сетях Великобритании во время перехода от частной сети на основе X.25 к Интернету. К 1995 году переход был завершен, и пришло время двигаться дальше.

До того времени Интернет был довольно дружелюбным местом, и не нужно было принимать много мер предосторожности против враждебных действий. Например, на большинстве сайтов работали открытые почтовые релеи. Однако было ясно, что эта ситуация меняется и возникают новые требования. Я внес некоторые изменения в код Smail, но к тому времени это был код восьмилетней давности, написанный на нестандартном C и изначально предназначенный для использования в совершенно другой среде, которая требовала значительной поддержки UUCP. Поэтому я решил посмотреть, смогу ли я создать новый MTA с нуля, взяв базовую философию Smail и расширив ее, но исключив поддержку UUCP, которая не была нужна в нашей среде. Поскольку я не был точно уверен, каков будет результат, я назвал его EXperimental Internet Mailer (Exim).

Один из моих коллег по компьютерным наукам пронюхал о том, чем я занимаюсь, попросил тестовую копию и сразу же ввел ее в эксплуатацию еще до того, как я запустил ее на своих хостах. Он начал рассказывать об этом другим, поэтому я начал размещать релизы на FTP-сайте и отвечать на электронные письма. Ранние выпуски никогда не были «анонсированы»; они просто распространяются из уст в уста. Через некоторое время британский интернет-провайдер вызвался запустить веб-сайт и список рассылки, и с этого момента он продолжал расти. Был непрерывный поток комментариев и предложений, и в текущих релизах гораздо больше возможностей, чем я когда-либо планировал в начале.

Несмотря на то, что я считаю обязательным наличие всеобъемлющего справочного руководства, одной вещи, которой не хватало в течение некоторого времени, были вводные и обучающие материалы. Я все надеялся, что кто-то еще что-нибудь напишет, но в итоге меня попросили написать книгу самому. Эта первая книга описывала Exim 3 и была опубликована O'Reilly.

С появлением Exim 4 потребовалась обширная переработка, потому что Exim 4 отличается от Exim 3 рядом фундаментальных особенностей, и результатом стала эта новая книга. Я не пытался охватить обе версии в одной книге; слишком много различий, и книга получилась бы очень длинной, что особенно запутало бы новичков. Если вы все еще используете Exim 3, вам нужна копия более ранней книги.

Кому следует прочитать эту книгу

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

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

Я надеюсь, что эта книга облегчит жизнь тем, кому трудно работать со справочником самостоятельно.

Организация книги

После короткой обзорной главы эта книга продолжается общим введением в электронную почту в Интернете, потому что эта тема, кажется, недостаточно подробно освещена где-либо еще. Остальная часть книги посвящена объяснению того, как работает Exim, и как вы можете использовать его конфигурацию для управления тем, что он делает. Вот подробная разбивка по главам:

Глава 1 Введение

Эта глава представляет собой краткое «исполнительное» резюме.

Глава 2. Как работает интернет-почта

Эта глава представляет собой общее введение в то, как электронная почта обрабатывается в интернет-системах.

Глава 3. Обзор Exim

Эта глава содержит общий обзор того, как работает Exim, и знакомит вас с тем, как он настроен, в частности, в отношении способа доставки сообщений.

Глава 4 Обзор операций Exim

Эта глава продолжается дополнительным обзорным материалом, в основном по темам, отличным от доставки сообщений.

Глава 5 Расширение конфигурации доставки

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

Глава 6 Общие параметры, применимые ко всем роутерам

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

Глава 7 Роутеры

В этой главе подробно описывается каждый из роутеров.

Глава 8 Общие параметры, применимые ко всем видам транспорта

В этой главе обсуждаются общие опции, общие для всех транспортов, которые являются компонентами Exim, которые фактически транспортируют сообщения.

Глава 9 Транспорты

В этой главе подробно обсуждается каждый из транспортов.

Глава 10 Фильтрация сообщений

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

Глава 11 Общие данные и процессы Exim

В этой главе описываются различные типы процессов Exim и данные, которые они разделяют.

Глава 12 Ошибки доставки и повторные попытки

Эта глава посвящена временным ошибкам доставки и тому, как Exim их обрабатывает.

Глава 13. Шифрование, аутентификация и другая обработка SMTP

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

Глава 14. Прием сообщений и управление политиками

В этой главе описываются средства, доступные для управления приемом входящих сообщений.

Глава 15 Перезапись адресов

Эта глава описывает возможности перезаписи адресов в сообщениях, когда они проходят через Exim.

Глава 16 Поиск файлов и баз данных

Это первая из трех глав, в которых подробно рассматриваются три основных средства, обеспечивающие гибкость конфигурации Exim. Все они представлены в предыдущих главах, но полное описание начинается здесь.

Глава 17. Расширение строки

В этой главе рассказывается о механизме расширения строки Exim.

Глава 18 Списки доменов, хостов и адресов

Эта глава дает более подробную информацию о нескольких типах списков, которые могут появиться в конфигурациях Exim.

Глава 19 Разное

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

Глава 20 Интерфейс командной строки для Exim

Эта глава описывает опции и аргументы, которые используются для управления тем, что на самом деле делает вызов Exim.

Глава 21 Администрирование Exim

В этой главе обсуждается ряд тем, связанных с администрированием, и описываются служебные программы, которые могут помочь в этом, включая монитор Exim, который является приложением для отображения информации о действиях Exim в X-window.

Глава 22 Сборка и установка Exim

В этой главе описывается, как собрать и установить Exim из исходного кода.

Приложение A. Сводка по расширению строки

Это приложение представляет собой сводку элементов расширения строки.

Приложение B. Регулярные выражения

Это приложение является справочным описанием регулярных выражений, поддерживаемых Exim.

Условные обозначения, используемые в этой книге

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

Италик

Используется для имен файлов и каталогов, имен программ и команд, имен хостов и доменов, адресов электронной почты, почтовых заголовков, URL-адресов и новых терминов.

Болд

Используется для имен роутеров, транспортов и аутентификаторов Exim.

Курсив

Используется для имен переменных Exim.

Моноширный

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

<Моноширный италик>

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

<Моноширный болд>

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

Перекрестные ссылки в книге даются по номеру раздела и часто заключаются в круглые скобки, например: (7.2).

Предложения и комментарии к этой книге

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

comments@exim-book.com
suggestions@exim-book.com
errors@exim-book.com

Благодарности

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

Что касается самого Exim, я должен прежде всего поблагодарить своих коллег из вычислительной службы Кембриджского университета. Руководство разрешило мне написать Exim, и как только он появился, вычислительная служба поддержала его использование в университете и в других местах. С момента выхода первого релиза Exim многие люди присылали предложения по улучшению или новым функциям, а также исправления проблем, и это не прекращается.

Пит Брукс был достаточно храбр, чтобы ввести в эксплуатацию первую версию для обработки почты кембриджских ученых-компьютерщиков, а также реализовал схему компиляции в нескольких операционных системах. Пит предположил, что интегральный фильтр был бы хорошей вещью. Алан Баррат предоставил исходный код для проверки релеев. Найджел Метерингем убедил своих тогдашних работодателей, Planet Online Ltd., обеспечить поддержку веб-сайта Exim и списка рассылки. Хотя он больше не работает на них, он по-прежнему управляет сайтом и списками рассылки и Planet (теперь называется Energis) по-прежнему предоставляет аппаратные и сетевые ресурсы. Найджел также предоставил код для взаимодействия с библиотекой Berkeley DB, для поддержки файлов cdb и для доставки в почтовые ящики в формате maildir. Янн Голански предоставил код числовой хеш-функции. Стив Кларк провел эксперименты, чтобы определить наиболее эффективный способ определения средней нагрузки в Linux. Филип Бланделл реализовал первую поддержку IPv6, когда был студентом Кембриджа. Джейсон Ганторп предоставил дополнительный код IPv6 для Linux. Стюарт Линн предоставил первый код для поддержки LDAP: последующие модификации исходили от Майкла Хаардта, Брайана Кэндлера, Барри Педерсона и Петра Савича. Стив Хаслам предоставил предварительный код для поддержки TLS/SSL и продолжает давать множество идей. Малкольм Битти написал интерфейс для вызова встроенного интерпретатора Perl. Пол Келли написал исходный код для вызова MySQL, и Петр Чех сделал то же самое для PostgreSQL. Марк Прудоммо переупаковал часть кода из проекта Samba для аутентификации клиента SPA. Александр Сабуренков проделал аналогичную работу для аутентификации с помощью демона pwcheck из библиотеки Cyrus SASL. Ян Кирк предоставил код для поддержки Radius. Стюарт Леви внес замену сломанной функции inet_ntoa() в IRIX. Мэтью Бинг-Мэддик предложил тип поиска dsearch и предоставил предварительную реализацию. Роберт Уол предоставил реализацию типа поиска whoson. Пьер Хамблет заставил Exim работать под Cygwin. Стив Кэмпбелл взял на себя поддержку утилиты eximstats и значительно расширил ее.

Наконец, для самого Exim. Я должен признать свой долг перед Smail 3, написанным Роном Карром, на котором я основал первые версии Exim. Хотя сейчас Exim изменился почти до неузнаваемости, его происхождение все еще видно.

Справочное руководство Exim было улучшено в результате множества полезных комментариев, которые я получил. Джефф Голдберг указал, что я использовал слово «fail» в двух разных смыслах в документации Exim, и предложил «decline» для одного из них. Джон Хорн читает каждое издание справочного руководства и выявляет мои опечатки и другие ошибки.

При написании обеих версий этой книги я продолжал пользоваться поддержкой моих коллег и сообщества Exim. Моя жена Джудит не только в целом поддерживала меня, но и читала как ранний черновик, так и последнюю версию в качестве профессионального редактора и нашла много мест, где я был неясен или непоследователен. Кен Бейли сделал несколько полезных замечаний по поводу некоторых первых глав. Джон Хорн прочитал ранний черновик и внес предложения, которые помогли мне расположить материал в более доступном порядке, а затем снова прочитал первую книгу в позднем черновике, предоставив дополнительные полезные отзывы. Джон также прочитал и прокомментировал совершенно новую главу об управлении доступом для второй книги. Майкл Шэпп прочитал первые главы второй книги и предоставил мне еще более полезную техническую и редакционную обратную связь.

Моим редактором первой книги в O'Reilly был Энди Орам. Его комментарии и рекомендации оказали большое влияние на форму и вид законченной книги, и многое из того, что он сделал, было перенесено в эту новую книгу.

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

Наконец, что не менее важно, я должен поблагодарить Найла Мэнсфилда из UIT Cambridge, который пришел мне на помощь, когда мне понадобился новый издатель.

Глава 1
Введение

Exim — это mail transfer agent (агент по передаче почты) (MTA)[1], который можно запустить в качестве альтернативы Sendmail на большинстве Unix и Unix-подобных систем[2]. Exim — это программное обеспечение с открытым исходным кодом, которое распространяется под Стандартной общественной лицензией GNU (GPL)[3]. Некоторые дистрибутивы операционных систем включают Exim в качестве MTA по умолчанию.

Я написал Exim для использования на серверах среднего размера с постоянным подключением к Интернету в университетской среде, но теперь он используется в самых разных ситуациях, от однопользовательских машин с коммутируемым подключением до кластеров серверов, обслуживающих миллионы клиентов. Код небольшой (от 500 КБ до 1.2 МБ на большинстве аппаратных средств, в зависимости от компилятора и включенных дополнительных модулей), и его производительность хорошо масштабируется.

Работа агента по пересылке почты состоит в том, чтобы получать сообщения из разных источников и доставлять их по назначению, возможно, различными способами. Exim может принимать сообщения от удаленных хостов, используя SMTP[4] через TCP/IP, а также от локальных процессов. Он обрабатывает локальные доставки в файлы почтовых ящиков или каналы, прикрепленные к командам, а также удаленные доставки через SMTP на другие хосты. Exim поддерживает новый протокол IPv6, а также текущий протокол IPv4. Он не поддерживает напрямую UUCP, хотя может быть связан с другим программным обеспечением, которое его поддерживает, при условии, что не требуется адресация UUCP «bang path», потому что Exim поддерживает только адресацию на основе домена в стиле Интернета.

Конфигурация Exim гибкая и может быть настроена для работы с широким спектром требований, включая виртуальные домены и расширение списков рассылки. Как только вы усвоите основные принципы работы Exim, вы обнаружите, что конфигурация времени выполнения прямолинейна и проста в настройке. Конфигурация состоит из одного файла, разделенного на несколько разделов. Записи в каждом разделе представляют собой пары ключевое слово/значение. Регулярные выражения, совместимые с Perl 5, доступны для использования в ряде вариантов.

Файл конфигурации может ссылаться на данные из других файлов в линейном и индексированном форматах, а также из баз данных NIS, NIS+, LDAP, MySQL, Oracle и PostgreSQL. Таким образом, вы можете сделать большую часть операционной таблицы Exim управляемой. Например, вы можете организовать локальную доставку на машину, на которой у пользователей нет учетных записей. Максимальная гибкость может быть достигнута (за определенную цену), если запустить интерпретатор Perl при обработке определенных строк опций.

Можно указать максимальный размер сообщений, и вы можете использовать access control lists (списки управления доступом) (ACL) для проверки и контроля входящих сообщений. К сообщению и его получателям может быть применен ряд тестов, прежде чем оно будет принято. К ним относятся проверка отправляющего хоста или сети и личность отправителя. Вы можете явно блокировать хосты и использовать онлайн-списки, такие как Realtime Blackhole List (RBL)[5]. Вы можете контролировать, каким хостам разрешено использовать хост Exim в качестве ретранслятора для дальнейшей передачи почты. Для этой цели можно использовать механизм SMTP AUTH для аутентификации клиентских хостов.

Конечным пользователям обычно не важно, какой MTA доставляет в их почтовые ящики, но когда используется Exim, его средства фильтрации, которые расширяют возможности традиционного файла .forward, могут быть им доступны. Файл фильтра может проверять различные характеристики сообщения, включая содержимое строк заголовка и начало тела, а затем направлять доставку по указанным адресам, файлам или каналам в зависимости от того, что он находит. Системный администратор также может использовать функцию фильтрации для проверки каждого сообщения перед доставкой.

Как и многие MTA, Exim принял интерфейс командной строки Sendmail, так что его можно установить вместо /usr/sbin/sendmail или /usr/lib/sendmail. Все соответствующие параметры Sendmail реализованы. Есть также некоторые дополнительные параметры, совместимые со Smail 3, и некоторые дополнительные параметры, специфичные для Exim.

Сообщениями в очереди можно управлять с помощью определенных привилегированных параметров командной строки. Существует также необязательная программа-монитор под названием eximon, которая отображает текущую информацию в окне X и содержит интерфейсы для параметров командной строки.

Exim не предназначен для хранения почты удаленных хостов. Когда объем такой почты велик, лучше «доставить» сообщения в файлы (то есть вне очереди Exim), а затем передать их другим способом.

Есть некоторые вещи, которые Exim не делает: он не поддерживает никакие формы уведомлений о статусе доставки (как описано в RFC 1891)[6], и у него нет встроенных средств для изменения тела сообщений. В частности, он никогда не переводит тело сообщения из одной формы кодирования в другую, хотя для выполнения этого требования можно использовать фильтр в сочетании с внешней программой.

Цель этой книги — объяснить, как работает Exim, и предоставить базовую и учебную информацию по основным возможностям, о которых необходимо знать большинству администраторов. Некоторые опции, которые требуются только в особых обстоятельствах, не покрываются. В любом случае, книга никогда не поспевает за развитием программного обеспечения; если вы хотите точно знать, что доступно в той или иной версии, вам следует обратиться к справочному руководству и другой документации, включенной в дистрибутив для этой версии.

Exim все еще развивается с учетом накопленного опыта, меняющихся требований и отзывов пользователей. Первая книга, опубликованная в 2001 году, охватывала все основные функции релизов 3.2x. Текст этой новой книги был значительно переработан, чтобы соответствовать версии 4.10. Для Exim 4 основные изменения были внесены в способ маршрутизации адресов и в способ определения политики для входящих сообщений. Некоторые другие функции были пересмотрены и упрощены.

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

Справочное руководство по Exim и FAQ находятся онлайн на веб-сайте Exim по адресу http://www.exim.org и его зеркалах. Здесь вы также найдете последнюю версию Exim в виде исходного кода. В дополнение к текстовой версии, включенной в дистрибутив, руководство можно загрузить в формате HTML (для более быстрого доступа через браузер), в формате PostScript или PDF (для печати) и в формате Texinfo для команды info.

Некоторые версии GNU/Linux распространяются с включенными бинарными версиями Exim. По этой причине я оставил материал по сборке Exim из исходников до конца книги и сначала сосредоточился на аспектах выполнения. Если вы работаете с бинарным дистрибутивом, убедитесь, что у вас есть копия текстовой версии справочного руководства, поставляемого с исходным дистрибутивом. Он обеспечивает полный охват всех параметров конфигурации, и его легко найти.

Следующая глава представляет собой общее обсуждение того, как работает электронная почта в Интернете; Exim практически не упоминается. Этот материал был включен для удобства многих людей, которым приходится работать с почтовым сервером без этих необходимых базовых знаний. Вы можете перейти к главе 3, если вы уже знаете о формате сообщений RFC 2822, SMTP, маршрутизации почты и использовании DNS.

  1. Термины mail transfer agent и mail transport agent являются синонимами и взаимозаменяемы.

  2. Exim также можно запустить в среде Cygwin; однако в этой книге этот случай не рассматривается.

  3. См. http://www.gnu.org/copyleft/gpl.himl.

  4. Если вы не знакомы с SMTP или некоторыми другими используемыми здесь аббревиатурами, не отчаивайтесь. Следующая глава содержит описание того, как работает интернет-почта.

  5. См. http://mail-abuse.org/rbl.

  6. RFC — это документы, устанавливающие стандарты работы Интернета. Вы можете найти их в Интернете по адресу http://www.ietf.org (и во многих других местах). Мы немного поговорим о тех, которые относятся к почте, в следующей главе.

Глава 2
Как работает интернет-почта

Программы, которые люди используют для отправки и получения почты (часто называемые просто «почтовыми программами»), формально называются mail user agents (MUA). Они озабочены обеспечением удобного почтового интерфейса для пользователей. Они отображают входящую почту, находящуюся в почтовых ящиках пользователей, помогают пользователю создавать сообщения для отправки и предоставляют средства для управления папками сохраненных сообщений. Они являются «фронтэндом» почтовой системы. Можно установить множество различных пользовательских агентов, которые могут работать одновременно на одном компьютере, тем самым обеспечивая выбор различных пользовательских интерфейсов. Однако, когда MUA отправляет сообщение, он не берет на себя работу по фактической доставке его получателям. Вместо этого он отправляет его mail transfer agent (MTA), который может работать на том же хосте или на каком-то локальном сервере.

Рисунок 2-1: Поток данных сообщения

Агенты пересылки почты передают сообщения с одного хоста на другой. Когда сообщение достигает узла назначения, MTA доставляет его в почтовый ящик пользователя или в процесс, управляющий почтовыми ящиками пользователей. Эта работа сложна, и было бы нецелесообразно, чтобы в каждом MUA была вся необходимая аппаратура. Поток данных от отправителя сообщения к получателю показан на рис. 2-1. Однако, когда прикладной программе или сценарию необходимо отправить почтовое сообщение в рамках какой-либо автоматической операции, они обычно вызывают MTA напрямую, не привлекая MUA.

Одновременно на хосте может работать только один MTA, потому что только одна программа может быть назначена для приема входящих сообщений от других хостов. В Unix-подобных системах MTA должен быть привилегированной программой, чтобы прослушивать входящие SMTP-соединения через TCP-порт 25 (стандартный SMTP-порт) и иметь возможность писать в почтовые ящики пользователей. Выбор, какой MTA запускать, делается системным администратором, тогда как выбор запускаемого MUA осуществляется конечным пользователем.

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

С точки зрения MTA есть два источника входящих сообщений: локальные процессы и другие хосты. Существует три типа назначения: локальные файлы, локальные процессы через каналы и другие хосты, как показано на рис. 2-2.

Рисунок 2-2: Работа MTA

Разделение труда между MUA и MTA также означает, что MUA не обязательно должен работать на том же хосте, что и его MTA; Рисунок 2-3 иллюстрирует взаимосвязь между MUA и MTA в двух распространенных конфигурациях.

В верхней части рисунка MUA, MTA и дисковое хранилище являются частью единой системы, обозначенной пунктирной линией. Пользователи получают доступ к системе, регистрируясь и аутентифицируя себя с помощью пароля или каким-либо другим способом. MUA запускается по команде пользователя как процесс в системе, и когда он передает сообщение для доставки в MTA, он взаимодействует с другим процессом в той же системе. Следовательно, и MUA, и MTA знают аутентифицированный идентификатор отправителя сообщения, и MTA может гарантировать, что этот идентификатор включен в исходящее сообщение. Как указано в RFC 2822, если строка содержимого заголовка From: не соответствует фактическому отправителю, MTA обычно должен добавить строку Sender:, содержащую аутентифицированную личность.

Сообщения удерживаются адаптером MTA в своей области спулинга в ожидании доставки. Слово «spool» применительно к дисковому хранилищу имеет два разных значения. В этой книге мы используем его для обозначения дискового хранилища, которое MTA использует для передачи сообщений. Иногда вы будете видеть, что «spool» используется для области диска, в которой хранятся почтовые ящики пользователей, но это не тот смысл, в котором он используется здесь.

Рисунок 2-3: MUA и MTA

Сообщения, предназначенные для других узлов, передаются через Интернет на другие MTA с использованием Simple Mail Transfer Protocol (SMTP). Когда исходный хост и конечный хост оба напрямую подключены к Интернету, сообщение может быть доставлено непосредственно на конечный хост, но иногда оно должно проходить через промежуточный MTA. Крупные организации часто организуют маршрутизацию всей своей входящей почты через центральный mail hub (почтовый концентратор), который затем доставляет ее на другие узлы в локальной сети организации. Они могут быть за брандмауэром и поэтому недоступны для Интернета в целом. Когда сообщение достигает узла назначения, MTA доставляет его в почтовый ящик получателя, который затем может получить к нему доступ с помощью MUA по своему выбору.

Другой случай, когда задействован промежуточный MTA, — это когда конечный пункт назначения или его сетевое соединение не работают. С помощью Domain Name Service (DNS, см. 2.7) или какого-либо частного метода для домена может быть назначен резервный хост. Входящая почта накапливается на этом узле до тех пор, пока основной узел снова не начнет работать, после чего накопившаяся почта переносится. Преимущество этого заключается в том, что накопленная почта может храниться рядом с конечным пунктом назначения и в конечном итоге может быть передана быстро и контролируемым образом. Напротив, когда загруженный хост без резервной копии перезагружается, он может получить очень большое количество одновременных входящих SMTP-подключений со всего Интернета, что может вызвать проблемы с производительностью.

Нижняя часть рисунка 2-3 иллюстрирует другую популярную конфигурацию, в которой MUA не работает в той же системе, что и MTA. Вместо этого он работает на рабочей станции пользователя. Получение и отправка сообщений в этой конфигурации являются совершенно отдельными операциями. Когда пользователь читает почту, MUA использует протокол POP (RFC 1939) или IMAP (RFC 2060) для доступа к почтовому ящику и удаленным папкам на сервере. Для этого пользователь должен быть каким-то образом аутентифицирован; обычно для получения доступа к почтовому ящику и удаленным почтовым папкам используются имя пользователя и пароль. Однако ни протоколы POP, ни IMAP не содержат никаких средств для отправки сообщений. Поэтому MUA этого типа традиционно использовали протокол SMTP для передачи сообщений MTA в серверной системе. Таким образом, протокол, который изначально был разработан для передачи сообщений между MTA, используется для отправки новых сообщений в MTA, что на самом деле представляет собой операцию другого типа. Такое использование приводит к ряду проблем:

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

  1. См. http://mail-abuse.org/dul/.

2.1 Различные типы MTA

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

Хостам, которые постоянно подключены, не обязательно отправлять все через один и тот же сервер. Вместо этого они могут использовать DNS, чтобы узнать, как отправлять исходящие сообщения непосредственно их конечным адресатам. У одного исходящего сообщения может быть несколько получателей, что требует отправки копий более чем на один удаленный сервер. Это означает, что MTA должен справляться с сообщениями, некоторые из которых не могут быть доставлены немедленно, и он должен реализовывать подходящие механизмы повторных попыток для использования с несколькими серверами. Для входящей почты домен можно настроить таким образом, чтобы почта поступала напрямую из любой точки Интернета без необходимости прохождения через промежуточный сервер.

Организация может не захотеть иметь все свои локальные почтовые ящики на одном хосте. Даже в небольшой организации с одним доменом могут быть пользователи, работающие на собственных настольных системах, которые хотят, чтобы их почта доставлялась им. Хост, на котором работает «корпоративный» MTA, теперь стал хабом, принимающим почту из мира и распределяющим ее по пользователям в своей локальной сети. Обычно в таких конфигурациях вся исходящая почта из сети проходит через хаб. Из соображений безопасности также часто настраивают сетевой маршрутизатор таким образом, чтобы прямые SMTP-соединения между миром и рабочими станциями были запрещены.

Отдельные организации могут поддерживать более одного домена, но MTA, поддерживающие очень большое количество доменов, обычно управляются интернет-провайдерами, и существует два распространенных способа их обработки:

2.2 Стандарты интернет-сообщений

Сообщения электронной почты в Интернете форматируются в соответствии с RFC 2822, который определяет формат сообщения при его передаче между хостами, но не протокол, используемый для обмена. Simple Mail Transfer Protocol (SMTP) используется для передачи сообщений между хостами (2.2.3). Это определено в RFC 2821.

Первоначальные RFC для электронной почты (821 и 822) были опубликованы в 1982 году, а впоследствии были опубликованы и другие RFC, описывающие расширения. RFC 2821 и 2822 являются последними версиями, объединяющими материалы из более ранних RFC и включающими текущую практику Интернета. На момент написания книги эти новые RFC по-прежнему имеют формальный статус «черновик», но на практике они представляют собой документы, которые сейчас используют разработчики.

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

2.2.1 Формат сообщения RFC 2822

Сообщение состоит из строк текста, и когда оно передается между хостами, каждая строка заканчивается символом возврата каретки (carriage return) (ASCII-код 13), за которым сразу следует перевод строки (linefeed) (ASCII-код 10), последовательность, которая обычно записывается как CRLF. Внутри хоста сообщения обычно хранятся для удобства в формате RFC 2822. При этом многие приложения используют соглашение локальной операционной системы для завершения строки, но некоторые используют CRLF. Обычное соглашение Unix состоит в том, чтобы заканчивать строки одним символом перевода строки без предшествующего возврата каретки.

Сообщение состоит из заголовка (header) и тела (body). Заголовок содержит ряд строк, структурированных особым образом, как определено в RFC 2822. Следующие примеры представляют собой строки заголовка, которые обычно показываются тем, кто составляет сообщение, и знакомы любому пользователю электронной почты:

From: Philip Hazel <phl0@exim.example>
To: My Readers <all@exim.book.example>,
    My Loyal Fans <fans@exim.example>
Cc: My Personal Assistant <cwbaft@exim.example>
Subject: How electronic mail works

Отдельную строку заголовка можно продолжить на несколько фактических строк, начав продолжение с пробела. Весь раздел заголовка завершается пустой строкой. Далее следует тело сообщения. В своей простейшей форме тело представляет собой неструктурированный текст, но другие RFC (MIME, RFC 1521) определяют дополнительные строки заголовков, которые позволяют разделить тело на несколько разных частей. Каждая часть может быть в другой кодировке, и существуют стандартные способы перевода двоичных данных в печатные символы, чтобы их можно было передавать с помощью SMTP. Это механизм, который используется для «вложений» сообщений.

RFC 2822 допускает множество вариантов адресов, которые появляются в строках заголовков сообщений. Например:

To: caesar@rome.example.com
To: Julius Caesar <caesar@rome.example.com>
To: caesar@rome.example.com (Julius Caesar)

Текст в круглых скобках в любом месте строки является комментарием. Это относится ко всем строкам заголовков, структура которых ограничена RFC, а не только к тем строкам заголовков, которые содержат адреса. Например, в следующей строке:

Date: Tue, 7 Jan 2003 14:20:24 -0500 (EST)

аббревиатура часового пояса является комментарием в отношении форматирования RFC 2822. Наряду с общедоступными комментариями в скобках заголовки, содержащие адреса, могут содержать последовательность слов перед фактическим адресом в угловых скобках; они обычно используются для описательного текста, такого как полное имя получателя. Когда строка заголовка содержит более одного адреса, запятая должна использоваться для завершения всех адресов, кроме последнего[2].

Термины локальная часть (local part) и домен (domain) используются для обозначения частей почтового адреса, которые предшествуют и следуют за знаком @ соответственно. В адресе электронной почты caesar@rome.example.com локальная часть — caesar, а домен — rome.example.com. Локальная часть часто является именем пользователя, но поскольку это также может быть абстракция, такая как название списка рассылки или адрес в каком-либо другом почтовом домене в сообщении, отправляемом на шлюз, здесь используется более общий термин, как это указано в справочной документации Exim.

  1. Некоторые MUA позволяют вводить списки получателей с использованием пробелов, точек с запятой или других символов в качестве разделителей; они также могут использовать эти символы при отображении адресов. Однако, когда такие списки используются для создания строк заголовка To:, Cc: или Bcc: для передачи через Интернет, разделители заменяются запятыми.

2.2.2 Сообщение «на проводе»

К сообщению, которое передается между MTA, добавляется несколько вещей сверх того, что видит составляющий его пользователь. В дополнение к разделу заголовка и телу непосредственно перед данными RFC 2822 передается еще одна часть данных, называемая конвертом (envelope), с использованием SMTP-команд MAIL и RCPT. Конверт содержит адрес отправителя и один или несколько адресов получателя. Эти адреса имеют вид <user@domain> без дополнительной текстовой информации, такой как полное имя пользователя, которая может появляться в строках заголовка сообщения.

Доставки, осуществляемые принимающим агентом MTA (либо в локальные почтовые ящики, либо путем передачи сообщения другим хостам), основаны на получателях, перечисленных в конверте, а не на строках заголовка To: или Cc: в сообщении. В случае сбоя какой-либо доставки отчет об ошибке отправляется на адрес отправителя конверта, а не на адрес, указанный в строке заголовка From: или Reply-To:.

Необходимость в отдельном конверте становится очевидной при рассмотрении сообщения с несколькими получателями. При этом почтовые ящики, на которые доставляется сообщение, могут находиться на нескольких разных хостах. В строках заголовка RFC 2822 обычно перечислены все получатели, но для доставки сообщение должно быть клонировано в отдельные копии, по одной для каждого хоста-получателя, и в каждой копии конверт содержит только тех получателей, чьи почтовые ящики находятся на этом хосте.

Помимо добавления конверта, и MUA, и MTA добавляют дополнительные строки заголовка перед передачей сообщения на другой хост. Вот пример сообщения «в пути», где на конверте указаны только два из трех получателей. В этом примере показаны только SMTP-команды и данные, которые отправляет клиент, без ответов сервера:

MAIL FROM:<phl0@exim.example>
RCPT TO:<fans@exim.example>
RCPT TO:<cwbaft@exim.example>
DATA
Received: from phli0 by draco.exim.example with local (Exim 4.01)
        id 14T1i0-000501-00;
        Fri, 15 Feb 2002 14:18:05 +0000
From: Philip Hazel <phl0@exim.example>
To: My Readers <all@exim.book.example>,
    My Loyal Fans <fans@exim.example>
Cc: My Personal Assistant <cwbaft@exim.example>
Subject: How electronic mail works
Date: Fri, 15 Feb 2002 14:18:05 +0000
Message-ID: <Pine.SOL.3.96.990117111343 .19032A-100000@
  draco.exim.example>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII

Hello,
  If you want to know about Internet mail, look at chapter 2.
.

Первые три строки — конверт; само сообщение следует за командой DATA и заканчивается строкой, содержащей только точку. Обратите внимание, что строки были добавлены как в начале, так и в конце раздела заголовка.

Перед передачей сообщения MTA MUA обычно добавляет Date: (требуется RFC 2822) и Message-ID:. MUA также может добавлять строки заголовка, такие как MIME-Version: и Content-Type:, если тело сообщения структурировано в соответствии с определениями MIME. Каждый MTA, через который проходит сообщение, добавляет в начало строку заголовка Received:, как того требует RFC 2821. Таким образом, историю маршрутизации сообщения можно получить, прочитав эти строки заголовка в обратном порядке.

Поскольку к моменту доставки сообщения может быть довольно много «закулисных» строк заголовка, большинство MUA обычно показывают только подмножество при отображении сообщения пользователю (обычно строки, содержащие адреса, тему и дату). Однако обычно есть способ настроить MUA для отображения всех строк заголовков.

Адрес получателя, указанный в конверте, может не указываться ни в одной строке заголовка самого сообщения. Обычно это происходит после того, как сообщение прошло через расширитель списка рассылки, а также является средством реализации «слепых точных копий» (blind carbon copies). Когда пользователь отправляет сообщение, либо MUA, либо первый MTA создает конверт, беря получателей из данных To:, Cc: и Bcc: и удаляя любую строку заголовка Bcc:, если нет других получателей, в этом случае пустая строка заголовка Bcc: сохраняется[3]. Альтернативной разрешенной реализацией является сохранение строки заголовка Bcc: только в тех копиях сообщения, которые передаются получателям Bcc:.

Когда сообщение доставляется в почтовый ящик пользователя, некоторые MTA, включая Exim (как обычно настраивается), добавляют строку заголовка Envelope-to:, указывающую адрес получателя конверта, который был получен с сообщением. Это может быть полезно, если конечный получатель конверта не отображается в строках заголовка. Например, рассмотрим сообщение, отправленное из списка рассылки на такой адрес, как postmaster@xyz.example, который обрабатывается псевдонимом. Сообщения из списков рассылки обычно не содержат получателя ни в одной из строк заголовка. Вместо этого, скорее всего, будет такая строка:

To: some-list@listdomain.example

Адрес postmaster@xyz.example указан только в конверте. Предположим, что псевдоним заставляет это сообщение быть доставленным в почтовый ящик пользователя по имени pat, который является локальным почтмейстером. Без добавления Envelope-to: в самом сообщении нет ничего, что указывало бы на то, почему оно оказалось в почтовом ящике Пэта.

Отправитель конверта также называется обратным адресом (return path), поскольку он используется для возврата отчетов об ошибках доставки. В большинстве личных сообщений он идентичен адресу в заголовке From:, но это не обязательно. Есть два распространенных случая, когда он отличается:

Когда сообщение доставляется в почтовый ящик пользователя, Exim (как обычно настроено) добавляет строку заголовка Return-path:, в которой он записывает отправителя конверта.

  1. RFC 2822 не разрешает пустые строки заголовка To: или Cc:; если соответствующих адресов нет, эти строки должны быть опущены. Только Bcc: может отображаться без адресов. REC 822 требовал, чтобы присутствовал хотя бы один из To:, Cc: или Bcc:; это ограничение ослаблено в RFC 2822.

2.2.3 Краткое описание протокола SMTP

SMTP — это простой текстовый протокол команд-ответов. Хост-клиент отправляет команду на сервер, а затем ждет ответа, прежде чем перейти к следующей команде[4]. Ответы всегда начинаются с трехзначного десятичного числа, например:

250 Message accepted

Текст обычно представляет собой информацию, предназначенную для интерпретации человеком, хотя есть и некоторые исключения. Число кодирует тип ответа; первая цифра является самой важной и всегда является одной из цифр, показанных в таблице 2-1.

Таблица 2-1: Коды ответов SMTP
Код Значение
2xx Команда выполнена успешно
3xx Для команды требуются дополнительные данные
4xx В команде произошла временная ошибка
5xx В команде произошла постоянная ошибка

Вторая и третья цифры дают дополнительную информацию об ответе, но MTA не должен обращать на них никакого внимания. Exim, например, полностью оперирует первой цифрой кодов ответа SMTP. Ответы могут состоять из нескольких строк текста. Для всех, кроме последнего, за кодом следует дефис; в последней строке за ним следует пробел. Например:

550-Host is not on relay list
550 Relaying prohibited by administrator

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

220 ESMTP Ready

Клиент инициализирует сеанс, отправляя команду EHLO (extended hello) (расширенное приветствие), которая дает свое собственное имя[5]. Например:

EHLO client.example.com

К сожалению, многие используемые MTA неправильно сконфигурированы, случайно или преднамеренно, так что они не дают свое правильное имя в команде EHLO. Это означает, что данные, полученные от этой команды, не очень полезны. В ответе сервера на EHLO в первой строке указывается имя сервера, за которым может следовать другой информационный текст, а в последующих строках перечисляются расширенные функции SMTP, поддерживаемые сервером. Например, следующее:

250-server.example.com Hello client.example.com
250 SIZE 10485760

указывает, что сервер поддерживает параметр SIZE с максимальным размером сообщения 10 485 760 символов.

Как только команда EHLO принята, клиент может попытаться отправить хосту любое количество сообщений. Каждое сообщение начинается с команды MAIL, которая содержит адрес отправителя конверта. Если сервер поддерживает опцию SIZE, также может быть указан размер сообщения. Например:

MAIL FROM: <caesar@rome.example> SIZE=12345

После того, как это было принято, каждый соответствующий адрес получателя передается в отдельной команде RCPT, такой как:

RCPT TO: <brutus@rome.example>

Адреса получателей берутся из конверта сообщения, а не из строк заголовка. Клиент ждет ответа на каждую команду RCPT перед отправкой следующей. Сервер может принимать одних получателей и отклонять других, постоянно или временно. После постоянной ошибки клиент не должен пытаться повторно отправить сообщение на этот адрес. Наиболее распространенными причинами постоянного отказа являются:

Временные ошибки вызваны проблемами, которые должны быть устранены в надлежащее время, такими как невозможность проверить входящий адрес из-за неработающей базы данных или нехватки места на диске. Ожидается, что после временной ошибки клиент снова попытается использовать адрес в новом SMTP-соединении после соответствующей задержки. Обычно это происходит не менее чем через 10–15 минут после первого сбоя; если состояние временной ошибки сохраняется, время между повторными попытками обычно увеличивается.

После того, как клиент передал всех получателей в командах RCPT и хотя бы один получатель был принят, клиент отправляет:

DATA

и сервер отвечает кодом 354, запрашивая дополнительные данные (само сообщение). Клиент передает сообщение, не дожидаясь дальнейших ответов, и заканчивает его строкой, содержащей только один символ точки. Если сообщение содержит какие-либо строки, начинающиеся с точки, вставляется дополнительная точка для защиты от преждевременного завершения. Сервер удаляет начальную точку из любых строк, содержащих больше текста. Если сервер возвращает ответ об успехе после отправки данных, он берет на себя ответственность за последующую обработку сообщения, и клиент может отказаться от его копии. После отправки всех своих сообщений клиент завершает сеанс SMTP, отправляя команду QUIT.

Поскольку SMTP передает конверт отдельно от самого сообщения, серверы могут отклонять адреса конвертов по отдельности до того, как будет отправлено много данных. Однако, если сервер недоволен содержанием сообщения, он не может отправить отказ до тех пор, пока не будет получено все сообщение[6]. К сожалению, некоторые клиентские программы (в нарушение RFC 2821) рассматривают любой ошибочный ответ на DATA или отслеживание самих данных как временную ошибку и продолжают попытки доставить сообщение через определенные промежутки времени.

  1. Существует необязательная оптимизация под названием «конвейерная обработка», которая позволяет отправлять пакеты команд и получать пакеты ответов, но это делается исключительно для повышения производительности. Общее поведение остается прежним, и здесь мы описываем только простой случай.

  2. Первоначальный протокол SMTP использовал HELO (sic) в качестве команды инициализации, и серверы по-прежнему обязаны это распознавать. Разница в том, что ответ на EHLO включает в себя список необязательных расширений SMTP, поддерживаемых сервером.

  3. Например, сообщение может быть слишком большим, или сервер может быть настроен на проверку синтаксиса адресов в строках заголовка.

2.3 Подделка

Подделать незашифрованную почту тривиально. Как правило, агенты MTA «незнакомы» друг с другом, поэтому принимающий агент MTA не может аутентифицировать содержимое конверта или само сообщение. Все, что он может сделать, это зарегистрировать IP-адрес хоста-отправителя и включить его в строку Received:, которую он добавляет к сообщению.

Нежелательная почта (спам) обычно содержит некоторые поддельные строки заголовка. Вы должны знать об этом, если вам когда-нибудь придется расследовать происхождение такой почты. Если сообщение содержит строку заголовка, например:

Received: from foobar.com.example ([10.9.8.7])
        by podunk.edu.example (8.9.1/8.9.1) with SMTP id DAA00447;
        Tue, 6 Mar 2001 03:21:43 -0500 (EST)

это не означает, что компания FooBar или Университет Подунка вообще обязательно участвуют; заголовок мог быть просто вставлен злоумышленником, чтобы ввести в заблуждение. Единственные заголовки Received:, на которые вы можете рассчитывать, — это заголовки в верхней части сообщения, которые были добавлены агентами передачи сообщений, работающими на хостах, администраторам которых вы доверяете. После того, как вы передадите эти заголовки Received:, те, что ниже, могут быть подделаны, даже если кажется, что они относятся к авторитетной организации, такой как интернет-провайдер.

2.4 Аутентификация и шифрование

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

2.5 Маршрутизация сообщения

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

Существует два различных типа адресов: те, для которых локальная часть используется при принятии решения о том, как доставить сообщение, и те, для которых имеет значение только домен. Обычно, когда домен относится к удаленному хосту, локальная часть адреса не играет никакой роли в процессе маршрутизации, но если домен является именем локального хоста, локальная часть имеет первостепенное значение. Шаги, которые должен выполнить MTA для обработки сообщения, следующие, хотя они не обязательно выполняются именно в таком порядке:

2.6 Проверка входящей почты

Существует ряд проверок, которые обычно применяются к входящим сообщениям от других хостов.

2.6.1 Проверка адресов получателей

Некоторые MTA проверяют правильность локальных адресов во время транзакции SMTP. Если входящее сообщение имеет неправильную локальную часть, команда RCPT, которая передает эту часть конверта, отклоняется, давая ответ об ошибке. Это означает, что отправляющий MTA сохраняет контроль над сообщением для этого получателя и является тем, кто генерирует рикошет, который возвращается отправителю. Эта проверка предотвращает прием недоставленных сообщений локальным хостом. Однако получение рикошета от MTA, который не находится на сайте, который они отправляли, сбивает некоторых пользователей с толку и заставляет их думать, что что-то не работает. Они спрашивают: «Как локальный демон почтовой программы может узнать, что это недопустимый адрес на удаленном узле?».

Альтернативный подход, принятый некоторыми MTA, состоит в том, чтобы принимать сообщения без проверки адресов получателей и выполнять проверку позже. Преимущество этого заключается в минимизации продолжительности SMTP-транзакции, а для недействительных адресов рикошеты — это то, что интуитивно ожидают пользователи. Кроме того, их можно сделать так, чтобы они содержали полезную информацию о поиске правильных почтовых адресов. Недостатком является то, что недоставленные сообщения, отправители конвертов которых также являются недействительными, приводят к недоставке рикошетов, которые должны быть отсортированы постмастером. Многие нежелательные нежелательные сообщения («спам») относятся к этому типу, и объем этих сообщений таков, что все больше и больше администраторов настраивают свои MTA для реализации прежнего поведения.

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

Exim может быть сконфигурирован, чтобы вести себя любым из этих способов, и поведение может быть сделано условным на домене адреса отправителя. Например, все адреса из локальной среды могут быть приняты, а неизвестные переданы программе, которая отправляет обратно полезное сообщение, а неизвестные адреса извне могут быть отклонены в протоколе SMTP. Однако, из-за высокого уровня злоупотреблений конфигурация Exim по умолчанию проверяет все адреса получателей во время SMTP.

2.6.2 Проверка адресов отправителей

Не все MTA проверяют правильность адресов отправителей конвертов. Они могут быть недействительны по ряду причин, например:

Как правило, проверка адресов отправителей обычно ограничивается проверкой регистрации домена в DNS. Exim содержит средство для создания «выноски», чтобы убедиться, что входящий адрес отправителя приемлем в качестве получателя для хоста, который обрабатывает его домен, но это дорогостоящий подход, который не всегда подходит для использования на загруженных серверах.

2.6.3 Другие элементы управления политикой

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

2.7 Обзор DNS

DNS — это всемирная распределенная база данных, в которой хранятся различные виды данных, проиндексированных ключами, называемыми доменными именами (domain names). Вот очень краткий обзор средств, имеющих отношение к обработке почты[7]. Данные хранятся в единицах, называемых записями (records), каждая из которых содержит ряд полей, из которых имя домена, тип записи и данные, относящиеся к типу записи, относятся к приложениям, использующим DNS[8]. Например, для записи:

www.web.example. A 192.168.6.4

доменное имя — www.web.example, тип записи — «A» (для «адреса»), а данные — 192.168.6.4. Записи адресов, подобные этой, используются для поиска IP-адресов хостов по их именам и, вероятно, являются наиболее распространенным типом записей DNS.

Нынешняя схема адресации в Интернете, которая использует 32-битные адреса и известна как IPv4, дополняется новой схемой, называемой IPv6, которая использует 128-битные адреса. Поддержка IPv6 становится обычным явлением в операционных системах и прикладном программном обеспечении, а IPv6 уже широко используется в некоторых частях мира, где не хватает адресов IPv4. Некоторые люди считают, что IPv6 в конечном итоге полностью заменит IPv4, хотя сроки этого изменения неясны.

Адреса IPv6 обычно записываются в шестнадцатеричном формате с использованием двоеточий-разделителей между каждой парой октетов. В DNS запись AAAA которая является прямым аналогом записи A, используется для их записи. Например:

ipv6.example. AAAA 3ffe:ffff:836f:0a00:000a:0800:200a:c031

Второй тип записи, А6, также был определен для хранения адресов IPv6 (см. RFC 2874). Это обеспечивает более гибкую схему, в которой префиксные части адресов IPv6 могут храниться отдельно. Сначала ожидалось, что A6 заменит тип AAAA. но это не понравилось всем, и поэтому этот тип был переведен в экспериментальный статус.

Если хост имеет более одного IP-интерфейса, каждый появляется в отдельной записи адреса с тем же доменным именем. Регистр букв в доменных именах DNS не имеет значения, а отдельные компоненты имени могут содержать широкий диапазон символов. Например, запись в DNS может иметь доменное имя abc_xv#2.example.com. Однако, символы, которые используются для имен хостов, ограничены RFC 952 буквами, цифрами и дефисами, и те же ограничения применяются к доменам, которые используются в адресах электронной почты.

Невозможно отправить почту на такой адрес, как user@abe_xvi#2.example.com, используя SMTP, из-за символов в домене, которые недопустимы в соответствии с RFC 2821. По этой причине, все доменные имена MX (описанные ниже) и имена хостов используют только ограниченный набор символов. Это ограничение часто ошибочно принимают за внутреннее ограничение DNS, но это не так[9].

Серверы, реализующие DNS, называются серверами имен (name servers), и распространяются по всему Интернету. Иерархическое пространство имен разбито на зоны (zones), каждый из которых управляется собственным администратором и хранится на своем собственном главном сервере. Разделение на зоны, хранящиеся на независимых серверах, делает возможным управление таким большим набором данных[10].

Точки останова между зонами всегда находятся между компонентами доменного имени, но не обязательно на каждой границе. Например, есть зона uk, зоны ac.uk и cam.ac.uk, но отдельной зоны csx.cam.ac.uk нет, хотя есть доменные имена, оканчивающиеся на эти компоненты. Данные для этих имен хранятся в зоне cam.ac.uk. Это не мешает другим зонам ниже cam.ac.uk. Этот пример проиллюстрирован на рис. 2-4 с использованием пунктирных эллипсов для обозначения зон.

Рисунок 2-4: Домены и зоны DNS

Обычно для зоны используется один главный сервер имен и несколько подчиненных, которые копируют свои данные с главного. Один сервер имен может быть ведущим для одних зон и подчиненным для других. Любой сервер имен (главный или подчиненный), имеющий собственную полную копию файла зоны, считается авторитетными (authoritative) для этой зоны. Вы иногда будете видеть это слово, используемое в выводе таких команд, как host, которые опрашивают DNS. Данные, полученные сервером имен от какого-либо другого сервера имен без передачи всей зоны, не являются авторитетными.

Предпочтительно, чтобы ведомые устройства находились по крайней мере в разных локальных сетях по отношению к ведущему, а лучше всего, если некоторые из них находились в совершенно разных местах, чтобы максимально увеличить доступность зоны для запросов. Интернет-провайдеры обычно предоставляют своим клиентам средства подчинения серверов имен. Сервер имен для зоны, в которой есть подзоны, знает расположение серверов этих зон. В основе иерархии лежат корневые (root) серверы имен в «хорошо известных» местах в Интернете.

Кэширование широко используется в программном обеспечении DNS для повышения производительности. Каждая запись содержит поле времени жизни, и серверы имен имеют право запоминать и повторно использовать данные в течение этого периода времени. Типичное время жизни составляет около одного дня. Поиск данных осуществляется путем передачи доменного имени и типа на ближайший сервер имен; если недавно был запрос на те же данные, они будут в кеше сервера, и на запрос можно будет ответить немедленно. В противном случае, если сервер не закэшировал идентификационные данные серверов имен для требуемой зоны, он может запросить их напрямую, но если у него нет соответствующей информации, он начинает с запроса одного из корневых серверов имен и продвигается вниз по зональнай иерархиии. Например, если запрос на www.cam.ac.uk получен корневым сервером имен, он отвечает списком серверов имен для зоны uk. Запрос к одному из них приводит к получению списка серверов имен для зоны ac.uk и так далее, пока не будет достигнут сервер имен, содержащий фактические данные.

  1. Полное обсуждение см. в статье DNS and BIND Пола Альбитца и Крикета Лю (O'Reilly). Основные DNS RFC — 1034 и 1035.

  2. Другие поля связаны с внутренним управлением самой DNS.

  3. См. также RFC 2181, Пояснения к спецификации DNS.

  4. До DNS список хостов Intemet хранился в одном файле, который нужно было полностью скопировать на все хосты.

2.8 DNS-записи, используемые для маршрутизации почты

Домен в почтовом адресе не обязательно должен соответствовать имени хоста. Например, организация может использовать домен pic.example.com для всей своей электронной почты, но обрабатывать ее с помощью узлов с именами mail-1.plc.example.com и mail-2.plc.example.com. Такая гибкость достигается за счет использования записей обмена почтой (mail exchange) (MX) в DNS. Запись MX сопоставляет почтовый домен с хостом, который зарегистрирован как обрабатывающий почту для этого домена, со значением предпочтения. Для домена может быть любое количество MX-записей, и при запросе к серверу имен он возвращает их все. Например:

hermes.example.com. MX 5 green.csi.example.com.
hermes.example.com. MX 7 sw3.example.com.
hermes.example.com. MX 7 sw4.example.com.

показывает три хоста, которые обрабатывают почту для hermes.example.com. Значения предпочтений можно рассматривать как расстояния от цели; чем меньше значение, тем предпочтительнее соответствующий хост, поэтому в данном примере наиболее предпочтительным является green.csi.example.com. MTA, доставляющий почту для hermes.example.com, сначала пытается доставить почту на green.csi.example.com; если это не удается, он пробует менее предпочтительные хосты в порядке их значений предпочтений. Используется только числовой порядок предпочтений; абсолютные значения не имеют значения. Когда есть записи MX с одинаковыми значениями предпочтения (как в этом примере), они упорядочиваются случайным образом перед использованием.

Прежде чем MTA сможет использовать список хостов, который он получил из записей MX, он сначала должен найти IP-адреса для хостов. Для этого он ищет соответствующие записи адресов (записи A для IPv4 и записи AAAA для IPv6). Например, это могут быть следующие записи адресов:

green.csi.example.com. A 192.168.8.57
sw3.example.com. A 192.168.8.38
sw4.example.com. A 192.168.8.44

На практике, если сервер имен уже имеет запись адреса для любого хоста в списке MX, который он возвращает, он отправляет запись адреса вместе с записями MX. Во многих случаях это позволяет избежать дополнительного DNS-запроса.

На заре DNS не было записей MX, а почтовые домены соответствовали именам хостов. Для обратной совместимости с тем временем, если для домена нет записей MX, MTA имеет право искать запись адреса и обрабатывать ее так, как если бы она была получена из записи MX с нулевым значением предпочтения (наиболее предпочтительно). Однако, если он не может определить, существуют ли какие-либо записи MX (поскольку, например, соответствующие серверы имен недоступны), он не должен этого делать.

Записи MX изначально были изобретены для использования шлюзами других почтовых систем, но в настоящее время они широко используются для реализации «корпоративных» почтовых доменов, которые не обязательно соответствуют какому-то одному конкретному хосту.

2.9 Связанные записи DNS

Два других вида записей DNS полезны при работе с почтой. Записи PTR ("pointer") сопоставляют IP-адреса с именами через специальные зоны, называемые in-addr.arpa для адресов IPv4 и ip6.arpa для адресов IPv6[11]. Записи PTR позволяют осуществлять поиск хоста в обратном порядке: при наличии IP-адреса записи PTR позволяют узнать соответствующее имя хоста. Имя записи PTR состоит из IP-адреса, за которым следует один из специальных доменов. Однако для доменов in-addr.arpa и ip6.arpa компоненты адреса меняются местами, чтобы разрешить делегирование DNS частей IP-сети. Для адреса 192.168.8.57 запись PTR будет такой:

57.8.168.192.in-addr.arpa. PTR green.csi.example.com.

При этом регистрируется, что имя хоста с IP-адресом 192.168.8.57 — green.csi.example.com. Для IPv6-адресов в домене ip6.arpa компоненты, которые перевернуты, представляют собой шестнадцатеричные цифры. Для адреса:

3ffe:ffff:836f:0a00:000a:0800:200a:c031

имя записи PTR выглядит следующим образом:

1.3.0.c.a.0.0.2.0.0.8.0.a.0.0.0.0.0.a.0.£.6.3.8.ff.f.ff.f.e.f.f.3.ip6.arpa.

Записи PTR не обязательно должны совпадать с соответствующей записью адреса. В примере из предыдущего раздела адресная запись отображается как:

pw4.example.com. A 192.168.8.44

Если вы используете адрес 192.168.8.44 для поиска имени хоста через запись PTR, вы можете найти имя sw4.example.com или что-то совершенно другое, например:

44.8.168.192.in-addr.arpa. PTR lilac.csi.example.com.

Эта запись дает имя lilac.csi.example.com для адреса 192.168.8.44, несмотря на то, что адрес был дан для имени sw4.example.com. Такое расположение часто встречается там, где широко публикуется название какой-либо службы с адресной записью, указывающей на хост, который в настоящее время предоставляет услугу. Однако сам хост имеет другое основное имя, которое содержится в записи PTR.

Например, используемое нами имя sw4.example.com может быть именем службы коммутации почты, которая в настоящее время предоставляется хостом lilac.csi.example.com. Перемещение службы на другой хост требует лишь обновления DNS; ни один хост не должен менять свое имя. Если услугу предоставляют несколько хостов, для одного и того же домена может существовать несколько адресных записей. Современные серверы имен возвращают их в другом порядке при каждом запросе, что обеспечивает форму распределения нагрузки.

Между записями адресов и записями PTR нет принудительной связи, и для любого заданного хоста одна может существовать без другой. Эти записи в основном используются в связи с почтой для поиска имени удаленного хоста, отправляющего сообщение, потому что все, что первоначально известно о хосте на дальнем конце входящего TCP/IP-соединения, — это его IP-адрес. Имя хоста может потребоваться для проверки правил политики, определяющих, какие типы сообщений могут отправлять удаленные хосты.

Записи CNAME ("canonical name") предоставляют другой вид средства псевдонима. Например:

pelican.example.com. CNAME redshank.csx.example.com.

утверждает, что каноническое имя (настоящее или основное) хоста, доступ к которому можно получить как pelican.example.com, на самом деле является redshank.csx.example.com. Не может быть других записей DNS с тем же именем, что и у записи CNAME. Записи CNAME обычно не должны использоваться в связи с маршрутизацией почты. Записи MX обеспечивают достаточные возможности перенаправления, а чрезмерное использование псевдонимов только замедляет работу.

  1. Доменное имя верхнего уровня arpa уходит своими корнями в историю. Оно относится к исходной глобальной сети под названием Arpanet, но теперь было переименовано в аббревиатуру от Address and Routing Parameter Area.

2.10 Распространенные ошибки DNS

Ряд ошибок, обычно допускаемых администраторами DNS (которых обычно называют «hostmasters» (администраторы)), показан в следующем списке. Все, кроме первого, препятствуют доставке почты.

Некоторые сломанные серверы имен выдают ошибку сервера при запросе несуществующей записи MX. Это предотвращает доставку почты, поскольку агенту MTA разрешено искать запись адреса только в том случае, если он уверен, что записей MX нет. В случае ошибки сервера MTA об этом не знает. Подобные ошибки сервера наблюдались в случаях, когда значение предпочтения было опущено в записи MX. Более надежные серверы имен проверяют записи при загрузке своих зон и выдают ошибку, если какие-либо зоны содержат неверные данные.

Иногда кажется, что DNS дает разные ответы на одинаковые запросы. В контексте почты это приводит к тому, что некоторые сообщения отклоняются с ошибками «unknown domain», в то время как другие сообщения в тот же домен доставляются нормально. Наиболее распространенной причиной такого поведения является несоответствие серверов имен зоны. Если вы подозреваете это, вы можете проверить, направив DNS-запрос на определенный сервер имен.

Первый шаг — найти соответствующие серверы имен, просматривая записи NS зоны. Чтобы найти серверы имен для зоны ioe.example.com, например, вы можете использовать команду:

$ nslookup -type=ns ioe.example.com

который мог бы дать эти строки как соответствующие части своего ответа[12]:

ioe.example.com    nameserver = mentor.ioe.example.com
ioe.example.com    nameserver = ns0.example.net

Как только вы узнаете серверы имен, вы можете запросить каждый из них по очереди для рассматриваемого домена; если команде nslookup дается второй аргумент, это имя определенного сервера имен, на который должен быть отправлен запрос. Эта последовательность команд и ответов (где команды выделены жирным шрифтом) указывает на наличие проблемы, поскольку разные серверы имен дают противоречивые ответы:

$ nslookup saturn.example.com mentor.ioe.example.com
Server:  mentor.ioe.example.com
Address:  192.168.34.22

Name:  saturn.example.com
Address:  192.168.5.4
$ nslookup saturn.example.com ns0.example.net
Server:  ns0.example.net
Address:  192.168.255.249

*** nsO.example.net can’t find saturn.example.com:
    Non-existent host/domain

Однако проблема может быть временной. При обновлении главного сервера имен может пройти несколько часов, прежде чем данные достигнут подчиненных серверов, в течение которых может наблюдаться такое поведение. Однако, если несоответствие сохраняется какое-то время, это указывает на какую-то ошибку DNS.

  1. nslookup — одно из приложений, которое пропускает конечные точки при отображении доменных имен.

2.11 Роль постмастера

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

Традиционный способ сделать это — сохранить псевдоним адреса postmaster@your.domain, который перенаправляет на человека, который в настоящее время выполняет роль администратора почты. Действительно, RFC утверждают, что postmaster всегда должен поддерживаться как локальное имя без учета регистра.

Глава 3
Обзор Exim

В предыдущей главе работа MTA описана в общих чертах. В этой главе мы объясним, как Exim организован для выполнения этой работы, и каков общий способ его работы. Затем в следующей главе мы рассмотрим основы администрирования Exim, прежде чем перейти к более подробной информации о его настройке.

3.1 Философия Exim

Exim разработан для использования в сети, где большинство сообщений могут быть доставлены с первой попытки. Обычно это верно для большей части Интернета. Измерения, проведенные в среде автора (британский университет), показывают, что более 90% сообщений доставляются почти сразу при нормальных условиях. Это означает, что нет необходимости в сложном централизованном механизме очередей, через который проходят все сообщения. Когда приходит сообщение, немедленная попытка доставки, скорее всего, будет успешной; только для небольшого количества сообщений необходимо реализовать механизм сохранения и пересылки.

Следовательно, несмотря на то, что Exim можно сконфигурировать по-другому, нормальным действием является попытка немедленной доставки, как только сообщение будет получено. Во многих случаях это удается, и для обработки сообщения больше ничего не требуется. Тем не менее, необходимо принять некоторые меры предосторожности, чтобы избежать перегрузки системы во время стресса. Например, если нагрузка на систему превысит некоторый порог или имеется большое количество одновременных входящих SMTP-соединений, немедленная доставка может быть временно отключена. В этих случаях входящие сообщения ждут в очереди Exim'а и доставляются позже.

Все операции выполняются одним двоичным файлом Exim, который работает по-разному, в зависимости от аргументов, с которыми он вызывается. Хотя получение и доставка сообщений рассматриваются как совершенно отдельные операции, в обоих случаях требуется код для определения способа доставки на конкретный адрес, поскольку во время приема сообщения адреса проверяются путем проверки возможности доставки на них. Например, Exim проверяет адрес удаленного отправителя, ища домен в DNS точно так же, как при настройке доставки на этот адрес.

В системе, где Exim полностью установлен как замена Sendmail, один или оба пути /usr/lib/sendmail или /usr/sbin/sendmail являются символической ссылкой на двоичный файл Exim. Следовательно, любой MUA, программа или сценарий, который пытается отправить сообщение, вызывая Sendmail, на самом деле вызывает Exim[1].

  1. Системы на основе Linux и BSD, как правило, используют /usr/sbin/sendmail, тогда как Solaris использует /usr/lib/sendmail. Разные MUA имеют разные значения по умолчанию, поэтому некоторые администраторы устанавливают оба пути для обеспечения безопасности.

3.2 Очередь Exim'а

Слово queue (очередь) используется для обозначения набора сообщений, которые Exim контролирует в любой момент времени, поскольку это слово часто используется в контексте передачи почты. Тем не менее, очередь Exim обычно рассматривается как коллекция сообщений без подразумеваемого порядка, больше похожая на «пул», чем на «очередь». Более того, Exim не поддерживает отдельные очереди для разных доменов или разных удаленных хостов. Существует только один неупорядоченный набор сообщений, ожидающих доставки, у каждого из которых может быть несколько получателей. Если вы являетесь администратором Exim, вы можете вывести список сообщений в очереди, выполнив команду:

exim -bp

предполагая, что ваш путь содержит каталог, в котором находится исполняемый файл Exim. Администраторы Exim называются «admin users» (пользователями-администраторами) (19.3.2).

Сообщения, которые не доставляются сразу по прибытии, подхватываются позже процессами queue runner (обработчика очереди), которые сканируют всю очередь и запускают процесс доставки для каждого сообщения по очереди. Процесс запуска очереди ожидает завершения каждого процесса доставки перед запуском следующего.

3.3 Получение и доставка сообщений

Прием сообщения и доставка сообщения — это две совершенно разные операции в Exim, и их единственная связь в том, что Exim обычно пытается доставить сообщение, как только он его получил. Получение сообщения состоит из его записи в локальные буферные файлы («постановка в очередь») и проверки того, что файлы были успешно записаны, прежде чем подтвердить получение отправляющему хосту или локальному процессу. Существует только одна копия каждого сообщения, сколько бы у него ни было получателей, а совокупность буферных файлов представляет собой очередь; нет никаких дополнительных файлов или списков сообщений в памяти.

Все данные о состоянии сообщения хранятся в его буферных файлах. Каждая попытка доставки заставляет каждый недоставленный адрес получателя обрабатываться заново. Exim выполняет псевдонимы, переадресацию и расширение списка рассылки для локальных адресов (где указано) и поиск домена для удаленных адресов каждый раз, когда он обрабатывает сообщение. Обычно он не сохраняет предыдущие псевдонимы, перенаправления или расширения списка рассылки от одной попытки доставки к другой.

Однако есть одно исключение: если для списка рассылки установлена опция one_time, адреса списка добавляются в исходный список получателей при первой попытке доставки, и при последующих попытках повторного расширения не происходит (> 5.3. 3).

3.4 Exim-процессы

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

  1. Чтобы прослушивать порт SMTP для входящих подключений TCP/IP. Получив такое соединение, прослушиватель запускает новый процесс для обработки этого соединения. Можно установить верхний предел количества одновременно активных процессов приема SMTP. При достижении лимита дополнительные SMTP-соединения отклоняются.

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

Для выполнения обеих этих функций может использоваться один процесс-демон, и это наиболее распространенная конфигурация. Однако можно запустить Exim вообще без использования демона; inetd может использоваться для приема входящих SMTP-соединений и запуска процесса Exim для каждого из них, а процессы запуска очереди могут быть запущены с помощью cron или других средств. Однако в этих случаях Exim не может контролировать, сколько таких процессов запущено, поэтому, если вы беспокоитесь о перегрузке системы, вы должны сами контролировать количество процессов[2].

  1. xinetd (www.xinetd.org) является заменой inetd, которая включает дополнительные средства управления.

3.5 Координация между процессами

Процессы получения и доставки сообщений по большей части полностью независимы. Небольшая необходимая координация достигается за счет обмена файлами. Минимизация требований к синхронизации и сериализации между процессами помогает Exim хорошо масштабироваться. Помимо самих сообщений, общие данные состоят из ряда файлов, содержащих «подсказки» о доставке почты. Например, если не удается связаться с удаленным хостом, записывается время сбоя и предложение в следующий раз попробовать этот хост. Любой процесс доставки, у которого есть сообщение для этого хоста, прочитает подсказку и воздержится от попытки доставки, если время повторной попытки не истекло. Это не влияет на доставку одного и того же сообщения другим хостам, если имеется более одного адреса получателя.

Поскольку координирующие данные обрабатываются как набор подсказок, потеря части или всех данных не является серьезной катастрофой; может быть период менее оптимальной доставки почты, но это все. Следовательно, код, поддерживающий подсказки, может быть простым, потому что его не нужно делать устойчивым к нестандартным обстоятельствам.

3.6 Как настроен Exim

Информация о конфигурации, предоставляемая администратором, используется в два разных времени: один файл конфигурации используется при сборке бинарного файла Exim, а другой считывается всякий раз, когда бинарный файл запускается. Большинство параметров можно указать только в одном из этих файлов. То есть они либо контролируют построение бинарного файла, либо изменяют его поведение во время выполнения. Однако есть несколько параметров времени сборки, которые устанавливают значения по умолчанию для поведения во время выполнения. Источники информации о конфигурации Exim показаны на рисунке 3-1.

Рисунок 3-1: Конфигурация Exim

Варианты времени сборки бывают трех видов:

Процесс сборки Exim из исходников описан в главе 22. Здесь мы рассмотрим конфигурацию среды выполнения. Это контролируется одним текстовым файлом, часто называемым чем-то вроде /usr/exim/configure или /etc/exim.conf. Вы можете узнать фактическое имя, выполнив следующую команду:

exim -bP configure_file

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

Для очень простых установок можно включить все данные конфигурации в файл конфигурации среды выполнения. Конфигурация этого типа показана в следующей главе (4.3.2). Однако обычно конфигурация времени выполнения относится к вспомогательным данным, которые могут находиться в обычных файлах или в базах данных, таких как NIS или LDAP. Типичными примерами являются системный файл псевдонимов (обычно называемый /etc/aliases) и файлы пользователей .forward. Файлы или базы данных также можно использовать для списков хостов, доменов или адресов, которые должны обрабатываться каким-то особым образом и которые слишком длинны, чтобы их было удобно включать в сам файл конфигурации. Данные из таких источников считываются заново каждый раз, когда они необходимы, поэтому обновления вступают в силу немедленно, и нет необходимости посылать демону сигнал SIGHUP.

Самый простой элемент, который можно найти в файле конфигурации среды выполнения, — это параметр, для которого задана фиксированная строка. Например, следующая строка:

qualify domain = example.com

указывает, что адреса, содержащие только локальную часть и не содержащие домена, должны быть преобразованы в полные адреса («квалифицированные») путем добавления @example.com[3]. Каждая такая настройка появляется на отдельной строке. Для многих настроек опций достаточно фиксированных данных, но Exim также предоставляет способы предоставления данных, которые переоцениваются и модифицируются каждый раз, когда они используются. Примеры и пояснения этой функции представлены далее в этой главе.

  1. Неквалифицированные адреса принимаются только от локальных процессов или от определенных удаленных хостов, которые вы явно указываете.

3.7 Как Exim доставляет сообщения

Конфигурация Exim определяет, как он обрабатывает адреса; эта обработка включает в себя поиск информации о получателях сообщения и о том, как доставить его к этим адресатам. В этом и следующем разделах мы обсудим, как настроенная вами конфигурация управляет тем, что происходит.

Существует множество различных способов обработки адреса. Например, поиск домена в DNS включает в себя совершенно другой способ обработки, чем поиск локальной части в файле псевдонимов, а доставка сообщения с использованием SMTP через TCP/IP имеет очень мало общего с добавлением его в файл почтового ящика. В Exim есть отдельные блоки кода для выполнения различных видов обработки, и каждый отдельно и независимо настраивается. Слово driver (драйвер) используется как общий термин для одного из этих кодовых блоков. Во многих случаях, когда вы указываете, что должен использоваться конкретный драйвер, вам нужно указать для него только один или два параметра. Однако у большинства драйверов есть ряд других параметров, значения по умолчанию которых можно изменить, чтобы изменить их поведение.

Есть три разных типа драйверов. Два из них связаны с обработкой адресов и доставкой сообщений и называются routers (роутерами) и transports (транспортами). Работа роутеров состоит в том, чтобы обрабатывать адреса и решать, какие доставки должны иметь место. Транспорты, с другой стороны, являются компонентами Exim, которые фактически доставляют сообщения, записывая их в файлы, или в каналы, или через соединения SMTP. Драйвер третьего типа обрабатывает аутентификацию SMTP и описан в главе 13.

Прежде чем углубиться в детали, мы кратко рассмотрим, как используются драйверы, когда сообщение проходит через систему. Exim должен решить, должен ли каждый адрес быть доставлен на локальный хост или на удаленный. Затем он должен выбрать правильную форму транспорта для каждого адреса (например, добавление к почтовому ящику пользователя или подключение к другому хосту через SMTP), и, наконец, он должен вызвать эти транспорты. Например, в типичной конфигурации сообщение, адресованное bug_reports@exim.example, где exim.example — локальный домен, может обрабатываться следующим образом:

  1. Первый роутер в конфигурации — это роутер dnslookup, который обрабатывает адреса, не принадлежащие локальному домену, путем поиска записей MX в DNS. Поскольку рассматриваемый нами адрес находится в локальном домене, этот роутер пропускается.

  2. Следующий роутер обрабатывает системные псевдонимы; он говорит Exim проверить файл /etc/aliases. Здесь он обнаруживает, что локальная часть bug_reports действительно является псевдонимом, и что он разрешается в два других адреса: локальный адрес brutus@exim.example и удаленный адрес julia@helpersys.org.example. Exim добавляет эти два новых адреса в список получателей, которых он маршрутизирует. Теперь он закончил с оригинальным адресом.

  3. Два новых адреса рассматриваются по очереди. Для первого из них, brutus@exim.example, снова пропускается первый роутер, потому что домен локальный. В этот раз второй роутер не находит алиас для brutus, поэтому адрес передается следующим роутерам. Один из них — это роутер, который распознает локальных пользователей, таких как brutus, и организует для Exim запуск транспорта, называемого appendfile, который добавляет копию сообщения в почтовый ящик Brutus'а. Фактическая доставка не происходит до тех пор, пока Exim не разработает, как обрабатывать все адреса.

  4. Для другого получателя, поскольку домен helpersys.org.example не является локальным, запускается первый роутер. Он ищет домен в DNS и находит IP-адрес удаленного хоста, на который должно быть отправлено сообщение. Затем он организует для Exim запуск smtp-транспорта для выполнения доставки.

В этом примере представлено несколько наиболее часто используемых драйверов. Далее в этой главе мы рассмотрим аналогичный пример более подробно. Отдельные драйверы описаны в отдельных разделах последующих глав; вот их алфавитный список:

accept

Роутер, принимающий любой переданный ему адрес. Обычно это ограничивается одним или несколькими предварительными условиями, как описано в следующем разделе. Например, accept используется с опцией check_local_user, чтобы принимать сообщения для локальных пользователей. Без каких-либо предварительных условий accept можно использовать в качестве «улавливающего» роутера.

appendfile

Транспорт, который записывает сообщения в локальные файлы. Его можно настроить либо для добавления к одному файлу, содержащему несколько сообщений, либо для записи нового файла для каждого сообщения.

autoreply

Транспорт, генерирующий автоматические ответы на сообщения.

dnslookup

Роутер, который ищет домены в DNS и выполняет обработку MX.

ipliteral

Роутер, который обрабатывает «литеральные IP-адреса», такие как user@[192.168.5.6]. Это реликвии раннего Интернета, которые больше не используются.

lmtp

Транспорт, доставляющий сообщения внешним процессам с использованием протокола LMTP (RFC 2033), который представляет собой вариант SMTP, предназначенный для передачи сообщений между локальными процессами.

manualroute

Роутер, который маршрутизирует домены, используя локальную информацию, такую как список доменов и соответствующих хостов.

pipe

Транспорт, который передает сообщения внешним процессам через конвейеры.

queryprogram

Роутер, который запускает внешнюю программу для маршрутизации адреса.

redirect

Роутер, который обрабатывает несколько различных видов перенаправления, включая файлы псевдонимов, файлы .forward пользователей и файлы фильтров Exim. Это также может явно вызвать сбой адреса.

smtp

Транспорт, который записывает сообщения на другие хосты через соединения TCP/IP, используя либо SMTP, либо LMTP.

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

3.8 Обработка адреса

При маршрутизации адреса Exim предлагает его каждому настроенному роутеру по очереди, пока один из них не сможет с ним справиться. Поэтому важен порядок, в котором роутеры определены в файле конфигурации. Однако перед запуском роутера Exim сначала проверяет ряд предварительных условий. Если какое-либо из них не выполняется, роутер пропускается. Например, роутер можно настроить так, чтобы он применялся только к определенным доменам или локальным частям. Можно протестировать широкий диапазон условий; в крайнем случае, вы даже можете ограничить роутеры определенным временем суток, если хотите. Процесс маршрутизации адреса показан на рисунке 3-2.

Рисунок 3-2: Маршрутизация адреса

Роутер, который успешно обрабатывает адрес, может добавить этот адрес в очередь для определенного транспорта. В качестве альтернативы роутер может сгенерировать один или несколько «дочерних» адресов, которые добавляются в список адресов сообщения и обрабатываются самостоятельно, при этом исходный адрес больше не играет никакой роли. Вот что происходит, когда локальная часть совпадает с записью в списке псевдонимов или когда активирован файл .forward пользователя.

Когда роутер не может обработать адрес, говорят, что он отклоняется (decline). Когда это происходит, адрес по умолчанию передается следующему роутеру. Если каждый роутер отклоняет или пропускается, адрес вообще не может быть обработан, и доставка невозможна. Однако вы можете сократить процесс маршрутизации, установив параметр more на роутере в значение false. В этом случае адреса, которые вызывают отказ роутера, не передаются. Вместо этого маршрутизация немедленно завершается ошибкой. Например, первый роутер в конфигурации часто является роутером dnslookup, у которого есть предварительное условие для проверки того, что домен не является локальным, вместе с параметром more, установленным в false. Для локального домена роутер пропускается и используются следующие роутеры, поскольку предварительное условие не выполняется. Для нелокального домена роутер выполняет поиск DNS. В случае успеха адрес направляется на подходящий транспорт; если это не удается, роутер отклоняется, но из-за ложной настройки больше никакие дальнейшие роутеры не запускаются. Это означает, что последующие роутеры могут предположить, что они имеют дело только с локальными доменами.

Логические параметры, такие как more, можно задать с помощью слов true и false, как в этом примере:

more = false

Альтернативный синтаксис допускает имя опции само по себе для установки true и имя, которому предшествует no_ для установки false. Таким образом, часто можно увидеть конфигурации роутера, содержащие строку no_more, которая имеет тот же эффект, что и в приведенном выше примере.

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

defer

Роутер может обрабатывать адрес, но в настоящее время он не может этого сделать. Как правило, это происходит, когда некоторые данные, требуемые роутером, недоступны. Например, база данных может быть отключена или время поиска DNS истекло. Доставка по адресу откладывается до более поздней попытки доставки.

fail

Роутер распознает адрес и знает, что маршрутизация невозможна. Например, вы можете вести список бывших сотрудников и сделать так, чтобы почта для них не выдавалась со специальным сообщением вместо стандартного «unknown user», в попытке уменьшить количество запросов к вашему постмастеру.

pass

Роутер распознает адрес, но не может сам с ним справиться. Он запрашивает, чтобы адрес был передан более позднему роутеру, отменяя установку false для more.

Рисунок 3-3 иллюстрирует эти результаты для отдельного роутера. Однако большинство роутеров ограничены лишь некоторыми из этих возможностей.

Рисунок 3-3: Отдельный роутер

3.9 Простой пример в деталях

Чтобы прояснить только что описанные механизмы и представить некоторые детали файла конфигурации среды выполнения, здесь представлен полный пример доставки сообщения. Сценарий представляет собой хост с именем simple.example, где имя хоста является единственным локальным почтовым доменом. Хост использует простой файл конфигурации Exim, который поддерживает псевдонимы, файлы переадресации пользователей, доставку в почтовые ящики локальных пользователей и удаленную доставку SMTP. Предположим, пользователь этого хоста отправил сообщение, адресованное одному локальному и одному удаленному получателю:

postmaster@simple.example
friend@another.example

В начале доставки список адресов для обработки Exim'ом инициализируется двумя первоначальными получателями, и его первая задача — проработать этот список, решив, что делать с каждым адресом. Мы предполагаем, что он начинается с маршрутизации postmaster@simple.example. Первый роутер в конфигурации такой:

notlocal:
  driver = dnslookup
  domains = ! simple.example
  transport = remote smtp
  no_more

Первая строка, оканчивающаяся двоеточием, — это имя данного экземпляра роутера, выбранное системным администратором. Каждый экземпляр драйвера определенного типа (роутер или транспорт) должен иметь отличное имя. Вторая строка конфигурации указывает, какой это тип роутера (или, другими словами, она выбирает, какой блок кода роутера запускать), а остальные строки являются опциями для роутера. Параметры определяют как предварительные условия, так и параметры, которые использует роутер.

Единственным предварительным условием для этого роутера является настройка параметра domains, в котором указываются домены, которые обрабатывает роутер. В этом случае в списке указан только один домен, которому предшествует восклицательный знак. Это признак отрицания в списках конфигурации Exim. Таким образом, это предварительное условие означает «домен не должен быть simple.example». Следовательно, этот роутер пропускается при обработке postmaster@simple.example, и Exim переходит к следующему роутеру, конфигурация которого выглядит следующим образом:

system_aliases:
  driver = redirect
  data = ${lookup{$local_part}lsearch{/etc/aliases} }

Роутер redirect реализует различные виды перенаправления адресов, и в данном случае он настроен на поиск в файле системных псевдонимов (/etc/aliases), чтобы определить, является ли локальная часть адреса псевдонимом.

Значение параметра data отличается от настроек, с которыми мы встречались до сих пор, и все они были фиксированными значениями. По большей части гибкость конфигурации Exim достигается за счет использования настроек опций, где указанные строки содержат переменные и другие специальные элементы. Каждый раз, когда такая строка используется, она подвергается процессу, известному как расширение строки (string expansion), в котором переменные заменяются их текущими значениями, а другие специальные элементы заменяются результатами обработки текста различными способами. Расширение строки используется во многих примерах в этой книге. Полное описание всех возможностей расширения дано в главе 17. Здесь мы приведем лишь краткое описание этого конкретного примера.

Встроенные в строку символы доллара запускают механизм раскрытия. Первый из них вводит элемент поиска, который заменяется данными, которые ищутся в файле. Второй доллар вводит переменную замену; в этом случае значение переменной $local_part используется как ключ для поиска. Как вы могли догадаться, $local_part содержит локальную часть обрабатываемого адреса, в данном случае это postmaster.

Оставшаяся часть элемента поиска определяет файл и способ его поиска. В данном случае это файл /etc/aliases, и требуется линейный поиск («lsearch»). Это предполагает, что каждая строка файла будет содержать имя псевдонима, необязательно заканчивающееся двоеточием, за которым следует список замещающих адресов для псевдонима, который может быть продолжен в последующих строках, начиная их с пробела. Запятая используется для разделения адресов в списке. Например:

root:         postmaster@simple.example,
              herb@simple.example
postmaster:   simon@simple.example

Обратите внимание, что первая строка указывает, что root — это псевдоним для postmaster, который сам является псевдонимом. Это обычная практика, и она работает именно так, как вы могли бы ожидать, хотя необходимо соблюдать осторожность, чтобы избежать петель маршрутизации (3.10.3). Exim читает файл и находит запись для postmaster с соответствующими данными. Результатом раскрытия параметра данных является строка:

simon@simple.example

Роутер redirect добавляет этот новый адрес в список адресов для маршрутизации и возвращает код, указывающий на успех. Это означает, что postmaster@simple.example полностью обработан. Список ожидающих адресов теперь содержит следующее:

simon@simple.example
friend@another.example

Предположим, Exim возьмется за simon@simple.example следующим. Это другой локальный адрес, поэтому снова роутер notlocal пропускается, а адрес предлагается роутеру system_aliases. Однако на этот раз для simon в /etc/aliases нет совпадений, поэтому результат раскрытия опции data пуст. Роутер возвращает код, указывающий «отклонить», что заставляет Exim предложить адрес следующему роутеру, конфигурация которого следующая:

userforward:
  driver = redirect
  check _local_user
  file = Shome/.forward

Это еще один экземпляр роутера redirect. На этот раз есть одно предварительное условие: опция check_local_user. Она проверяет, что локальная часть адреса соответствует имени пользователя на локальном хосте. Если нет подходящего пользователя, роутер пропускается. Однако мы предполагаем, что simon является действительным пользователем, поэтому роутер запущен. Параметр data не установлен для этого экземпляра перенаправления; вместо этого есть настройка параметра file. Значение параметра расширено (с заменой $home на домашний каталог simon), но вместо списка адресов перенаправления (как data была для предыдущего роутера) расширенная строка интерпретируется как имя файла. Роутер теперь проверяет, существует ли файл.

Если у simon есть файл .forward в его домашнем каталоге, его содержимое представляет собой список адресов пересылки и других типов элементов (7.6.7). Адреса добавляются в список адресов для маршрутизации, и роутер userforward возвращает код, указывающий на успех. Новые адреса в конечном итоге обрабатываются независимо, так же, как обрабатывался новый адрес от роутера system_aliases.

Если у simon нет файла .forward, роутер отклоняется, и следующему роутеру в конфигурации предлагается simon@simple.example:

localuser:
  driver = accept
  check_local_user
  transport = local_delivery

У него то же предварительное условие (check_local_user), что и у предыдущего роутера. Exim хранит в кеше последнее просматриваемое имя пользователя, чтобы избежать ненужного повторения, поэтому он уже знает, что это предварительное условие выполнено. Если бы simon не был локальным пользователем, роутер отклонил бы вызов, а поскольку в этой конфигурации больше нет роутеров, адрес будет недействительным. Он будет помещен в список неудачных адресов и использован для создания рикошета в конце попытки доставки.

Роутер accept не накладывает никаких собственных условий. Если его предварительные условия выполнены, он принимает адрес и помещает его в очередь транспорта, указанного параметром транспорта (в данном случае, local_delivery). uid, gid и домашний каталог, которые были найдены для simon, присоединяются к адресу, так что они могут использоваться транспортом.

Это все, что происходит на данном этапе; фактическая доставка не происходит до более позднего времени. Обработка postmaster@simple.example показана на рис. 3-4, где эллипсы представляют источники информации, а прямоугольники — роутеры. Роутер notlocal, пропущенный для этого адреса, не показан.

Рисунок 3-4: Маршрутизация postmaster@simple.example

Осталось обработать еще один адрес: friend@another.example. Exim предлагает его первому роутеру:

notlocal:
  driver = dnslookup
  domains = ! simple.example
  transport = remote smtp
  no_more

На этот раз предварительное условие выполнено, поскольку домен не является simple.example, поэтому роутер запущен. Задача dnslookup — получить список удаленных хостов путем поиска домена в DNS с использованием записей MX и адресов (2.8). Если ему не удается найти домен в DNS, роутер отклоняется. В этой конфигурации параметр more имеет значение false (на основании строки no_more), что означает, что дальнейшие роутеры не запускаются. Другими словами, если домен не найден в DNS, адрес отбрасывается.

Когда dnslookup завершается успешно, он получает упорядоченный список хостов и их IP-адресов. Он помещает почтовый адрес в очередь для транспорта remote_smtp, прикрепляя список хостов. В нашем примере, если записи MX и адреса были следующими:

another.example.         MX   6   mail-2.another.example.
another.example.         MX   4   mail-1.another.example.
mail-l.another.example.  A        192.168.34.67
mail-2.another.example.  A        192.168.88.32

тогда список хостов, которые нужно передать с адресом в remote_smtp, будет таким:

mail-l.another.example 192.168.34.67
mail-2.another.example 192.168.88.32

Любые хосты с одинаковым значением предпочтения MX сортируются в случайном порядке. Обработка friend@another.example показана на рис. 3-5.

Рисунок 3-5: Маршрутизация friend@another.example

Теперь необработанных адресов больше нет, поэтому фаза маршрутизации процесса доставки завершена. Exim переходит к фактической доставке, запуская настроенные транспорты. Местные перевозки запускаются в первую очередь; в нашем примере настроена одна локальная доставка на адрес simon@simple.example с использованием транспорта local_delivery. Это было указано роутером localuser, который обрабатывал адрес. Транспорт настроен так:

local delivery:
  driver = appendfile
  file = /var/mail/$local_part
  delivery date_add
  envelope _to_add
  return_path_add

Это определяет транспорт appendfile (9.4), который добавляет копию сообщения в конец файла почтового ящика в обычном формате Unix при такой настройке. Имя файла задается параметром file, который представляет собой расширенную строку. Exim заменяет подстроку $local_part на локальную часть доставляемого адреса, так что фактически используется файл /var/mail/simon. Остальные три параметра требуют добавления трех обычно полезных строк заголовка по мере написания сообщения:

Delivery-date:

Строка заголовка, в которой записаны дата и время доставки, например:

Delivery-date: Fri, 31 Dec 1999 23:59:59 +0000
Envelope-to:

Строка заголовка, в которой записан первоначальный адрес получателя («адрес в конверте»), вызвавший доставку; в этом примере это будет:

Envelope-to: postmaster@simple.example

Сохранение этого адреса полезно на тот случай, если он не отображается в строках заголовка To: или Cc:.

Return-path:

Строка заголовка, которая записывает отправителя из конверта сообщения. Например:

Return-path: <user@simple.example>

Для рикошета, у которых нет отправителя, это выглядит следующим образом:

Return-path: <>

Локальные доставки запускаются последовательно в отдельных процессах, которые в каждом случае меняют свой идентификатор пользователя на соответствующее значение (19.1). В этом случае ID пользователя (uid) и ID группы (gid) локального пользователя будут переданы транспорту роутером localuser, поэтому они используются. Таким образом, подпроцесс доставки запускается «как пользователь», когда он обращается к почтовому ящику.

Когда подпроцесс завершен, локальных доставок больше нет, поэтому Exim переходит к удаленным. Они также запускаются в отдельных процессах, в данном случае с использованием собственных uid и gid Exim'a. Exim может быть сконфигурирован для запуска нескольких удаленных доставок параллельно, когда необходимо выполнить более одной удаленной доставки, но в нашем примере есть только одна удаленная доставка. Это было настроено для friend@other.example роутером notlocal, который перенаправил его на транспорт remote_smtp:

remote_smtp:
  driver = smtp

Здесь нет настроек опций, кроме той, которая выбирает тип транспорта, потому что список хостов был получен роутером notlocal и передан транспорту вместе с адресом. Параметры исходящего SMTP-соединения (например, таймауты) можно изменить другими опциями, но в этом случае мы принимаем все значения по умолчанию. Транспорт smtp пытается установить SMTP-подключение к каждому хосту по очереди. Если все идет хорошо, устанавливается соединение с одним из них, и сообщение передается.

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

3.10 Сложности при маршрутизации

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

3.10.1 Дублирующиеся адреса

Дубликаты адресов — это сложность, с которой Exim может столкнуться, либо потому, что отправитель сообщения указал один и тот же адрес более одного раза, либо из-за того, что псевдоним или переадресация дублировали существующий адрес получателя. Для любого заданного адреса происходит только одна доставка, за исключением случаев, когда дубликаты являются командами конвейера. Если один пользователь пересылает другому, и сообщение отправляется им обоим, доставляется только одна копия. Если, с другой стороны, два разных пользователя настроили свои файлы .forward для передачи в /usr/bin/vacation (например), сообщение, отправленное им обоим, запускает программу vacation дважды, по одному разу для каждого пользователя.

3.10.2 Отсутствующие данные

Иногда роутер не может определить, может ли он обрабатывать адрес. Например, если администратор неправильно написал имя файла псевдонимов или он был случайно удален, роутер redirect не сможет работать. Тайм-ауты могут возникать, когда роутер запрашивает DNS, и роутеры могут ссылаться на базы данных, которые иногда могут быть отключены. В этих ситуациях роутер возвращает код, указывающий «отложить» (defer) в основную часть Exim, и адрес не доставляется и не терпит неудачу, а остается в спуле для другой попытки доставки в более позднее время. Контроль времени повтора описан в главе 12. Если состояние ошибки считается достаточно серьезным, сообщение «замораживается» (frozen), что означает, что процессы обработчика очереди не будут пытаться его доставить. Поскольку замороженные сообщения выделяются в списках очередей, это также служит для привлечения к ним внимания администратора.

3.10.3 Циклы маршрутизации

Когда роутер redirect обрабатывает адрес, каждый новый адрес, который он создает, обрабатывается заново, точно так же, как исходные адреса получателя[4]. Это означает, что один псевдоним может ссылаться на другой, как в примере, который мы показали ранее:

root:        postmaster@simple.example
postmaster:  simon@simple.example

Однако это открывает возможность возникновения петель маршрутизации. Чтобы предотвратить это, Exim автоматически пропускает роутер, если адрес, который он обрабатывает, имеет «родительский» (parent) адрес, который был обработан этим роутером. Рассмотрим следующий сломанный файл псевдонима:

chicken:   egg@simple.example
egg:       chicken@simple.example

Этот роутер преобразует адрес chicken@simple.example в egg@simple.example, а затем снова превращает его в chicken@simple.example в следующий раз. Однако на третьем проходе Exim замечает, что адрес ранее был обработан роутером, поэтому он пропускается и вызывается следующий роутер. Скорее всего, получающаяся доставка или рикошет не те, что были задуманы, но, по крайней мере, петля разорвана.

  1. Это нормальная практика; бывают случаи, когда это нежелательно, и есть параметр redirect_router, который можно использовать для его отключения.

3.10.4 Маршрутизация удаленного адреса на локальный хост

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

3.11 Осложнения во время доставки

Успешный процесс маршрутизации для удаленного адреса обнаруживает список хостов, на которые он может быть отправлен, но не может проверить локальную часть адреса. Самая распространенная постоянная ошибка при удаленной доставке — «неизвестный пользователь» (unknown user) в ответ на команду SMTP RCPT. Ответственность за сообщение остается за хостом-отправителем, который должен вернуть отправителю рикошет.

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

Напротив, роутеры для локальных адресов обычно проверяют локальные части, поэтому любые ошибки «неизвестный пользователь» происходят во время маршрутизации. Единственные проблемы, с которыми, вероятно, столкнется локальный транспорт, — это ошибки при фактическом копировании сообщения. Наиболее распространенным является полный почтовый ящик; Exim соблюдает системные квоты, а также может быть настроен на введение собственных квот (9.4.6). Сбой квоты сохраняет сообщение в буфере обмена для последующей доставки.

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

3.12 Осложнения после доставки

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

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

Exim отправляет такие сообщения, вызывая себя в подпроцессе. Неспособность создать рикошет приводит к тому, что Exim записывает в свой журнал паники (panic log) и немедленно завершает работу. Это приводит к тому, что сообщение помещается в буфер, так что будет предпринята еще одна попытка доставки и, предположительно, еще одна попытка отправки рикошета, если доставка снова не удалась. С другой стороны, неспособность создать предупреждающее сообщение не рассматривается как серьезная проблема. Еще одна попытка отправки предпринимается при повторной обработке исходного сообщения.

3.13 Использование транспорта роутерами

В рассмотренном нами простом примере роутеры localuser и notlocal включают опцию transport (имеется в виду транспорты local_delivery и remote_smtp соответственно), тогда как другие роутеры не имеют никаких настроек транспорта. Транспорт требуется для любого роутера, который фактически настраивает доставку сообщения, чтобы определить, как должна выполняться доставка. Когда роутер просто меняет адрес доставки путем создания псевдонимов или переадресации, транспорт не требуется, поскольку на этом этапе доставка не настраивается.

Роутер redirect имеет дополнительные параметры для некоторых транспортных средств специального назначения. Этот роутер может доставить сообщение в определенный файл или в канал, связанный с данной командой. Например, строка в файле псевдонима вида:

majordomo:  |/usr/mail/majordomo ...

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

/usr/mail/majordomo ...

Другие записи в файле псевдонимов могут просто изменять адреса доставки и, следовательно, могут не требовать транспорта. Однако эта линия настраивает доставку, поэтому требуется транспорт. Мы можем добавить следующую строку в конфигурацию роутера system_aliases:

pipe_transport = alias_pipe

Это сообщает Exim'у, какой транспорт запускать, когда канал указан в файле псевдонимов. Сам транспорт очень прост:

alias pipe:
  transport = pipe
  ignore status
  return_output

Транспорт pipe запускает заданную команду в новом процессе и передает ему сообщение, используя канал для стандартного ввода. В этом примере команда предоставляется файлом псевдонима, поэтому транспорту не нужно ее определять[5]. Установка ignore_status указывает Exim'у игнорировать статус, возвращенный командой; без этого любое значение, отличное от нуля, рассматривается как ошибка, что приводит к сбою доставки и возврату отправителю рикошета.

Параметр return_output изменяет то, что происходит, если команда выводит вывод на свой стандартный вывод или стандартные потоки ошибок. По умолчанию такой вывод отбрасывается, но если установлено значение return_output, создание такого вывода рассматривается как ошибка, а сам вывод возвращается отправителю в рикошете.

Есть одна информация, которая нужна транспорту alias_pipe, которую мы еще не предоставили, и это uid и gid, под которыми он должен выполнить команду. Когда канал запускается записью в файле .forward пользователя, по умолчанию предполагается идентификатор пользователя, но когда используется файл псевдонима, как здесь, значения по умолчанию не существует. Опция user (и, необязательно, group) может отображаться либо в роутере, либо в конфигурации транспорта, поэтому транспорт может стать[6]:

alias_pipe:
  transport = pipe
  ignore_status
  return_output
  user = majordom

Помимо доставки в каналы, файлы псевдонимов и файлы переадресации могут также указывать файлы, в которые должны быть доставлены сообщения. Например, если пользователь caesar имеет .forward, содержащий:

caesar@another.domain.example,  /home/caesar/mail-archive

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

file transport = address file

Это говорит Exim, какой транспорт запустить, когда имя файла указано вместо адреса в файле пересылки. Сам транспорт еще проще, чем транспорт alias_pipe:

address file:
  driver = appendfile

Имя файла берется из файла переадресации, а все остальные параметры установлены по умолчанию.

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

  1. Если транспорт pipe запускается непосредственно с роутера, запускаемая команда определяется с помощью его параметра команды.

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

Глава 4
Обзор операций Exim

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

4.1 Как Exim идентифицирует сообщения

Каждому сообщению, которое обрабатывает Exim, при получении присваивается уникальный ID сообщения. ID состоит из 16 символов и состоит из трех частей, разделенных дефисами, например, 11uNWX-0004fP-00. Каждая часть на самом деле представляет собой число, закодированное по основанию 62, с десятичными цифрами, прописными и строчными буквами, используемыми для представления значений в диапазоне от 0 до 61[1]. Первая часть ID представляет собой обычное представление Unix времени начала получения сообщения, то есть количество секунд с начала эпохи (1 января 1970 г.). Вторая часть — это ID процесса (pid), получившего сообщение. Третья часть используется для различения сообщений, полученных одним и тем же процессом в одну и ту же секунду.

Для большинства установок уникальность ID сообщения требуется только в пределах одного хоста. Однако в некоторых конфигурациях кластера полезно убедиться, что идентификаторы сообщений уникальны в пределах кластера. Например, предположим, что два хоста предоставляют идентичные услуги шлюза или концентратора для некоторого домена, и один из процессоров имеет катастрофический сбой. Если его диск можно подключить к другому процессору, а идентификаторы сообщений уникальны в обеих системах, буферные файлы сообщений можно просто переместить в буферный каталог оставшегося в живых.

Уникальность среди нескольких хостов может быть обеспечена путем присвоения каждому хосту номера в диапазоне 0-16 и указания его в каждой конфигурации Exim. Например:

localhost_number = 4

Когда этот параметр установлен, номер хоста включается в третью часть ID сообщений.

  1. Когда Exim запускается в операционной системе, где имена файлов нечувствительны к регистру, должно использоваться основание 36 вместо основания 62, потому что ID сообщений используются для формирования имен файлов спула.

4.2 Наблюдение за работой Exim

Как новый администратор MTA, первые вопросы, которые вы должны задать:

Exim может отображать статус своей очереди несколькими способами (20.7). Самым простым является параметр командной строки -bp. Эта опция совместима с Sendmail, хотя вывод специфичен для Exim[2]:

$ exim -bp
25m 2.9K Ot5C6f-0000c8-00 <caesar@rome.example>
         brutus@rome.example

Это показывает, что есть только одно сообщение, от caesar@rome.example отправляемое на адрес brutus@rome.example, размером 2,9 КБ, которое находится в очереди 25 минут. Exim также выводит ту же информацию, если он вызывается под именем mailq, что является довольно распространенным соглашением[3].

Exim регистрирует каждое действие, которое он предпринимает, в своем основном файле журнала (21.1). Строка журнала записывается всякий раз, когда приходит сообщение, а также всякий раз, когда доставка завершается успешно или неудачно. Имя файла журнала зависит от конфигурации. Два распространенных варианта: /var/spool/exim/log/mainlog и /var/log/exim_mainlog[4]. Если у вас есть доступ к серверу X Window, вы можете запустить утилиту eximon (21.7), которая отображает «хвост» основного журнала в окне.

Exim использует два дополнительных файла журнала, которые находятся в том же каталоге, что и основной журнал. Один называется журналом rejectlog; он записывает сведения о сообщениях, которые были отклонены по соображениям политики. Другой называется paniclog; это используется, когда Exim сталкивается с какой-то катастрофой, с которой он не может справиться. Журнал паники обычно должен быть пуст; рекомендуется настроить какой-нибудь автоматический мониторинг, чтобы вы знали, было ли в него что-то записано, потому что это обычно указывает на инцидент, требующий расследования.

  1. В примерах команд, запускаемых из оболочки, ввод выделен полужирным шрифтом.

  2. Многие операционные системы настроены с помощью команды mailq как символической ссылки на sendmail; если это, в свою очередь, было связано с exim, команда mailq будет «просто работать».

  3. Можно настроить Exim для использования syslog, но это имеет несколько недостатков.

4.3 Файл конфигурации среды выполнения

Конфигурация среды выполнения Exim хранится в одном текстовом файле, который вы можете изменить в своем любимом текстовом редакторе. Если вы сделаете изменение, только что запущенные процессы Exim немедленно подхватят новый файл, но процесс-демон этого не сделает. Вы должны сказать демону перечитать свою конфигурацию, и это делается традиционным способом Unix, отправив ему сигнал SIGHUP. Номер процесса демона хранится в каталоге спула Exim, так что вы можете сделать это, запустив (как root или exim) следующую команду:

kill -HUP `cat /var/spool/exim/exim-daemon.pid`

При получении сигнала SIGHUP демон закрывается, а затем перезапускается, тем самым подхватывая новую конфигурацию.

Строки в конфигурационном файле, начинающиеся с символа #, являются комментариями, которые Exim игнорирует.

На протяжении всей конфигурации времени выполнения любую строку без комментариев можно продолжить, завершив ее обратной косой чертой. Например:

hold_domains = doml.example.com : \
               dom2.example.com : \
               dom3 .example.com

Завершающие пробелы после обратной косой черты и начальные пробелы в начале следующей строки игнорируются. Строки комментариев могут появляться в середине последовательности строк продолжения.

4.3.1 Структура файла конфигурации

Файл конфигурации среды выполнения разделен на следующие семь разделов:

Основной раздел

Общие настройки параметров и общие элементы управления вводом

Раздел ACL

Списки контроля доступа (Access control lists)

Раздел роутеров

Конфигурация для роутеров

Раздел транспортов

Конфигурация для транспортов

Раздел повтора попыток (retry)

Правила для определения того, как часто Exim должен повторять попытки адресов с временным сбоем.

Раздел переписей (rewrite)

Глобальные правила перезаписи адресов

Раздел аутентификатора

Конфигурация аутентификаторов SMTP

Основная конфигурация всегда должна находиться в начале файла. За исключением случаев, когда мы обсуждаем конкретный драйвер, безоговорочные ссылки на параметры всегда относятся к параметрам в основном разделе конфигурации. Каждый из остальных разделов начинается строкой, начинающейся со слова begin, за которым следует название раздела. Например:

begin routers

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

Разделы конфигурации ACL, retry и rewrite содержат строки в формате, уникальном для данного раздела, и мы обсудим их в последующих главах. Остальные разделы содержат настройки параметров в виде <name>=<value>, по одному в строке.

4.3.2 Минимальный файл конфигурации

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

# Main configuration: no policy checks

acl_smtp_rcpt = accept

# Routers: standard DNS routing and local users

begin routers

lookuphost:
  driver = dnslookup
  domains = ! localdomain.example
  transport = remote_smtp

localuser:
  driver = accept
  check_local user
  transport = local delivery

# Transports: SMTP and local mailboxes

begin transports

remote_smtp:
  driver = smtp

local delivery:
  driver = appendfile
  file = /var/mail/$local_ part

Этот пример даже проще в обращении с локальным доменом, чем случай, который мы рассмотрели в предыдущей главе; он не поддерживает псевдонимы или переадресацию. Поскольку в этой конфигурации нет правил повтора, сообщения с временными сбоями доставки будут возвращены их отправителям без повторных попыток. Кроме того, нет никаких проверок политики входящей SMTP-почты, что означает, что это конфигурация «open relay». Это не было бы разумным примером для реального использования.

4.3.3 Синтаксис настройки параметров

Мы уже видели ряд примеров настройки опций. Каждый находится на отдельной строке, и они всегда могут быть в форме <name>=<value>. Для тех, которые являются переключателями включения/выключения (булевы параметры), также разрешены другие формы. Имя само по себе включает опцию, тогда как имя, которому предшествует no_ или not_, отключает ее. Все эти настройки эквивалентны:

split_spool_directory
split_spool_directory = true
split_spool directory = yes

Так же как и эти:

no_split_spool_directory
not_split_spool_directory
split_spool_directory = false
split_spool_directory = no

Вам не нужно использовать кавычки для значений параметров, которые являются текстовыми строками, но если вы это сделаете, любые обратные косые черты в строках интерпретируются особым образом. Для этой цели Exim распознает только символы двойных кавычек. Например, последовательность \n в строке в кавычках преобразуется в символ перевода строки. Эта функция нужна не так часто.

Некоторые параметры указывают временной интервал, например период тайм-аута для SMTP-подключения. Интервал времени определяется как число, за которым следует одна из букв w (неделя), d (день), h (час), m (минута) или s (секунда). Вы можете комбинировать несколько из них, чтобы составить одно значение. Например, следующее:

connect_timeout = 4m30s

задает временной интервал 4 минуты 30 секунд. Exim не проверяет значения, используемые в этих комбинациях.

4.3.4 Включение других файлов в конфигурацию

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

.include <filename>

Например, предположим, что вы запускаете Exim на нескольких разных хостах с почти идентичными конфигурациями. Вы можете иметь один и тот же основной файл конфигурации на всех хостах и использовать файлы .include для включения частей, специфичных для хоста.

4.3.5 Макросы в файле конфигурации

Для более сложных файлов конфигурации может быть полезно использовать простые макросы. Если строка в основной части конфигурации (то есть перед первой стартовой строкой) начинается с прописной буквы, она воспринимается как определение макроса, вида:

<name> = <rest of line>

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

Как только макрос определен, все последующие строки в файле сканируются на наличие имени макроса; если макросов несколько, строка сканируется для каждого по очереди, в том порядке, в котором макросы определены. Замещающий текст не сканируется повторно для текущего макроса, хотя он будет сканироваться для определенных впоследствии макросов. По этой причине имя макроса не может содержать в качестве подстроки имя ранее определенного макроса. Например, вы можете определить следующее:

ABCD XYZ = <something>
ABCD = <something>

но размещение этих определений в обратном порядке вызовет ошибку конфигурации.

В качестве примера использования макроса предположим, что вы используете одну из баз данных SQL для хранения дополнительных псевдонимов. Если псевдоним не найден в /etc/aliases, вы хотите, чтобы Exim попытался найти его в базе данных. Текст SQL-запроса довольно длинный и может загромождать конфигурацию роутера, если он встроен; вы можете сделать все более аккуратно, определив запрос как макрос, например:

ALIAS_QUERY = select replacement from aliases where alias = \
              '${quote_pgsql:$local_part}'

Тогда роутер для обработки псевдонимов может быть следующим:

db_aliases:
  driver = redirect
  data = ${lookup{$local_part}lsearch{/etc/aliases} {$value}\
         {${lookup pgsql {ALIAS QUERY}}}

Расширение параметра данных сначала просматривает файл, а затем запрашивает базу данных. Мы подробно рассмотрим синтаксис поиска позже, но вы можете видеть, что этот параметр было бы труднее понять, если бы макрос был заменен полным оператором SQL.

В более ранних версиях Exim макросы также были полезны для абстрагирования списков доменов или хостов. Однако в Exim 4 лучше использовать средство «именованного списка» (4.4).

Значения макросов можно переопределить опцией командной строки -D (20.6).

4.3.6 Скрытие данных конфигурации

Опция командной строки -bP просит Exim вывести значение одной или нескольких опций конфигурации. Это может использовать любой вызывающий Exim, но некоторые конфигурации могут содержать данные, которые не должны быть общедоступными. Например, конфигурация, которая ссылается на базу данных SQL или сервер LDAP, может содержать пароли для управления таким доступом. Если какой-либо настройке параметра предшествует слово hide, только пользователь с правами администратора может видеть его значение. Например, если конфигурация содержит:

hide mysql_servers = localhost/usertable/admin/secret

непривилегированный пользователь увидит этот ответ:

$ exim -bP mysql servers
mysql servers = <value not displayable>

4.3.7 Расширения строк

Мы уже видели несколько примеров строковых расширений, таких как следующий параметр для транспорта appendfile:

file = /var/mail/$local part

Расширения — это мощная функция файлов конфигурации. Мы объясним еще некоторые их способности на примерах в последующих главах. Если вы хотите узнать все, что они могут сделать, перейдите к главе 17, в которой есть полная история. Между тем, помните, что всякий раз, когда вы видите символ $ в настройке конфигурации, это означает, что строка будет каким-то образом меняться всякий раз, когда она расширяется для использования.

Неправильный синтаксис в расширении строки является серьезной ошибкой и обычно приводит к тому, что Exim отказывается от того, что он пытается сделать; например, попытка доставить сообщение откладывается, если Exim не может расширить соответствующую строку. Однако есть некоторые операции во время расширения, которые преднамеренно провоцируют особый вид ошибки, называемый отказом принудительного расширения (forced expansion failure). В ряде таких случаев эти сбои просто заставляют Exim отказаться от активности, использующей строку, но в остальном продолжить. Например, сбой принудительного расширения при попытке перезаписать адрес просто отменяет перезапись. Всякий раз, когда сбой принудительного расширения имеет такой особый эффект, мы упоминаем об этом.

4.3.8 Поиск файлов и баз данных

Возможность использовать данные из баз данных и файлов в различных форматах — еще одна мощная особенность конфигурации Exim. Ранее мы показали этот роутер для обработки традиционных файлов псевдонимов:

aliasfile:
  driver = redirect
  data = ${lookup{$local_part}lsearch{/etc/aliases}}

Эта инструкция ищет данные в /etc/aliases с помощью линейного поиска, но в равной степени может использовать индексированный формат файла, такой как DBM:

aliasfile:
  driver = redirect
  data = ${lookup{$local_part}dbm{/etc/aliases.db}}

или данные псевдонимов могут храниться в базе данных:

aliasfile:
  driver = redirect
  data = ${lookup mysql{select addresses from aliases \
         where name='${quote_mysql:$local_part}'}}

Каждый отдельный тип поиска реализован в отдельном модуле. Конфигурация времени сборки Exim определяет, какие из них включены в бинарный файл Exim. Что касается основной части Exim, то существует фиксированный внутренний интерфейс (API) для этих поисков, и он не знает деталей фактического механизма поиска. Однако он различает два разных вида поиска:

Single-key

Использует одну строку ключа для извлечения данных из файла. Ключ и файл должны быть указаны. Линейный поиск и поиск DBM относятся к этому типу.

Query-style

Доступ к базе данных с помощью запроса, написанного на языке запросов пакета базы данных. Exim поддерживает NIS+, LDAP и несколько баз данных SQL.

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

4.3.9 Списки доменов, хостов и адресов

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

hold_domains = dom1.example.com : \
               dom2.example.com : \
               dom3 .example.com

В этом примере показан разделенный двоеточием список из трех доменов, которые указаны как «удерживаемые» (held) (то есть доставка на них приостановлена). Подобные средства списка используются для распознавания конкретных хостов и адресов электронной почты для определенных целей. Полное описание списков находится в главе 18, но до нее мы встретим множество примеров.

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

local_interfaces = 127.0.0.1 : ::::1

определяет адрес IPv4 127.0.0.1, за которым следует адрес IPv6 ::1. Поскольку требование двойного двоеточия особенно нежелательно в случае IPv6-адресов, есть способ изменить разделитель[5]. Если список начинается с левой угловой скобки, за которой следует любой знак пунктуации, этот символ становится разделителем списка. Предыдущий пример можно переписать так:

local_interfaces = <; 127.0.0.1 ; ::1

где разделитель заменен на точку с запятой.

  1. Это относится ко всем спискам, кроме log_file_path.

4.4 Именованные списки

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

domainlist local_domains = localhost:my.dom.example

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

notlocal:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  no_more

На первый взгляд может показаться, что именованный список ничем не отличается от макроса. Однако макросы — это просто текстовые замены, поэтому, если вы напишете это:

ALIST = host1 : host2
auth_advertise_hosts = !ALIST

это, вероятно, не будет делать то, что вы хотите, потому что это точно так же, как этот параметр:

auth_advertise_hosts = !host1 : host2

Обратите внимание, что второе имя хоста не инвертируется. Однако, если вы используете список хостов и вместо этого пишете это:

hostlist alist = host1 : host2
auth_advertise_hosts = ! +alist

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

4.5 Домен квалификации по умолчанию

В локально отправленном сообщении, если в конверте или в любой из строк заголовка, содержащих адреса, обнаружен неполный адрес (то есть локальная часть без домена), он уточняется с использованием домена, определяемого qualify_domain (для отправителей) или qualify_recipient (для получателей) во время получения сообщения. Пользовательские агенты обычно используют полные адреса, но есть и исключения.

Значением по умолчанию для обоих этих параметров является имя локального хоста. Если установлен qualify_domain, его значение используется как значение по умолчанию для qualify_recipient. Обычно эти параметры используются для настройки общего домена. Например, у Acme Widget Corporation может быть два хоста, обрабатывающих почту, mail1.awc.example.com и mail2.awc.example.com, но, вероятно, потребуется, чтобы сообщения, созданные на этих хостах, использовали только awc.example.com в качестве адреса домена по умолчанию, а не отдельные имена хостов. Это можно сделать с помощью следующей настройки:

qualify_domain = awc.example.com

Значение qualify_domain также используется, когда Exim создает адрес отправителя конверта для локально отправленного сообщения от непривилегированного пользователя.

4.6 Обработка замороженных рикошетов

Когда сообщение в очереди Exim помечено как замороженное (frozen), процессы обработчика очереди пропускают его и не пытаются его доставить. Одной из причин, по которой сообщение может быть заморожено, является проблема с конфигурацией Exim (3.10.2). Однако на сегодняшний день наиболее распространенной причиной блокировки сообщения является то, что это рикошет, который не может быть доставлен. Такие сообщения часто являются результатом входящей нежелательной почты, адресованной неизвестному локальному пользователю, но содержащей неверный адрес отправителя, что приводит к сбою получающегося рикошета.

Рекомендуется, чтобы вы позволили Exim проверить отправителя и получателей до того, как сообщение будет принято (14.8.16, 14.8.17), как реализовано в конфигурации по умолчанию. Это может значительно сократить количество замороженных сообщений.

Чтобы избежать почтовых циклов, Exim не позволяет неудачному рикошету привести к другому рикошету. Вместо этого Exim замораживает сообщение, чтобы привлечь к нему внимание постмастера. У некоторых администраторов нет человеческих ресурсов для проверки каждого замороженного сообщения, чтобы определить, в чем проблема, и их политика может заключаться в том, чтобы отбрасывать такие сбои. Exim можно настроить для этого, установив ignore_bounce_errors_after. Эта опция позволяет сохранять такие сбои в течение заданного времени, прежде чем они будут отброшены. Если установить следующее:

ignore_bounce_errors_after = 0s

неудавшиеся рикошеты немедленно отбрасываются, тогда как с этим параметром:

ignore_bounce_errors_after = 12h

Exim сохраняет ошибочные рикошеты в течение 12 часов. После первого сбоя сообщение замораживается, как и в случае по умолчанию, но после того, как оно находится в очереди указанное время, оно автоматически размораживается при следующем запуске очереди. Если доставка снова не удалась, сообщение отбрасывается. Это дает постмастеру время для проверки сообщения.

4.7 Снижение активности при высокой нагрузке

В основном разделе конфигурационного файла есть несколько опций, которые позволяют вам ограничивать или сокращать активность Exim'а, когда сразу приходит большое количество почты, или когда нагрузка на систему слишком высока. «Системная нагрузка» в этом смысле — это среднее количество процессов в очереди выполнения операционной системы за последнюю минуту, цифра, которую можно получить, запустив команду uptime, чтобы получить такой результат:

4:15pm up 1 day(s), 22:23, 75 users, load average: 0.09, 0.15, 0.22

Первая из цифр «load average» — это среднее значение за одну минуту. В ненагруженной системе это небольшое число, обычно значительно меньше 10. Когда оно велико, все замедляется; снижение нагрузки, создаваемой приемом и доставкой почты, может смягчить последствия этого.

4.7.1 Задержка или приостановка доставки при высокой загрузке

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

Если загрузка системы выше значения queue_only_load, то автоматическая доставка входящих сообщений не происходит; вместо этого они ждут в очереди Exim, пока их не найдет следующий процесс запуска очереди. Результатом этого является сериализация их доставки, поскольку обработчик очереди доставляет только одно сообщение за раз. Это уменьшает количество одновременно запущенных процессов Exim без существенного влияния на доставку почты, если обработчики очередей запускаются достаточно часто. Например, установка:

queue_only_load = 8

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

deliver_queue_load_max = 14

означает, что доставки подавляются в очереди, когда нагрузка превышает 14.

Доставки, форсированные с помощью параметров командной строки -M или -qf, переопределяют эти проверки загрузки.

4.7.2 Приостановка входящей почты при высокой нагрузке

Нет возможности остановить входящие сообщения от локальных процессов, когда нагрузка высока, но почту с других хостов можно остановить или ограничить доступ к определенным хостам. Если установлен smtp_load_reserve, а загрузка системы превышает его значение, входящие SMTP-соединения по TCP/IP принимаются только от тех хостов, которые соответствуют записи в smtp_reserve_hosts. Если это не установлено, все соединения с удаленных хостов отклоняются с временным кодом ошибки. Например, со следующим:

smtp_load_reserve = 5
smtp_reserve_hosts = 192.168.24.0/24

только хосты в сети 192.168.24.0/24 могут отправлять почту на локальный хост, когда его нагрузка превышает 5. Список хостов в smtp_reserve_hosts также используется параметром smtp_accept_reserve, который описан в следующем разделе.

Если вы используете пользовательские агенты, которые отправляют сообщения, выполняя вызовы TCP/IP на интерфейс loopback, вам, вероятно, следует добавить 127.0.0.1 (или :: 1 в системе IPv6) к smtp_reserve_hosts, чтобы эти отправки продолжались даже при высокой нагрузке.

4.7.3 Контроль количества входящих SMTP-соединений

Рекомендуется установить ограничение на количество одновременных входящих SMTP-соединений, поскольку каждое из них использует ресурсы, необходимые для отдельного процесса. Exim имеет опцию smtp_accept_max для этой цели. Значение по умолчанию — 20, что подходит для систем малого и среднего размера, но если вы работаете с большой системой, увеличьте это значение до 100 или 200.

Вы можете зарезервировать некоторые из этих входящих SMTP-слотов для определенных хостов, установив smtp_accept_reserve. Его значение — количество слотов, зарезервированных для хостов, перечисленных в smtp_reserve_hosts. Эта функция обычно используется для резервирования слотов для хостов в локальной сети, чтобы внешние соединения никогда не занимали все слоты. Например, если вы установите:

smtp_accept_max = 200
smtp_accept_reserve = 40
smtp_reserve_hosts = 192.168.24.0/24

затем, когда активны 160 подключений, новые подключения принимаются только от хостов в сети 192.168.24.0/24.

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

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

4.7.4 Проверка свободного места на диске

Вы можете сделать так, чтобы Exim временно отклонял входящие сообщения, если количество свободного места в разделе диска, содержащем его каталог спула, падает ниже заданного порога. Например:

check_spool_space = 50M

указывает, что никакая почта не может быть получена, пока не будет по крайней мере 50 МБ свободного места для ее хранения[6]. Проверка не является полной гарантией из-за возможности одновременного поступления нескольких сообщений.

  1. За цифрами в числовом параметре всегда может следовать K или M, что приводит к умножению на 1024 и 1024x1024 соответственно.

4.8 Ограничение размера сообщений

Рекомендуется установить ограничение на размер сообщения, которое будет обрабатывать ваш хост. Вы можете установить, например:

message_size_limit = 20M

и применить ограничение в 20 МБ на сообщение. Ограничение по умолчанию составляет 50 МБ. Также можно установить ограничение на отдельные транспорты (8,4).

4.9 Параллельная удаленная доставка

Если сообщение имеет несколько получателей, которые направляются к разным удаленным хостам, Exim выполняет несколько таких доставок одновременно (каждая в своем собственном процессе). Уровень параллелизма контролируется параметром remote_max_parallel, значение которого по умолчанию равно двум. В системах, обрабатывающих в основном личную почту, где сообщения обычно имеют не более двух или трех получателей, это не является важной проблемой. Однако в системах, обрабатывающих списки рассылки, где одно сообщение может быть доставлено по сотням или даже тысячам адресов, параллельная доставка может значительно улучшить производительность. Например:

remote_max_parallel = 12

позволяет Exim создавать до 12 одновременных процессов удаленной доставки для сообщения нескольким получателям.

4.10 Контроль количества процессов доставки

В обычной конфигурации, где Exim пытается доставить каждое сообщение, как только он его получает, нет контроля над количеством процессов доставки, которые могут выполняться одновременно. На хосте, где обработка почты является лишь одним из многих действий, это обычно не проблема. Однако на сильно загруженном узле, полностью предназначенном для доставки почты, такой контроль может оказаться желательным. Этого можно добиться, подавляя немедленную доставку (что означает, что все доставки происходят в очереди) и ограничивая количество процессов обработчика очереди. Например, вы можете поместить эти настройки в файл конфигурации:

queue_only
queue_run_max = 15

Установка queue_only отключает немедленную доставку, а queue_run_max указывает максимальное количество одновременно активных обработчиков очереди. Тогда максимальное количество одновременных процессов доставки равно значению queue_run_max, умноженному на значение remote_max_parallel.

При таком типе конфигурации вы должны организовать частый запуск процессов обработчика очередей (до максимального числа), чтобы свести к минимуму любую задержку доставки. Это можно сделать, запустив демон с такой опцией, как -q1m, которая каждую минуту запускает новый обработчик очереди (11.7).

4.11 Большие очереди сообщений

В главе 3 мы объяснили, что Exim разработан для среды, в которой большинство сообщений могут быть доставлены почти мгновенно. Следовательно, ожидается, что очередь сообщений, ожидающих доставки, будет короткой. Тем не менее, в некоторых ситуациях возникают большие очереди сообщений, что приводит к большому количеству (тысячам) файлов в одном каталоге (обычно называемом /var/spool/exim/input). Это может существенно повлиять на производительность. Чтобы уменьшить эту деградацию, вы можете установить:

split_spool_directory

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

4.12 Крупные установки

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

4.12.1 Файлы линейных паролей

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

Даже если у вас нет учетных записей на почтовом сервере, вам все равно нужен какой-то список локальных пользователей, и важно сделать поиск по этому списку максимально эффективным.

Поиск файла паролей провоцирует не только доставка почты. Если вы используете демон POP, проверка пароля происходит каждый раз, когда подключается клиент POP; в средах, где пользователи остаются подключенными и оставляют свои POP MUA включенными, эти проверки выполняются каждые несколько минут для каждого пользователя, всякий раз, когда клиент POP проверяет поступление новой почты[7]. IMAP в этом отношении намного дешевле, чем POP, потому что он устанавливает сеанс, который остается активным, поэтому проверка пароля происходит только в начале.

  1. Известно, что пользователи настраивают свои MUA для проверки каждые 20 или 30 секунд; такое использование съест ресурсы вашей машины, и его следует категорически не поощрять.

4.12.2 Каталоги почтовых ящиков

Производительность будет низкой, если у вас слишком много почтовых ящиков в одном каталоге. То, что составляет слишком много, зависит от вашей операционной системы. Было сказано, что файловая система GNU/Linux по умолчанию начинает деградировать примерно при одной тысяче файлов в одном каталоге, тогда как для Solaris это число составляет около десяти тысяч. Однако мне не удалось найти ссылок, подтверждающих эти утверждения. Деградация применяется независимо от того, используете ли вы отдельные файлы в качестве почтовых ящиков с несколькими сообщениями или доставляете сообщения в виде отдельных файлов в каталоге.

Решением этой проблемы является использование нескольких уровней каталогов. Например, вместо хранения почтового ящика jimbo в /var/mail/jimbo вы можете использовать /var/mail/j/jimbo. Разбиение на начальные символы локальной части легко реализовать, но это не так хорошо, как использование какой-либо функции хеширования. Средства расширения строки Exim могут быть использованы для реализации разделения на основе подстроки или хэша. Конечно, вам придется следить за тем, чтобы все программы, читающие почтовые ящики, использовали один и тот же алгоритм.

Для очень большого количества почтовых ящиков рекомендуется двухуровневое разделение с использованием числовой хеш-функции Exim, как в этом примере:

file = /var/mail/${nhash_8 512:$local_part}/$local_part

Расширение хеширования генерирует два числа, разделенных косой чертой, в этом случае используя локальную часть в качестве данных и гарантируя, что числа находятся в диапазонах 0-7 и 0-511. В этом примере почтовый ящик jimbo помещается в /var/mail/6/71/jimbo. Первоначальное разделение может быть между разными дисками или файловыми серверами, а второе может быть между каталогами на одном диске.

4.12.3 Одновременная доставка сообщений

Если два сообщения для одного и того же почтового ящика приходят одновременно, они не могут быть доставлены одновременно, если почтовый ящик представляет собой всего лишь один файл. Один процесс доставки должен ждать другого, что связывает ресурсы. Способ по умолчанию, который Exim делает это (в транспорте appendfile), заключается в том, что он немного приостанавливается, а затем начинает процесс блокировки почтового ящика с нуля. Это самый безопасный подход и единственный способ работы, когда используются файлы блокировки.

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

failed to lock mailbox

и Exim повторит попытку доставки позже. Если вы видите много таких сообщений в основном файле журнала, это указывает на наличие проблемы с конкуренцией за почтовый ящик.

Если вы находитесь в среде, в которой используются только блокировки fcntl() и нет отдельных файлов блокировки, вы можете настроить транспорт appendfile для использования блокирующих вызовов вместо ожидания и повторных попыток. Это дает лучшую производительность, поскольку ожидающий процесс освобождается, как только блокировка становится доступной, а не ждет время ожидания. В этой среде это единственное изменение может существенно повлиять на производительность.

Всю проблему блокировки можно обойти, если использовать почтовые ящики, в которых каждое сообщение хранится в отдельном файле[8]. Один из примеров такого типа хранения сообщений, называемый форматом maildir (9.4.5), в настоящее время довольно популярен и поддерживается рядом MUA и другими программами, обрабатывающими почтовые ящики. Поскольку каждое сообщение является полностью независимым, блокировка не требуется, несколько сообщений могут доставляться одновременно, а старые сообщения могут даже удаляться при поступлении новых.

  1. Конечно, между процессами, обновляющими каталог почтового ящика, все еще существует некоторая блокировка, но она обрабатывается внутри файловой системы и больше не является обязанностью Exim.

4.12.4 Минимизация задержек сервера имен

Занятый почтовый сервер делает большое количество обращений к DNS. По этой причине вы должны обеспечить запуск собственного сервера имен или убедиться, что сервер имен работает на соседнем хосте с высокоскоростным соединением, обычно в локальной сети почтового сервера. Убедитесь, что сервер имен имеет достаточно памяти, чтобы он мог создать большой кэш данных DNS.

4.12.5 Хранение сообщений для коммутируемых хостов

Вы не должны планировать хранение большого количества сообщений для периодически подключающихся клиентов в спуле Exim. Гораздо лучше доставлять их в локальные файлы для последующей передачи каким-либо другим способом (12.12).

4.12.6 Конфигурация оборудования

Если вы продолжаете увеличивать нагрузку на установку Exim, емкость дискового ввода-вывода — это то, что истощается в первую очередь. Каждое обрабатываемое сообщение требует создания и удаления как минимум четырех файлов, хотя это число можно сократить до трех, установив для параметра message_logs значение false, чтобы запретить использование отдельных журналов сообщений (21.1). Поэтому в больших установках следует использовать диски с максимально возможной производительностью. Кроме того, нет смысла продолжать повышать производительность процессора, если диски не поспевают за ним.

Лучшая общая производительность может быть достигнута путем разделения работы между несколькими хостами, каждый из которых имеет собственный набор дисков. Например, для входящей и исходящей почты можно использовать отдельные хосты. Общая форма масштабируемой конфигурации, используемая некоторыми очень большими установками, показана на рис. 4-1.

Рисунок 4-1: Конфигурация большой системы

Эта конфигурация имеет отдельные серверы для входящих и исходящих сообщений и может быть расширена путем добавления дополнительных серверов (обозначенных пунктирными линиями) по мере необходимости. Входящая почта доставляется на один или несколько файловых серверов, которые содержат локальные почтовые ящики в разделенной структуре каталогов, как описано ранее, а также сообщения, ожидающие коммутируемых узлов. Доступ к почтовым ящикам осуществляется с серверов POP и IMAP, а хосты коммутируемого доступа используют еще один сервер для доступа к хранимой почте.

Исходящие серверы отправляют сообщения, которые они не могут доставить за короткое время, на долгосрочный исходящий сервер, чтобы не влиять на их производительность из-за очень длинных очередей сообщений. Это можно реализовать с помощью fallback_hosts в соответствующих драйверах на основных серверах или с помощью переменной $message_age для перемещения сообщений через некоторое фиксированное время.

Глава 5
Расширение конфигурации доставки

В главе 3 мы описали основы того, как Exim доставляет сообщения, и рассмотрели простой и понятный пример. Главы, которые следуют за этой, охватывают все различные драйверы и их опции, но прежде чем мы углубимся в такие детали, мы рассмотрим некоторые дополнительные примеры довольно распространенных требований к доставке и обсудим способы настройки Exim для их поддержки. Во многих случаях предлагаемое решение не обязательно является единственно возможным подходом; часто существует несколько способов достижения одного и того же результата. Основная цель этой главы — показать вам еще несколько способов использования опций драйвера.

5.1 Несколько локальных доменов

В более раннем вводном примере конфигурации мы предположили, что Exim обрабатывает только один локальный домен, и мы поместили его имя (simple.example) в роутер notlocal:

notlocal:
  driver = dnslookup
  domains = ! simple.example
  transport = remote_smtp
  no_more

Позже мы представили списки именованных доменов и показали этот пример с двумя доменами:

domainlist local_domains = localhost:my.dom.example

Изменив роутер, чтобы он читался следующим образом:

notlocal:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  no_more

мы сделали так, чтобы оба домена считались локальными. Это наиболее удобный подход для использования с несколькими локальными доменами. До сих пор мы использовали только полные доменные имена, но есть и другие возможности (18.5). Например:

domainlist local_domains = simple.example : *.simple.example

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

Если локальных доменов много, включать список в конфигурационный файл затруднительно, и вместо этого лучше обратиться к файлу. Такую настройку, как:

domainlist local_domains = /etc/local.domains

можно использовать с файлом, содержащим такие строки, как:

simple.example
*.simple.example
unsimple.example
...

Файл может содержать элемент любого типа, который может появиться в списке доменов, за исключением другого имени файла. Он считывается каждый раз, когда это необходимо, и поэтому может быть обновлен независимо от файла конфигурации Exim. Однако он по-прежнему сканируется линейно, как и список в памяти, и это может быть медленным, если количество элементов в списке велико. Если список содержит только фиксированные имена (т. е. не содержит элементов с подстановочными знаками), его можно преобразовать в индексированный файл, поиск по которому будет выполняться быстрее. Вспомогательная программа под названием exim_dbmbuild поставляется для выполнения преобразования. Вы можете обратиться к проиндексированному файлу с помощью такой настройки, как:

domainlist local_domains = dbm;/etc/local.domains.db

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

Существует несколько различных программных библиотек, поддерживающих индексированные файлы; DBM — это общий термин, обозначающий такой метод доступа к файлам[1]. Большинство современных операционных систем имеют стандартную установленную подходящую библиотеку. Как пользователю Exim, все, что вам действительно нужно знать, это то, что индексированный файл дает более быстрый доступ к определенным данным. точно так же, как указатель в книге позволяет вам найти что-то быстрее, чем чтение. Детали реализации индекса внутри библиотеки DBM не важны.

Хотя библиотеки DBM поддерживают добавление и удаление отдельных записей в файле DBM, обычный подход для приложений, которые просто используют файл как быстрый способ доступа к фиксированным данным, заключается в восстановлении файла с нуля всякий раз, когда данные изменяются. Вот как работает exim_dbmbuild.

Когда Exim тестирует список доменов, который содержит элемент поиска DBM, если запись с совпадающим ключом найдена в файле, домен соответствует списку. Данные, которые были найдены, сами по себе в этом случае не используются; Exim интересует только то, существует ли ключ в файле.

Вам не нужно помещать каждый локальный домен в один поиск, потому что поиск — это всего лишь один элемент в списке, который ищется. Например, у вас может быть несколько встроенных доменов, а некоторые — в одном или нескольких поисковых запросах:

domainlist local_domains = maindomain.example : \
                           dbm;/etc/otherdomains.db

Exim обрабатывает списки слева направо, поэтому имеет смысл размещать наиболее ожидаемые домены первыми.

DBM — только один из нескольких типов поиска, поддерживаемых Exim; они описаны в главе 16. Когда поиск разрешен, может использоваться любой из доступных типов. Например, список локальных доменов может храниться в базе данных MySQL и проверяться с помощью такой настройки, как:

domainlist local_domains = mysql;\
  select * from domainlist where domain='$domain'

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

  1. DBM, вероятно, когда-то означало «database management», но никто больше никогда не произносит его полностью.

5.1.1 Различие между несколькими доменами

Если вы определяете набор локальных доменов, используя список local_domains, и не делаете никаких других изменений в конфигурации по умолчанию, Exim рассматривает их все как синонимы, с одной и той же локальной частью в любом из них, обрабатываемой одинаковым образом. Чтобы различать разные домены, роутеры, следующие за роутером notlocal, должны действовать по-разному для разных доменов. Обычно это делается путем установки опции domains на одном или нескольких роутерах. Эта опция предоставляет список доменов, для которых должен работать роутер. Вот простой пример с двумя локальными доменами, каждый со своим собственным файлом псевдонимов:

domainlist local domains = a.local.domain : b.local.domain

begin routers

notlocal:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  no_more

a_aliases:
  driver = redirect
  domains = a.local.domain
  data = ${lookup{$local_part}lsearch{/etc/a.aliases}}

b_aliases:
  driver = redirect
  domains = b.local.domain
  data = ${lookup{$local_part}lsearch{/etc/b.aliases}}

...

Адреса вида user@a.local.domain обрабатываются первым роутером redirect, но не вторым, тогда как user@b.local.domain обрабатывается вторым, а не первым. Если есть несколько доменов, имена файлов псевдонимов которых следуют регулярному шаблону, нет необходимости иметь отдельный роутер для каждого из них, поскольку имя файла может варьироваться в зависимости от домена, и может использоваться один роутер:

aliases:
  driver = redirect
  data = ${lookup{$local_part}lsearch{/etc/$domain. aliases}}

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

5.2 Виртуальные домены

Термин виртуальный домен (virtual domain) используется для обозначения домена, в котором все действительные локальные части являются псевдонимами для других адресов. Нет никаких реальных почтовых ящиков, связанных с виртуальным доменом. Поскольку каждый адрес, сгенерированный операцией присвоения псевдонимов, обрабатывается независимо, результатом обработки адреса в виртуальном домене может быть адрес локального почтового ящика или удаленный адрес, который вызывает отправку сообщения на другой хост.

Только что описанную схему псевдонимов можно использовать для обработки виртуальных доменов с отдельным файлом псевдонимов для каждого домена. Это позволяет легко иметь отдельного сопровождающего для каждого файла. Однако важно учитывать, что происходит, когда локальная часть не соответствует ни одному элементу в файле псевдонимов. Exim обычно предлагает адрес следующему роутеру. Однако для виртуального домена не следует запускать дополнительные роутеры, и вместо этого адрес должен дать сбой.

Один из способов сделать это — настроить domains для всех последующих роутеров, исключающие виртуальные домены, но обычно проще установить для more значение false, как это делается для удаленных доменов. Вот выдержка из файла конфигурации, который обрабатывает смесь реальных и виртуальных доменов:

domainlist local_domains = realdom.example : cdb;/etc/virtuals

begin routers

notlocal:
  driver = dnslookup
  domains = ! +local_ domains
  transport = remote smtp
  no_more

virtuals:
  driver = redirect
  domains = cdb;/etc/virtuals
  data = ${lookup{$local_part}lsearch{/etc/$domain.aliases}}
  no_more

system_aliases:
  driver = redirect
  data = ${lookup{$local_part }lsearch{/etc/aliases}}

...

Список виртуальных доменов в этом примере хранится в проиндексированном файле в формате cdb. Это формат, оптимизированный для файлов, которые никогда не обновляются после того, как они были созданы, и он работает лучше, чем обычный DBM, который допускает как чтение, так и запись (16.1).

Виртуальные домены обрабатываются вторым роутером; реальный домен обрабатывается остальными роутерами (здесь показан только первый). Поиск в файле /etc/virtuals в принципе происходит дважды (один раз, чтобы установить, что домен является локальным, и второй раз, чтобы проверить перед запуском роутера virtuals), но Exim кэширует результаты последнего поиска для каждого файла, поэтому на практике файл читается только один раз.

Для более сложных требований (например, когда некоторые виртуальные домены являются синонимами и, следовательно, используют один и тот же файл псевдонима), можно использовать несколько роутеров или имя файла псевдонима для каждого домена можно найти с помощью такой настройки, как:

data = ${lookup{$local_part}lsearch\
       {${lookup{$domain}cdb{/etc/virtuals}}}

Мы знаем, что внутренний поиск будет успешным, потому что это тот же самый поиск, который использовался доменами для управления работой роутера[2]. Данные, которые используются для создания файла cdb для этого примера, могут содержать такие строки, где первые два домена используют один и тот же файл псевдонимов:

virt10.example:   /etc/virtl.aliases
virt11.example:   /etc/virtl.aliases
virt20.example:   /etc/virt2.aliases

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

jan@virt1.example:  J.Smith@dom1.example
jim@virt1.example:  J.Smith@dom2.example
jan@virt2.example:  J.Jones@dom1.example
jim@virt2.example:  J.Joyce@dom3.example

Обратите внимание, что это отличается от «традиционного» файла псевдонимов тем, что псевдонимы перечислены с прикрепленными доменами, а не просто локальными частями. роутер может выглядеть так:

virtuals:
  driver = redirect
  domains = cdb;/etc/virtuals
  data = ${lookup{$local_part@$domain}cdb{/etc/virtual.aliases}}
  no more

Существует возможность путаницы, если в файлах псевдонимов используются локальные части без доменов. Рассмотрим следующее:

jac@virt2.example: J.Hawkins

Какой домен нужно добавить к J.Hawkins, чтобы он стал полным адресом? Относится ли он к пользователю на локальном хосте или должен сохранять входящий домен virt2.example? Если вы не укажете иначе, Exim предполагает, что неквалифицированные локальные части являются локальными, и использует значение qualify_recipient (основная опция конфигурации) для создания полного адреса. Если вы хотите другое поведение, вы должны установить qualify_preserve_domain на роутере virtuals.

  1. Фактически, из-за кэширования поиска поиск не повторяется; кешированный результат используется повторно.

5.2.1 Значения по умолчанию в виртуальных доменах

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

virtuals:
  driver = redirect
  domains = cdb;/etc/virtuals
  data = ${lookup{$local_part }lsearch*{/etc/$domain. aliases}}
  no more

позволит указать отдельное значение по умолчанию для каждого домена в его файле псевдонимов. Например, в файле /etc/virt3.example.aliases может быть:

*:          postmaster@virt3.example
postmaster: pat@dom5.example
jill:       jkr@dom4.example

Когда Exim ищет в этом файле локальную часть, отличную от postmaster или jill, он терпит неудачу. Из-за звездочки в типе поиска затем выполняется второй поиск ключевой строки *, которая находит запись по умолчанию, которая перенаправляет все остальные локальные части на postmaster.

Если поиск по умолчанию используется, когда домены включены как часть ключей поиска, он обеспечивает единое значение по умолчанию для всех доменов. Вы можете назначить каждому домену собственное значение по умолчанию, добавив *@ (вместо *) к типу поиска. Например:

virtuals:
  driver = aliasfile
  domains = cdb;/etc/virtuals
  data = ${lookup{$local_part@$domain}lsearch*@\
         {/etc/virtual.aliases}}
  no_ more

Затем вы можете включить такие данные, как:

*@virt4.example: virt4-admin@dom6.example

в объединенном файле виртуального домена. Если первоначальный поиск терпит неудачу, локальная часть заменяется звездочкой для второй попытки, и только если это также не удается, в качестве ключа пробуется простая звездочка.

5.2.2 Постмастеры в виртуальных доменах

Если у каждого виртуального домена есть свой постмастер, их можно включить в данные псевдонима, и проблем не будет. Однако иногда требуется, чтобы один адрес получал почту администратора почты для нескольких доменов, и есть более простой подход, чем сохранение одной и той же записи во всех файлах псевдонимов. Если это относится ко всем локальным доменам, можно использовать дополнительный роутер redirect, размещенный перед остальными:

postmaster:
  driver = redirect
  local_parts = postmaster
  data = postmaster@your.domain.example

Эффект настройки local_parts аналогичен настройке domains. Это заставляет роутер работать только для тех локальных частей, которым он соответствует. В этом случае роутер работает только тогда, когда локальная часть является postmaster. Поскольку настройки domains нет, адрес администратора почты для всех локальных доменов перенаправляется на один и тот же фиксированный адрес.

Новый адрес повторно обрабатывается сам по себе; если yourdomain.example является локальным доменом, он снова обрабатывается этим роутером, но в третий раз срабатывает правило защиты от петель, и роутер пропускается. Ненужного второго прохода через роутер можно избежать одним из двух способов:

Если некоторые из виртуальных доменов имеют своих собственных постмастеров, но вы хотите получать почту постмастеров для других, вы можете расширить настройку доменов, чтобы исключить нежелательные домены (или включить нужные, если это проще). Другая возможность, если значения по умолчанию не используются, заключается в размещении роутера redirect для postmaster после тех, которые обрабатывают виртуальные домены.

5.3 Списки рассылки

Exim может использоваться сам по себе для запуска простых списков рассылки, которые поддерживаются вручную, но для больших или сложных требований настоятельно рекомендуется использование дополнительного специализированного программного обеспечения для списков рассылки (5.3.5).

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

Для «взрывания» таких списков рассылки можно использовать роутер redirect, а опцию domains можно использовать, если требуется запускать эти списки в отдельном от обычной почты домене. Например, если ваш домен — simple.example, вы можете использовать lists.simple.example для адресов, ссылающихся на списки рассылки, чтобы полностью отделить их от обычной почты. Этот роутер делает именно это:

lists:
  driver = redirect
  domains = lists.simple.example
  more = false
  file = /usr/lists/$local_part
  errors_to = $local_part-request@$domain
  forbid_pipe
  forbid_file

С другой стороны, если вы используете имена списков, отличные от любых ваших локальных имен пользователей, вы можете использовать их в своем обычном домене. Тогда настройки domains и more не нужны.

Этот экземпляр роутера redirect отличается от тех, которые мы использовали ранее, потому что он имеет настройку file вместо data. Это взаимоисключающие параметры, которые определяют список замещающих адресов двумя разными способами:

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

Параметр errors_to указывает, что любые ошибки доставки, вызванные адресами, взятыми из списка рассылки, должны быть отправлены на указанный адрес, а не на исходного отправителя сообщения. Другими словами, он меняет отправителя конверта сообщения по мере его прохождения. Однако, прежде чем действовать на errors_to, Exim проверяет адрес ошибки. Если проверка не пройдена, отправитель конверта не изменяется. В этом примере проверка завершается успешно, если файл -request, соответствующий списку рассылки, существует.

Параметры forbid_pipe и forbid_file предотвращают преобразование локальной части в имя файла или конвейерную доставку, что обычно не подходит для списка рассылки.

Используя эту схему, вы можете создать список, создав основной файл, содержащий участников, и файл -request, содержащий менеджеров. Например, как только создается файл с именем /usr/lists/exim-users, почта для exim-users @lists.simple.example принимается и отправляется на все адреса в этом файле. Как только будет создан /usr/lists/exim-users-request, проверка адреса exim-users-request@lists.simple.example завершится успешно, что позволяет изменять адрес отправителя сообщений exim-users@lists.simple.example при их пересылке.

Альтернативой обработке как адреса списка, так и адреса менеджера с одним и тем же роутером является настройка более раннего роутера для обработки адреса менеджера (5.3.4).

5.3.1 Синтаксические ошибки в списках рассылки

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

Если для экземпляра роутера redirect задано значение skip_syntax_errors, роутер просто пропускает записи, которые не могут быть проанализированы, отмечая инцидент в журнале. Действительные адреса распознаются и используются. Если дополнительно syntax_errors_to установлена на проверяемый адрес, на него отправляются сообщения о пропущенных адресах. Обычно целесообразно установить то же значение, что и errors_to.

5.3.2 Списки рассылки NFS

Не рекомендуется иметь файлы списка, смонтированные через NFS, потому что отсутствие монтирования нельзя отличить от несуществующего файла. Таким образом, когда сервер NFS не работает, Exim будет вести себя так, как будто списка не существует. Одним из способов решения этой проблемы является непрямой доступ к списку. Если элемент в списке перенаправления :include:, за которым следует имя файла, содержимое файла включается в эту точку списка. Это означает, что вы можете настроить файл на локальном диске, содержащий список списков в следующем виде:

exim-users:     :include:/usr/lists/exim-users
exim-announce:  :include:/usr/lists/exim-announce

а затем использовать его в следующем роутере:

lists:
  driver = redirect
  data = ${lookup{$local_part}lsearch{/etc/listoflists}}
  forbid_pipe
  forbid_file
  errors_to = $local_part-request@$domain

Для существующего списка рассылки поиск выполняется успешно, поэтому существование списка можно определить без обращения к файлу NFS. Однако, если Exim не может открыть включенный файл, доставка откладывается, потому что ожидается, что :include: будет называть доступный файл.

Это немного сложнее поддерживать, потому что в дополнение к созданию файлов псевдонимы должны быть обновлены, чтобы настроить новый список.

5.3.3 Повторное расширение списков рассылки

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

Если такое поведение кажется нежелательным, на соответствующем роутере redirect можно установить параметр one_time. Когда это сделано, любые адреса, сгенерированные роутером, которые не могут быть доставлены с первой попытки, добавляются в сообщение как адреса «верхнего уровня», а адрес, который их сгенерировал, помечается как «доставленный». В результате расширение списка рассылки не происходит повторно при последующих попытках доставки. Недостатком этого является то, что если какой-либо из ошибочных адресов неверен, их изменение в файле не повлияет на ранее существовавшие сообщения.

5.3.4 Закрытые списки рассылки

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

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

Во-первых, необходимо настроить отдельный роутер для обработки адреса -request, на который любой может отправлять почту:

lists_request:
  driver = redirect
  domains = lists.simple.example
  local_part_suffix = -request
  file = /usr/lists/$local_part-request
  no_more

Здесь мы видим новую опцию, local_part_suffix, которую мы раньше не встречали. Это параметр предварительного условия, в результате которого проверяется локальная часть для данного суффикса и пропускается роутер, если он не соответствует. Таким образом, этот роутер запускается только для локальных частей, оканчивающихся на -request.

Роутер работает с $local_part, лишенным суффикса, который помещается в $local_part_suffix (хотя в этом примере эта переменная не используется). Существует аналогичная опция, называемая local_part_prefix, которая работает, проверяя другой конец локальной части. Вы бы использовали это, если бы ваши списки рассылки использовали форму owner-xxx для управления списками вместо xxx-request.

Следующий роутер сам обрабатывает закрытый список:

lists:
  driver = redirect
  domains = lists.simple.example
  senders = ${if exists {/usr/lists/$local_part}\
             {lsearch;/usr/lists/$local_part}{*}}
  file = /usr/lists/Slocal_part
  forbid_pipe
  forbid_file
  one_time
  skip_syntax_errors
  errors_to = $local_part-request@lists.simple.example
  no_more

Параметр senders является предварительным условием, которое проверяет отправителя конверта сообщения. Для существующего списка рассылки мы хотим найти список подписчиков. Однако, если мы просто использовали этот параметр:

senders = lsearch;/usr/lists/$local_part

будет проблема для неизвестных списков, потому что несуществующий файл в элементе списка адресов, таком как этот, заставляет Exim отложить доставку. Вместо этого мы используем элемент условного раскрытия для проверки существования файла. Элемент расширения, начинающийся с $if{, проверяет условие и расширяет одну подстроку, если условие выполнено, и другую, если нет (17.7). В этом случае, если файл не существует, расширение дает *, что соответствует всем отправителям. Таким образом, в этом случае всегда выполняется предварительное условие, что означает, что роутер работает, обнаруживает для себя, что файл не существует, и поэтому отклоняет его. Параметр no_more гарантирует, что никакие другие роутеры не будут запущены; поэтому адрес не работает.

Предположим, что существует список рассылки exim-users со списком подписчиков в /usr/lists/exim-users. Когда для этого списка получено сообщение, проверка на существование файла завершается успешно, поэтому расширение значения параметра senders дает lsearch;/usr/lists/exim-users. Это заставляет Exim искать в файле адрес отправителя. Если он найден, роутер запускается и сообщение доставляется в список. Однако, если файл не содержит адреса отправителя, роутер пропускается, а адрес предлагается следующему роутеру, поскольку отправители — это предварительное условие, на которое no_more не влияет. Следовательно, мы должны добавить третий роутер, чтобы поймать этот случай. Это позволяет нам указать индивидуальное сообщение об ошибке следующим образом:

closed_reject:
  driver = redirect
  domains = lists.simple.example
  allow_fail
  data = :fail: $locai_part@$domain is a closed mailing list

Элемент :fail: в списке перенаправления приводит к возврату адреса получателя. Его использование должно быть разрешено путем настройки allow_fail.

5.3.5 Программное обеспечение для внешнего списка рассылки

Использование специализированного программного обеспечения для работы со списками рассылки, такого как Majordomo, SmartList или Mailman, рекомендуется, если вы используете большие или многочисленные списки рассылки[3]. Сообщения, адресованные списку рассылки, передаются внешней программе, которая в конечном итоге повторно отправляет их в Exim для доставки подписчикам. Это может быть сделано либо предоставлением списка получателей с каждым повторно отправленным сообщением, либо использованием Exim'овских механизмов псевдонимов или переадресации для получения списков адресов из файлов. Такой подход помогает решить следующие проблемы:

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

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

majordomo_aliases:
  driver = redirect
  data = ${lookup{$local_part}lsearch\
          {/usr/local/majordomo/lists/majordomo.aliases}}
  pipe_transport = address_pipe
  user = majordom
  group = majordom

Это гарантирует, что любые пайпы, которые запускаются в результате этого конкретного файла псевдонимов, делают это как пользователь majordom.

Когда обычный пользователь отправляет сообщение в Exim из процесса, работающего на том же хосте, адрес отправителя конверта создается из имени пользователя и имени домена в qualify_domain (которое по умолчанию соответствует имени хоста). Существует опция командной строки -f, которая может переопределить это, но Exim игнорирует ее, если вызывающий не является доверенным (19.3). Идея состоит в том, что доверенные пользователи могут подделывать адреса отправителей и другие данные сообщений. Например, если вы используете Majordomo, у вас должно быть следующее:

trusted_users = majordom

в вашей конфигурации Exim, чтобы опция -f учитывалась для сообщений, приходящих от Majordomo, что позволяло указать соответствующего отправителя конверта для каждого списка рассылки.

Использование псевдонимов в качестве средства маршрутизации сообщений для программного обеспечения управления списками — не единственная возможность. Другой подход заключается в использовании специализированных роутеров и транспортных средств. Например, для передачи сообщений в SmartList можно использовать следующий транспорт:

list_transport :
  driver = pipe
  command = /usr/slist/.bin/flist $local_part$local_part_suffix
  current_directory = /usr/slist
  home_directory = /usr/slist
  user = slist
  group = slist

Транспорт активируется с роутера так:

list router:
  driver = accept
  local_part_suffix = -request
  local_part_suffix_optional
  local_parts = !.bin:!.etc
  require_files = /usr/slist/$local part/re.init
  transport = list_transport

Опция require_files является предварительным условием, которое проверяет наличие одного или нескольких файлов. В этом случае он гарантирует, что роутер работает только тогда, когда локальная часть является именем существующего списка. То есть существование файла, имя которого включает имя списка, используется в качестве триггера для передачи сообщения в SmartList. Обратите внимание на использование опции local_parts, чтобы избежать обработки файлов /usr/slist/.bin и /usr/slist/.etc как списков рассылки.

  1. Для получения информации о Majordomo см. http:/www.greatcircle.com/majordomo; для SmartList см. http://www.procmail.org (sic); для Mailman см. http://www.gnu.org/software/mailman/mailman.html.

5.4 Использование внешнего локального агента доставки

Альтернативой использованию транспорта appendfile для записи в локальные почтовые ящики является использование для этой цели внешней программы. Это может быть для всех местных поставок или только для определенных местных частей. Транспорт pipe можно использовать для передачи сообщений отдельному локальному агенту доставки, такому как procmail[4]. Далее мы используем procmail в качестве примера локального агента доставки, но аналогичный подход можно использовать для любого локального агента доставки.

Отдельные пользователи могут организовать доставку своей почты с помощью procmail, вызвав его из своих файлов .forward, при условии, что конфигурация Exim разрешает использование каналов (pipes) из файлов .forward. В некоторых установках, однако, может быть требование всегда использовать procmail для локальных доставок или разрешить пользователям выбирать его использование, не позволяя им запускать команды канала из своих файлов .forward. Один из способов справиться с такими случаями — настроить отдельный транспорт только для использования procmail.

При этом необходимо следить за тем, чтобы пайп работал под соответствующими uid и gid. В некоторых конфигурациях требуется, чтобы это был uid, которому агент доставки доверяет указание правильного отправителя сообщения. Может потребоваться перекомпилировать или перенастроить агент доставки, чтобы он доверял соответствующему пользователю. Ниже приведен пример транспорта, который доставляет с помощью procmail:

procmail_pipe:
  driver = pipe
  command = /usr/local/bin/procmail -d $local_part
  return_path_add
  delivery_date_add
  envelope_to_add
  check_string = "From "
  escape_string = ">From "
  user = $local_part
  group = mail

Это запустит procmail с uid пользователя, но с группой, настроенной на mail. Настройки check_string и escape_string гарантируют экранирование любых строк, начинающихся с «From ». Это значение по умолчанию для транспорта appendfile, но не для pipe.

Транспорт procmail_pipe может использоваться роутером, который проверяет файл .procmailrc пользователя:

procmail:
  driver = accept
  check_local_user
  require_files = $home/.procmailrc
  transport = procmail_pipe

Если в домашнем каталоге пользователя нет файла .procmaiirc, этот роутер отказывается обрабатывать адрес. Следующий роутер может быть обычным роутером accept, который направляет к транспорту appendfile обычным способом. Таким образом, все, что нужно сделать пользователю, чтобы перейти от обычной доставки Exim к доставке через procmail, это создать .procmailrc. Файл .forward не требуется.

В следующем примере показан транспорт для системы, в которой локальные доставки обрабатываются сервером Cyrus IMAP:

local_delivery_cyrus:
  driver = pipe
  command = /usr/cyrus/bin/deliver \
            -m ${substr_1:$local_part_suffix} \
            -- $local_part
  user = cyrus
  group = mail
  return_output
  log_output
  message_prefix =
  message_suffix =

Если какой-либо текст написан Cyrus, return_output гарантирует, что он будет возвращен отправителю, а log_output заносит в журнал первую его строку. Настройки message_prefix и message_suffix отключают добавление разделительной строки, содержащей обратный путь, который добавляется в начале сообщения по умолчанию, и пустой строки в конце соответственно. Этот транспорт может быть активирован роутером, например:

local_user_cyrus:
  driver = atcept
  check_local_user
  transport = local_delivery_cyrus
  1. См. http://www.procmail.org. Многие, но не все, вещи, которые может сделать procmail, также могут быть сделаны с помощью фильтра Exim. См. главу 10 для обсуждения различий.

5.5 Несколько адресов пользователей

Один пользователь обычно имеет один адрес электронной почты и один почтовый ящик. Например, пользователь caesar на хосте simple.example имеет следующий адрес:

caesar@simple.example

и почта на этот адрес обычно доставляется в /var/mail/caesar. Пользователи с большими объемами входящей почты часто предпочитают использовать какой-либо метод автоматической сортировки ее по категориям, чтобы сделать ее более удобной для обработки. Один из способов сделать это — использовать возможности фильтрации Exim или запустить внешний локальный агент доставки, такой как procmail. Эти методы основаны на анализе строк заголовка или содержимого сообщения.

Другой подход заключается в разрешении использования префиксов или суффиксов в именах пользователей в локальных частях входящей почты. Например, дополнительные адреса, такие как следующие:

caesar-rome@simple.example
casear-gaul@simple.example

распознаются как принадлежащие пользователю caesar. Затем пользователь может использовать пересылку или фильтрацию файлов для проверки суффикса. Могут быть указаны фиксированные суффиксы, но обычно используются подстановочные знаки, чтобы пользователи могли выбирать свои собственные суффиксы. Например, роутер показан в следующем примере:

userforward:
  driver = redirect
  check_local_user
  file = $home/.forward
  local_part_suffix = -*
  local_part_suffix_optional
  filter

запускает пользовательский файл .forward (обычно это файл фильтра Exim) для всех локальных частей, которые начинаются с действительного имени пользователя, за которым может следовать дефис, а затем произвольный текст. В файле фильтра пользователь может различать разные случаи, проверяя переменную $local_part_suffix. Например:

if $local_part_suffix contains -special then
  save /home/$local_part/Mail/special
endif

Если файл фильтра не существует или не имеет отношения к таким адресам, они предлагаются последующим роутерам, и, если в дальнейшем не используется опция local_part_suffix, роутеры с суффиксами, по-видимому, не работают. Таким образом, пользователи могут контролировать, какие суффиксы допустимы.

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

userforward:
  driver = redirect
  check_local_user
  file = $home/.forward$local_part_suffix
  local_part_suffix = -*
  local_part_suffix_optional
  filter

Если суффикса нет, используется .forward; если суффикс -special, например, используется .forward-special. Еще раз, если соответствующий файл не существует или не имеет отношения к адресу, он предлагается последующим роутерам. Будет ли адрес доставлен или возвращен, зависит от того, как они настроены. Пользователь контролирует, какие суффиксы допустимы, создавая соответствующие файлы, которые могут перенаправлять сообщения на другие адреса или перенаправлять их в определенные файлы или команды канала, используя традиционные функции .forward или команды фильтра Exim.

5.6 Смешанные локальные/удаленные домены

Рассмотрим корпоративный почтовый шлюз, который доставляет одни локальные части в локальные почтовые ящики, а другие отправляет на персональные рабочие станции. Для упрощения обсуждения предположим, что задействован только один домен, поэтому нам не нужно указывать параметр domains на каких-либо роутерах, кроме исходного, который обрабатывает удаленные домены. Это будет стандартный роутер notlocal, который мы уже показывали несколько раз, так что повторяться не будем.

Для реализации нашего смешанного домена требуется сопоставление локальных частей с именами рабочих станций. Например:

ceo:     bigcheese.plc.co.example
alice:   castor.plc.co.example
bob:     pollux.ple.co.example

означает, что почта, адресованная локальной части ceo, должна быть отправлена на хост bigcheese.pic.co.example и т.д. Мы покажем два разных способа использования этой таблицы (мы предполагаем, что она находится в файле /etc/wsusers), потому что они вводят различные новые возможности Exim. В обоих случаях для локальных частей, которые должны быть доставлены на другие хосты, используется один роутер. Другие локальные части передаются последующим роутерам, которые могут доставлять их в почтовые ящики на локальном хосте обычным способом.

5.6.1 Использование роутера с ручным маршрутом

Роутер manualroute является основным способом маршрутизации адресов Exim к удаленным хостам с использованием данных локальной маршрутизации. Это позволяет вам настроить правила маршрутизации в форме «отправлять почту для этих адресов на этот хост». В нашем примере этот роутер отбирает локальные части, которые должны быть доставлены на рабочие станции:

workstation_people:
  driver = manualroute
  local_parts = lsearch;/etc/wsusers
  route_list = * $local_part_data
  transport = remote_smtp

Предварительное условие local_parts ограничивает этот роутер теми локальными частями, которые находятся в файле. Правило маршрутизации задается параметром route_list, который состоит из двух частей:

Поскольку роутер определяет хост, на который должно быть отправлено сообщение, можно использовать стандартный транспорт remote_smtp.

5.6.2 Использование принимающего роутера и специального транспорта

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

workstation_people:
  driver = accept
  local_parts = lsearch;/etc/wsusers
  transport = local_smtp

Мы снова используем local_parts для выбора соответствующих локальных частей; на этот раз мы направляем их на специальный транспорт под названием local_smtp. Его конфигурация такова:

local_smtp
  driver = smtp
  hosts = $local_part_data

В более ранних примерах транспорта smtp опция hosts не используется, поскольку на них ссылаются роутеры, предоставляющие список хостов. Однако в этом случае на транспорт ссылается роутер, который может передавать историю узлов, поэтому список должен отображаться на самом транспорте. Переменная $local_part_data содержит данные из поиска в опции роутера local_parts, который является соответствующим именем хоста.

5.7 Доставка в UUCP

Exim не содержит специальных функций UUCP и, в частности, он не поддерживает метод адресации UUCP "bang path"[5]. Однако, если вы придерживаетесь использования адресов интернет-домена, почта может быть легко перенаправлена на UUCP. Прежде всего, вы должны настроить сопоставление доменных имен с именами хостов UUCP. Это может быть файл, содержащий такие данные, как:

darksite.plc.example: darksite
bluesite.plc.example: indigo

Тогда вам нужен роутер, который использует эти данные:

uucphost:
  driver = manualroute
  route_data = ${lookup{$domain}lsearch{/usr/local/uucpdomains}}
  transport = uucp

Это роутер manualroute, который ищет информацию о маршрутизации в файле /usr/local/uucpdomains. Если он находит домен, он направляет адрес на транспорт uucp:

uucp:
  driver = pipe
  command = /usr/local/bin/uux -r - $host!rmail $local_part
  user = nobody
  return_fail_output = true

Поскольку pipe является локальным транспортом, роутер manualroute не может передавать список хостов для доставки (как это было для транспорта smtp в примере из предыдущего раздела). Вместо этого роутер помещает имя хоста в переменную $host. Используя эту конфигурацию, сообщение, адресованное:

postmaster@bluesite.plc.example

в конечном итоге будет передано команде:

/usr/local/bin/uux -r - indigo!rmail postmaster

который запускается от имени пользователя nobody. Параметр return_fail_output гарантирует, что в случае сбоя команды все выходные данные, которые она производит, возвращаются в сообщении о возврате.

  1. «Bang path» UUCP — это адрес вида host1!host2!host3...!user.

5.8 Игнорирование локальной части в локальных поставках

Местные доставки не обязаны использовать локальную часть адреса. Одним из распространенных примеров является небольшая компания, в которой только один человек читает входящую электронную почту. Вместо того, чтобы настраивать фиксированные локальные части, такие как sales, info, enquiries и т. д., они хотят, чтобы вся почта доставлялась в один почтовый ящик, какой бы ни была локальная часть. Предполагая, что выбранный получатель является postmaster, все, что вам нужно, это следующий роутер:

catchall:
  driver = redirect
  data = postmaster

Его нужно поставить последним в списке роутеров, чтобы он подхватывал все неизвестные локальные части и перенаправлял их на postmaster.

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

Предположим, что провайдер выделяет доменное имя diego.isp.example клиенту с именем пользователя diego и почтовым ящиком /var/mail/diego на почтовом хосте провайдера. Конфигурация Exim на почтовом хосте может использовать один роутер accept для получения этих адресов и маршрутизации их на специальный транспорт:

onebox_customers:
  driver = accept
  domains = *.isp.example
  address_data = ${if match{$domain}{^([^.]+)}{$1}}
  transport = onebox

Опция address_data — это что-то новое. Когда она появляется на роутере, ее значение расширяется непосредственно перед запуском роутера, а результат сохраняется в переменной $address_data. Это можно использовать в опциях роутера или в последующем транспорте. Чаще всего она используется для запоминания данных, которые ищутся в файле или базе данных. Однако в этом примере мы используем ее, чтобы не повторять несколько сложную строку расширения в транспорте.

Элемент условного расширения в настройке address_data извлекает первый компонент домена путем сопоставления содержимого $domain (то есть diego.isp.example в нашем примере) с регулярным выражением. Регулярное выражение ^([{^.]+) соответствует строке, начинающейся с последовательности неточечных символов, и сохраняет эту последовательность в $1. Поскольку домен уже проверен параметром domains, мы знаем, что регулярное выражение всегда будет совпадать. В нашем примере значение $1 будет diego. Затем оно подставляется в окончательный набор фигурных скобок, давая diego как результат всего элемента расширения if.

Транспорт использует это значение для доставки в файл почтового ящика при работе от имени правильного пользователя:

onebox:
  driver = appendfile
  file = /var/mail/$address_data
  user = $address_data
  envelope_to_add
  return_path_add

Настройка envelope_to_add и return_path_add приводит к сохранению адресов конверта в строках заголовков, поэтому владелец почтового ящика может различать сообщения в разные локальные части, даже если адреса получателей не отображаются в строках заголовка To:.

На практике метод доставки всех локальных частей в один почтовый ящик уже не так полезен, как раньше, из-за увеличения объема нежелательной почты, рассылаемой по случайно сгенерированным адресам. В наши дни большинство сайтов предпочитают проверять входящие локальные части во время диалога SMTP и отклонять те, которые не распознаются.

5.9 Обработка локальных частей с учетом регистра

В RFC 2822 указано, что регистр букв в локальных частях адресов следует считать значимым. Напротив, регистр букв в доменных именах никогда не имеет значения. Exim сохраняет регистр обеих частей адреса и передает их точно так, как они были получены. Однако в большинстве Unix-подобных систем имена пользователей пишутся строчными буквами, а локальные части адресов электронной почты должны обрабатываться без учета регистра, поэтому все сообщения, адресованные:

icarus@knossos.example
Icarus@knossos.example
ICARUS@knossos.example
iCaRuS@knossos.example

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

Если вы хотите, чтобы конкретный роутер обрабатывал локальные части с учетом регистра, вы можете изменить значение по умолчанию для параметра caseful_local_part:

caseful_local_part = true

Это относится только к роутеру, на котором он установлен. Четыре адреса в предыдущем примере будут рассматриваться таким роутером как имеющие разные локальные части, и если роутер отправляет адрес в транспорт, значение $local_part при запуске транспорта сохраняет исходный регистр. Напротив, $domain всегда пишется только строчными буквами.

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

Поэтому установки caseful_local_part на всех соответствующих роутерах недостаточно; вы также должны организовать преобразование входящих локальных частей в правильный регистр. Один из способов сделать это — настроить первый роутер для локального домена в качестве роутера redirect, который выполняет преобразование путем поиска файлов, например:

adjust_case:
  driver = redirect
  data = ${lookup{$local_part}cdb{/etc/usercased.cdb}{$value}fail}\
         @$domain

У этого роутера не установлен caseful_local_part, поэтому значение $local_part указано в нижнем регистре. Оно используется в качестве ключа для поиска в файле cdb (в этом примере), данные которого могут содержать такие записи, как:

icarus:   Icarus
j.caesar: J.Caesar

Перенаправленный адрес дополняется добавлением домена. Таким образом, все четыре ранее перечисленных адреса будут преобразованы в Icarus@knossos.example. Новый адрес имеет правильный регистр и поэтому может успешно обрабатываться последующими роутерами, у которых установлен caseful_local_part. Если локальная часть не найдена в файле, сбой при поиске вызывает сбой принудительного расширения, и в этом случае роутер отклоняет запрос, и адрес обрабатывается с использованием исходного регистра.

Для максимальной эффективности такой роутер должен содержать настройку параметра redirect_router. Без него новый адрес обрабатывается заново, и если он отличается от исходного адреса, он проходит через роутер adjust_case во второй раз, хотя это просто регенерирует тот же самый новый адрес. В следующий раз роутер пропускается, потому что он уже обработал этот адрес. Настройка redirect_router для указания на следующий роутер, как показано в этом примере:

redirect_router = system_aliases

избегает второго прохода через adjust_case.

5.10 Проверка сообщений на наличие вирусов

Существует ряд программ, которые сканируют сообщение электронной почты, чтобы определить, содержит ли оно какие-либо вирусы в виде вложений. Некоторые из них работают в системах Unix, но другие доступны только для других операционных систем. Общая техника использования такой программы от Exim одинакова в обоих случаях: входящие сообщения доставляются на сканер, который имеет безопасное средство передачи «чистых» сообщений обратно в Exim для окончательной доставки. Этот процесс приводит к добавлению в сообщение дополнительного заголовка Received:, но обычно это не проблема.

Сообщения могут быть перенаправлены в программу сканирования непосредственно с роутера, или в некоторых случаях может использоваться системный фильтр. Мы еще не обсуждали средства фильтрации Exim (подробности в главе 10), но, вкратце, системный фильтр позволяет проверить сообщение перед его доставкой, и могут быть предприняты различные действия, в зависимости от того, что находит фильтр. В контексте сканирования на вирусы это может сэкономить некоторые ресурсы, выполнив предварительное тестирование перед вызовом внешнего сканера. Однако использование системного фильтра создает некоторые сложности, которых можно избежать, если фильтр не используется.

В качестве примера использования фильтра рассмотрим проверку сообщения на наличие вложений. Сообщения MIME с вложениями должны содержать строку заголовка, например:

Content-Type: multipart/mixed;
  boundary="------------ 9D6D28528332819A908698F9"

Должна быть определена «граница» (boundary), чтобы были какие-либо вложения. Вы можете проверить это в системном фильтре с помощью этой команды:

if $h_ Content-Type: contains "boundary" then ...

и передать сообщение внешнему сканеру, только если условие было выполнено.

5.10.1 Проверка на вирусы на локальном хосте

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

Обычный способ идентификации сообщений, возвращенных сканером, заключается в использовании параметра командной строки -oMr. Это определяет протокол, по которому принимается сообщение. Обычно для него устанавливаются такие значения, как smtp или local, но доверенные вызывающие абоненты могут установить для него произвольную строку. Значение записывается в журнал и доступно в переменной $received_protocol, но Exim не использует его иначе.

Поэтому, чтобы это работало, вам нужно решить, какой uid будет использоваться для запуска сканера, и настроить его как доверенного пользователя Exim. Хорошей идеей будет зарезервировать специальный uid только для этой цели. Предположим, вы создали пользователя с именем vircheck. Добавьте следующее в вашу конфигурацию Exim:

trusted_users = vircheck

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

Затем вам нужно настроить транспорт для передачи сообщения сканеру. В следующем примере используется пакетный SMTP (BSMTP):

pipe_to_scanner:
  driver = pipe
  command = /path/to/scanner/command
  user = vircheck
  use_bsmtp
  batch_max = 100

Установка use_bsmtp приводит к тому, что сообщение доставляется в формате BSMTP, который включает конверт в виде SMTP-команд. Значение batch_max гарантирует, что только одна копия сообщения будет отправлена максимум 100 получателям. (По умолчанию для локальных транспортов используется одна копия на получателя.)

Использование BSMTP упрощает для сканера возврат сообщения с неизменными отправителем и получателями. Например, если значение специального протокола имеет значение scanned-ok, сканер может запустить команду вида:

exim -oMr scanned-ok -bS

и скопировать полученное сообщение на стандартный ввод этой команды без изменений. Например, следующий Perl-скрипт представляет собой фиктивный сканер, который на самом деле не выполняет никаких проверок, а просто повторно отправляет сообщение:

#!/usr/bin/perl
open(OUT, "|exim -oMr scanned-ok -bS")
  || die "Failed to set up Exim process\n";
print OUT while (<STDIN>) ;
close (OUT);

Теперь вам нужно организовать перенаправление новых сообщений на транспорт, который передает их сканеру. Вы можете сделать это, вставив новый роутер в начале конфигурации роутера:

send_to_scanner:
  driver = accept
  transport pipe _to_scanner
  condition = ${if eq {$received_protocol}{scanned-ok}{no}{yes}}

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

Ранее мы сталкивались с такими параметрами, как domains и local_parts, которые применяют предварительные условия к работе роутеров. Эти опции существуют независимо, потому что они реализуют часто требуемые тесты. Чтобы справиться с менее распространенными требованиями, существует опция condition. Ее значение представляет собой развернутую строку. Если результат равен 0, no или false, роутер пропускается. При любых других значениях роутер запускается (конечно, при соблюдении других предварительных условий). Это средство позволяет применять настраиваемые предварительные условия, используя любые функции, доступные в расширениях строк.

Параметр condition в этом примере проверяет значение $received_protocol и пропускает роутер, если его значение scanned-ok. Без этой проверки, сообщения будут вечно зацикливаться между Exim и сканером. Для сообщений, не полученных от сканера, все адреса направляются на транспорт pipe_to_scanner. Если получателей не более 100, транспорт объединяет их в одну доставку в программу сканера из-за настройки batch_max.

Эта конфигурация отправляет все сообщения, как входящие, так и исходящие, на сканер. Если вы хотите ограничить его только входящими сообщениями, вы можете сделать это с помощью подходящей настройки domains на роутере, чтобы исключить нелокальные домены.

5.10.2 Проверка на вирусы с помощью системного фильтра

Альтернативный способ организовать передачу сообщений программе сканера — использовать системный фильтр (см. главу 10), который будет применяться независимо от того, являются ли получатели локальными или удаленными. Однако в некоторых отношениях это сложнее. Фильтр будет содержать такую команду:

if $received_protocol is not scanned-ok then
  pipe "/the/scanner/command $sender_address 'Srecipients'"
endif

Когда системный фильтр настраивает доставку таким образом, считается, что он обработал механизмы доставки сообщения, поэтому обычная доставка на обычные адреса получателей обходится, а доставка осуществляется только на сканер. В этом примере адрес отправителя передается команде в качестве первого аргумента, а список получателей (разделенных запятой и пробелом) является вторым аргументом, созданным путем расширения $recipients[6]. Чтобы вернуть чистое сообщение для доставки, сканер должен вызвать Exim с эквивалентом этой командной строки:

exim -oi -oMr scanned-ok -f '$sender_address' '$recipients'

и напишисать сообщение на стандартный ввод. Хотя дополнительный роутер не требуется, вам все равно необходимо определить транспорт. У него не должно быть настройки команды, потому что команда задается фильтром. Итак, все, что нужно, это:

pipe_to_scanner:
  driver = pipe
  user = vircheck
  message_prefix =

Удаление message_prefix необходимо, потому что, когда BSMTP не используется, по умолчанию используется строка-разделитель сообщений. Определив транспорт, необходимо установить:

system_filter_pipe_transport = pipe_to_scanner

в основной конфигурации, чтобы указать Exim'у, какой транспорт запускать, когда он встречает команду pipe в системном фильтре. Пакетный SMTP в этом случае использовать нельзя, потому что это особый вид разовой доставки всего сообщения, не зависящий от обычных получателей.

  1. Переменная $recipients доступна только в системных фильтрах. Из соображений конфиденциальности она недоступна в пользовательских фильтрах.

5.10.3 Проверка на вирусы на внешнем хосте

Если ваша программа проверки на вирусы работает на внешнем узле, все, что вам нужно, — это настроить отправку сообщений на проверяющий узел, если только они не поступили оттуда. Допустим, чекер работает на IP-адресе 192.168.13.13; транспорт для доставки сообщений есть:

smtp_to_scanner:
  driver = smtp
  hosts = 192.168.13.13

Роутер для отправки непроверенных сообщений на сканер теперь:

send _to_scanner:
  driver = accept
  transport = smtp _to_ scanner
  condition = ${if eq {$sender_host_address}{192.168.13.13}\
              {no} {yes} }

Если у вас есть несколько хостов, на которых запущена программа проверки на вирусы, вы можете указать транспорт следующим образом:

smtp_to_scanner:
  driver = smtp
  hosts = 192.168.13.13 : 192.168.14.14 : ...
  hosts_randomize

Опция hosts_randomize заставляет Exim размещать хосты в случайном порядке, прежде чем пробовать их, каждый раз, когда запускается транспорт. Условие в роутере теперь усложнилось:

send_to_scanner:
  driver = accept
  transport = smtp_to_scanner
  condition = ${if or {\
                 {eq {$sender_host_address}{192.168.13.13}}\
                 {eq {$sender host _address}{192.168.14.14}}\
                 ...
                 }{no}{yes}}

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

5.11 Изменение тела сообщения

В списке рассылки Exim было много вопросов об изменении тела сообщений, когда они проходят через MTA. Есть три конкретные вещи, которые люди хотят делать:

В связи с этим возникают правовые и этические вопросы, которые здесь обсуждаться не будут[7], но есть и серьезные технические проблемы. С момента появления MIME (RFC 2025) тела сообщений больше не являются (в общем) просто строками текстовых символов. Простое добавление дополнительных символов в конце сообщения может нарушить синтаксис сообщения, в результате чего оно станет нечитаемым для стандартных MUA[8]. Кроме того, если сообщение снабжено цифровой подписью, любое изменение тела делает подпись недействительной. Цифровые подписи становятся все более широко используемыми в настоящее время, когда они имеют юридическую силу в некоторых странах.

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

Если, несмотря на все эти соображения, вы хотите сделать что-то подобное с помощью Exim, вы можете использовать транспортный фильтр (8.8). Транспортный фильтр позволяет пропускать исходящие сообщения через программу или скрипт по вашему выбору. Задача этого скрипта — внести в сообщение необходимые вам изменения. Таким образом, вы имеете полный контроль над тем, какие изменения вносятся, и Exim'у не нужно ничего знать о телах сообщений. Однако использование транспортного фильтра требует дополнительных ресурсов и может замедлить доставку почты.

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

transport = remote_smtp

для роутера, который обрабатывает удаленные адреса, у вас может быть это:

transport = ${if eq {$sender_address_domain} {your.domain}\
            {remote_smtp_filter}{remote_smtp}}

Расширение опции transport проверяет содержимое переменной $sender_address_domain, которая содержит домен отправителя сообщения. Если это your.domain, адрес перенаправляется на транспорт remote_smtp_filter вместо remote_smtp. Новый транспорт определяется следующим образом:

remote_smtp_filter:
  driver = smtp
  transport_filter = /your/filter/command

Все сообщение передается вашей команде фильтра на стандартный ввод. Он должен записать измененную версию в стандартный вывод, стараясь не нарушить синтаксис RFC 2822. Поскольку это удаленный транспорт, команда запускается от имени пользователя Exim.

  1. См. http://www.goldmark.org/jeff/stupid-disclaimers для обсуждения глупости некоторых отказов от ответственности.

  2. Один из таких клиентов (ныне устаревший) аварийно завершает работу при обнаружении сообщения, которое было изменено таким образом.

Глава 6
Общие параметры, применимые ко всем роутерам

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

Как показывают более ранние примеры, один общий параметр, который всегда установлен, — это driver. Это определяет, какой конкретный роутер будет использоваться. Еще один общий вариант, который уже обсуждался, — это transport. Когда роутер решает принять адрес и поставить его в очередь для транспорта, значение transport расширяется и должно выдавать имя доступного транспорта. Если этого не происходит, доставка откладывается.

Общий параметр, который появляется во многих более ранних примерах, — это more. Когда роутер отказывается обрабатывать адрес, установка для more значения false останавливает Exim от передачи адреса другим роутерам, тем самым вызывая сбой маршрутизации. Поскольку параметр more является логическим, следующие два параметра являются синонимами и взаимозаменяемы:

more = false
no_more

Еще одна общая опция caseful_local_part для обработки локальных частей с учетом регистра обсуждалась в предыдущей главе (5.9).

Остальные общие параметры можно разделить на следующие типы:

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

6.1 Условный запуск роутеров

Как мы видели в нескольких примерах, Exim предлагает адрес всем определенным роутерам по очереди, пока не будет найден тот, который может его обработать, или пока не будут опробованы все. Однако перед запуском любого конкретного роутера Exim проверяет ряд предварительных условий. Эти тесты применяются в том порядке, в котором они описаны в остальной части этого раздела; порядок, в котором они появляются в конфигурации, не имеет значения.

6.1.1 Префиксы и суффиксы локальной части

Существует ряд распространенных ситуаций, в которых полезно иметь возможность распознавать префикс или суффикс в локальной части и обрабатывать аффикс и остальную часть локальной части независимо друг от друга. Параметры local_part_prefix и local_part_suffix, обеспечивающие эту возможность, были введены ранее (5.3.4). Они могут быть установлены как списки строк, разделенных двоеточиями. Если какой-либо из них установлен, роутер пропускается, если только локальная часть не начинается или не заканчивается (соответственно) одной из заданных строк. Когда роутер запущен, значение аффикса удаляется из локальной части и помещается в $local_part_prefix или $local_part_suffix, в зависимости от ситуации.

Другим примером использования префикса является предоставление способа обхода файлов .forward пользователей. Например, вы можете сделать это, поставив следующий роутер перед тем, который обрабатывает файлы .forward:

real_users:
  driver = accept
  check_local_user
  local_part_prefix = real-
  transport = local_delivery

Если локальная часть начинается с real-, этот роутер запускается, а если остальная часть локальной части представляет собой имя пользователя, адрес принимается и направляется на транспорт local_delivery. Локальные части, которые не начинаются с префикса, не обрабатываются этим роутером и поэтому передаются дальше.

Использование префикса или суффикса можно сделать необязательным, установив local_part_prefix_optional или local_part_suffix_optional, в зависимости от ситуации. В этих случаях роутер запускается независимо от того, совпадает ли аффикс, но если аффикс есть, он удаляется во время работы роутера. Значения $local_part_prefix и $local_part_suffix можно использовать для определения того, был ли аффикс в исходной локальной части.

Для префиксов и суффиксов доступна ограниченная форма подстановочных знаков. Если префикс начинается со звездочки, он соответствует максимально длинной последовательности произвольных символов в начале локальной части. Точно так же, если суффикс заканчивается звездочкой, он соответствует самой длинной возможной последовательности в конце. Существует более ранний пример этого (5.5), в котором используется следующая опция:

local_part_suffix = -*

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

Если для роутера установлены и local_part_prefix, и local_part_suffix, оба условия должны быть выполнены, если они не являются необязательными. Следует соблюдать осторожность, если подстановочные знаки используются как в префиксе, так и в суффиксе на одном и том же роутере. Во избежание двусмысленности необходимо использовать разные символы-разделители.

6.1.2 Использование роутеров при проверке

Exim проводит различие между обработкой адреса, чтобы доставить на него сообщение, и проверкой адреса, чтобы убедиться, что он действителен. Проверка в основном используется для проверки правильности адресов во время входящих SMTP-соединений в момент получения конверта сообщения. Его также можно явно запросить с помощью SMTP-команды VRFY. Exim использует ACL, чтобы решить, разрешено ли клиентскому хосту использовать VRFY, по умолчанию это запрещено (4.8.2).

Проверка заключается в прогоне адреса через роутеры, как будто он обрабатывается для доставки. Адрес успешно проверяется, если его принимает один из роутеров. Верификация подробно обсуждается в главе 14.

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

verify = false

на роутере приводит к тому, что он будет пропущен во время проверки. На самом деле, это просто сокращение от:

verify_sender = false
verify_recipient = false

которые подавляют роутер при проверке отправителей и получателей независимо друг от друга.

В качестве примера того, где это может быть полезно, предположим, что вы используете роутер accept без предварительных условий для передачи всех нераспознанных локальных частей сценарию, который пытается генерировать полезные сообщения об ошибках, или другому хосту, который может обрабатывать эти адреса. Это означает, что никакая локальная часть, передаваемая роутерам, никогда не приведет к сбою во время доставки сообщения. Однако, если настроена проверка отправителей во время SMTP, вы не хотите, чтобы произвольные локальные части в вашем домене принимались в качестве действительных входящих отправителей. Решение состоит в том, чтобы установить no_verify_sender на специальном роутере accept, чтобы он не запускался при проверке отправителей.

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

verify_only

в его конфигурации. Если вы действительно этого хотите, используя verify_only и no_verify, вы можете разделить роутеры на набор, который используется только для доставки, и другой набор, который используется только для проверки.

6.1.3 Управление EXPN

Команда SMTP EXPN не используется для доставки почты, а вместо этого запрашивает расширение адреса, чтобы показать результат псевдонима или переадресации. В последнее время EXPN потерял популярность, и многие сайты считают его нарушением конфиденциальности. По этой причине Exim использует ACL, чтобы решить, разрешено ли клиентскому хосту использовать EXPN, по умолчанию запрещая это.

Когда EXPN разрешен, данный адрес передается роутерам для расширения. Однако, если для любого роутера опция expn установлена как false, она пропускается при расширении адреса в результате обработки команды EXPN. Если вы вообще разрешаете EXPN, вы можете, например, отключить эту опцию на роутере для файлов .forward пользователей, оставив ее включенной для системного файла псевдонимов.

6.1.4 Ограничение роутеров определенными доменами

Параметр domains используется для указания того, что конкретный роутер должен работать только тогда, когда адрес содержит определенные домены, как в примере с виртуальными доменами из предыдущей главы:

virtuals:
  driver = redirect
  domains = cdb;/etc/virtuals
  data = ${lookup{$local_part] lsearch{/etc/$domain.aliases}}
  no_more

6.1.5 Ограничение роутеров определенными локальными частями

Точно так же, как опция domains ограничивает роутер определенными доменами, опция local_parts ограничивает роутер определенными локальными частями. Мы показали пример этого при перенаправлении почты postmaster для группы локальных доменов на один и тот же адрес:

postmaster:
  driver = redirect
  local_parts = postmaster
  data = postmaster@your.domain.example

Как упоминалось ранее, обработка local_part_prefix и local_part_suffix происходит перед проверкой local_parts.

6.1.6 Проверка локальных учетных записей пользователей

В нескольких более ранних примерах показана опция check_local_user, которая является предварительным условием, гарантирующим, что локальная часть является действительным именем пользователя на локальном хосте перед запуском роутера. Проверка выполняется вызовом системной функции getpwnam(), а не просмотром файла /etc/passwd напрямую, так что пользователи, определенные другими средствами (такими как NIS), распознаются.

6.1.7 Ограничение роутеров определенными отправителями

Опция senders ограничивает роутер сообщениями только с определенными отправителями. Мы видели пример того, как его можно использовать для реализации закрытого списка рассылки, в предыдущей главе (5.3.4). Проверяется отправитель конверта, а не содержимое строки заголовка From:.

6.1.8 Ограничение роутеров по наличию файла

Опция require_files определяет наличие (или отсутствие) определенных файлов для определения того, запущен ли роутер, и примеры ее использования приведены в предыдущей главе. Вот роутер, который был показан в качестве примера вызова SmartList:

list_router:
  driver = accept
  local_part_suffix = -request
  local_part_suffix_optional
  local_parts = !.bin:!.etc
  require_files = /usr/slist/$local_ part/re.init
  transport = list_transport

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

Если параметр require_files используется на роутере с установленным check_local_user, переменная расширения $home может использоваться для ссылки на домашний каталог пользователя, чье имя совпадает с локальной частью адреса.

Роутер пропускается, если какой-либо требуемый путь не существует или если какой-либо путь предшествует ! существует. Если Exim не может определить, существует файл или нет, доставка сообщения откладывается. Это может произойти, когда файловые системы, смонтированные по NFS, недоступны.

Когда Exim маршрутизирует адрес как часть доставки сообщения, проверка требуемых файлов запускается от имени пользователя root, но есть средство для проверки доступности файла другим пользователем. Если элемент в списке require_files не содержит символов косой черты, он принимается за имя пользователя (и необязательное имя группы, разделенное запятой) для проверки последующих файлов в списке. Например:

require files = $local_part:$home/.procmailre

Если группа не указана, но пользователь указан символически, используется первичная группа пользователя. Если имя пользователя или группы в списке require_files не существует, предварительное условие не выполняется.

Exim выполняет эту проверку, сканируя компоненты пути к файлу и проверяя доступ для данного пользователя и группы. Он проверяет наличие доступа «execute» к каталогам и доступ «read» к конечному имени файла[1].

Когда Exim проверяет адрес как часть политики проверки входящего SMTP-сообщения, роутеры запускаются как exim, а не как root. Это может повлиять на результат проверки require_files и привести к ошибке «Permission denied» (отказано в доступе), потому что пользователь Exim не может получить доступ к одному из каталогов на пути к файлу. По умолчанию это рассматривается как ошибка конфигурации; происходит временный сбой проверки.

Во многих случаях вы можете обойти это, установив no_verify на роутере. Например, проверка наличия у пользователя файла .procmailrc обычно не имеет значения для проверки. Однако в некоторых случаях может быть желательным рассматривать это состояние так, как если бы файл не существовал. Если перед именем файла (или восклицательным знаком, стоящим перед именем файла в случае отсутствия) стоит знак «плюс», ошибка «Permission denied» обрабатывается так, как если бы файл не существовал. Например:

require_files = +/some/file
  1. Это означает, что списки контроля доступа к файлам, если они есть в операционной системе, игнорируются.

6.1.9 Ограничение роутеров по другим условиям

Только что описанные domains, local_parts, senders и другие параметры существуют, потому что это общие предварительные условия, которые часто необходимы. Для менее распространенных случаев есть опция, называемая condition. Ее значение расширяется, и если результатом является принудительный сбой, пустая строка или одна из строк 0, no или false, роутер не запускается. Есть несколько примеров этого в обсуждении сканирования на вирусы в предыдущей главе. Благодаря гибкости механизма растяжения строк можно тестировать широкий диапазон условий. Однако, как и все параметры, condition может быть установлено только один раз на данном роутере, хотя условие может быть сколь угодно сложным.

Например, предположим, что вы готовы доставлять небольшие сообщения напрямую через Интернет, но хотите отправлять большие сообщения на какой-либо другой хост (который может, например, отправлять их в одночасье). Роутер notlocal можно изменить следующим образом:

notlocal:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  condition = ${if < {$message_size}{500K}{yes}{no}}

Строка расширения использует числовое сравнение (обозначенное знаком «меньше») переменной $message_size, которая содержит размер сообщения. Если размер сообщения меньше 500 КБ, строка расширяется до yes. В противном случае она расширяется до no. Таким образом, этот роутер обрабатывает только сообщения размером менее 500 КБ. Вам, конечно, также понадобится дополнительный роутер для обработки больших сообщений. Для этого можно использовать роутер с ручным маршрутом.

6.2 Изменение успешного результата роутера

Общие параметры, описанные в этом разделе, позволяют вам изменить то, что происходит в некоторых случаях, когда роутер успешно обрабатывает адрес.

6.2.1 Обработка перенаправленных адресов

Когда роутер перенаправляет адрес и генерирует один или несколько дочерних адресов, каждый из дочерних адресов маршрутизируется независимо, обычно начиная с первого роутера. Однако вы можете указать, что дочерние адреса должны начинаться с другого роутера, установив параметр redirect_router. Например, если у вас есть роутер с псевдонимами, который превращает настоящие имена в имена для входа, повторная обработка имен для входа на том же роутере будет пустой тратой ресурсов, потому что он всегда будет отклоняться. Вы можете сэкономить ресурсы, настроив альтернативный роутер следующим образом:

real_names:
  driver = redirect
  data = ${lookup{$local_part}1lsearch{/etc/real-to-login}}
  redirect_router = system_aliases
system_aliases:
...

Роутер, указанный как redirect_router, может быть более ранним или более поздним в конфигурации.

6.2.2 Настройка нескольких доставок

Когда роутер успешно обрабатывает адрес, этот адрес обычно не требует дальнейших действий, и поэтому он не передается большему количеству роутеров. Предположим, однако, что вы хотите сохранить копии сообщений, адресованных определенным получателям[2]. Использование роутера для настройки доставки необходимых копий является удобным способом справиться с этим требованием, но, конечно, сообщения также должны продолжать нормально доставляться. Опция unseen предназначена именно для этой цели. Это дополнение no_more; unseen заставляет маршрутизацию продолжаться, хотя в противном случае она была бы остановлена. Таким образом, указанный ниже роутер отправляет копию каждого сообщения, адресованного ceo@plc.example, на secretary@plc.example, не нарушая нормальной доставки:

copy_ceo:
  driver = redirect
  local_parts = ceo
  domains = plc.example
  data = secretary@plc.example
  unseen

Чтобы это работало, этот роутер должен предшествовать роутеру, который устанавливает обычную доставку. Настройка доменов необходима только в том случае, если роутеры по-разному обрабатывают разные домены.

  1. Если вы хотите сохранять копии сообщений независимо от получателей, лучше всего использовать системный фильтр (см. главу 10), потому что он может создать единственную копию, сколько бы получателей ни было у сообщения.

6.2.3 Принудительный отказ проверки адреса

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

fail_verify_sender
fail_verify_recipient

Эти параметры управляют механизмом принудительного отказа независимо для адресов отправителя и получателя. Они позволяют не пройти проверку для набора адресов, который определяется тем, что соответствует конкретному роутеру. Эти параметры применяются только во время проверки и игнорируются при обработке адреса для доставки. Вот пример реального случая, когда это используется.

При аннулировании учетной записи в одной из центральных систем Кембриджского университета она не сразу удаляется из данных пароля; вместо этого пароль сбрасывается, а домашний каталог устанавливается в /home/CANCEELLED. Это упрощает восстановление учетной записи. Только по прошествии некоторого времени она полностью удаляется. Почта для этих отмененных учетных записей не должна приниматься, и поэтому проверка их адресов должна завершиться ошибкой, чтобы входящая SMTP-почта для них отклонялась. Для этого необходимы действия, потому что имена пользователей все еще находятся в данных пароля. Самое простое, что можно сделать, это добавить:

condition = ${if eq{$home}{/home/CANCELLED}{no}{yes}}

к обычному роутеру localuser, чтобы учетные записи, не имеющие обычного домашнего каталога, не распознавались. Этим достигаются две вещи:

Проблема в том, что в рикошетах выдается ошибка «unknown user», из-за чего отправители часто пристают к почтмейстеру с вопросами о том, что случилось с аккаунтом, «который работал еще вчера». Поэтому мы используем другую стратегию. Следующий дополнительный роутер вставляется перед теми, которые обрабатывают локальных пользователей:

cancelled_users:
  driver = accept
  check_local_user
  condition = ${if eq{$home}{/home/CANCELLED}{yes}{no}}
  fail_verify
  transport = cancelleduser_pipe

Этот роутер проверяет локального пользователя, чей домашний каталог /home/CANCEELLED; другие локальные части передаются следующему роутеру. При доставке сообщение передается транспорту cancelleduser_pipe, чтобы сгенерировать сообщение о возврате, объясняющее, что произошло. Установка fail_verify гарантирует, что при проверке адресов любые совпадения вызовут ошибку проверки.

6.2.4 Игнорирование ошибочных IP-адресов

Существуют определенные IP-адреса (например, 127.0.0.1), которые никогда не должны находиться в DNS в качестве целей для доставки почты. К сожалению, некоторые администраторы DNS (по незнанию или по злому умыслу) настраивают их. Если Exim встречает 127.0.0.1 как IP-адрес предположительно удаленного хоста, он обычно замораживает сообщение (6.5). Это добавляет работы локальному администратору.

Параметр ignore_target_hosts позволяет указать, что определенные IP-адреса должны полностью игнорироваться. Exim ведет себя так, как будто соответствующей записи DNS (или другого источника информации) не существует. Например, в файле конфигурации Exim по умолчанию роутер dnslookup, который обрабатывает удаленные домены, содержит этот параметр:

ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8

Это игнорирует 0.0.0.0[3], а также любой IP-адрес, начинающийся с 127. Любые домены, которые разрешаются в эти адреса, обрабатываются так же, как и домены, поиск по которым невозможен.

  1. Многие стеки TCP/IP интерпретируют 0.0.0.0 как «локальный хост», но в других ситуациях (например, в таблицах брандмауэра) это может означать «любой хост».

6.3 Добавление данных для использования транспортами

Когда роутер принимает адрес, он может прикрепить к нему данные для использования, когда адрес наконец будет транспортирован. Некоторые пункты актуальны только тогда, когда роутер передает адрес напрямую транспорту; другие накапливаются по мере того, как адрес проходит через несколько роутеров (например, несколько экземпляров псевдонимов или переадресации).

6.3.1 Запоминание произвольных данных

В системе, где вся информация о локальных пользователях хранится в базе данных, желательно свести к минимуму количество операций поиска в базе данных. В процессе маршрутизации и доставки сообщения, разные опции Exim'а могут нуждаться в обращении к разным полям в одной и той же записи базы данных.

Вы можете настроить конфигурацию с отдельным поиском в базе данных для каждого поля каждый раз, когда оно используется. Однако более эффективно указать единый поиск, чтобы получить все поля только один раз. Опция address_data делает это возможным.

Непосредственно перед запуском роутера его значение расширяется и связывается с адресом. Доступ к результату можно получить через переменную $address_data в приватных опциях роутера и в опциях транспорта, на который направляется адрес.

Если роутер генерирует дочерние адреса, значение $address_data распространяется на них. Если роутер отказывается принимать адрес, $address_data остается установленным и может использоваться в последующих роутерах. Однако, если другой роутер использует опцию address_data, она перезаписывает предыдущее значение.

Если расширение address_data принудительно завершится ошибкой, роутер отклонит запрос, оставив $address_data пустым. Любая другая ошибка расширения приводит к отсрочке доставки адреса.

Рассмотрим хост, на котором информация о пользователях хранится в базе данных MySQL. Первый роутер, который обслуживает локальных пользователей, может быть следующим:

userforward:
  driver = redirect
  address_data = ${lookup mysql {\
         select uid,gid,mailbox,home,forward from users\
         where name='{mysql_quote:$local_part}'}{$value}fail}
  data = ${extract{home}{$address_data}\
        /${extract{forward}{$address_data}}

У роутера нет предварительных условий, поэтому он всегда пытается расширить address_data, вызывая поиск локальной части в базе данных. Если поиск терпит неудачу (то есть пользователь не существует), расширение принудительно завершается ошибкой, и роутер отказывается. В противном случае, поскольку оператор MySQL выбирает несколько полей, значение $address_data может быть установлено примерно так для пользователя, чье имя для входа в систему — adc:

uid=1234 gid=5678 mailbox=/var/mail/adc home=/home/adc forward=.forward

Роутер запускается и расширяет параметр данных, который использует два элемента расширения извлечения для получения имени домашнего каталога и файла пересылки в нем из строки в $address_data. Если этот файл существует, он обрабатывается; если нет, роутер отказывается, оставляя значение $address_data для следующего роутера:

localuser:
  driver = accept
  condition = ${if def:address_data{no}{yes}}
  transport = local_delivery

Этот роутер проверяет действительного пользователя, проверяя, установлено ли в $address_data значение, отличное от пустой строки. Затем он направляет адрес на этот транспорт:

local delivery:
  driver = appendfile
  file = ${extract{mailbox}{$address_data}}
  user = ${extract{uid}{$address_data}}
  group = ${extract{gid}{$address_data}}

Транспорт использует $address_data для получения имени почтового ящика, а также пользователя и группы, под которыми должна выполняться доставка.

Опция address_data также может быть полезна в тех случаях, когда поиск вообще не задействован. Вы можете использовать его как общее средство передачи данных между роутерами и транспортами. Мы показали использование адресных данных, которые ранее не включали поиск (5.8).

6.3.2 Добавление или удаление строк заголовков

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

Добавление и удаление строк заголовков можно указать на роутерах (а также на транспортах), установив headers_add и headers_remove соответственно. Обратите внимание, однако, что адреса с разными настройками headers_add или headers_remove не могут быть переданы как несколько получателей конверта в одной копии сообщения. Это может снизить производительность.

Каждый из этих параметров задает строку, которая расширяется во время маршрутизации и сохраняется для использования во время транспортировки. Если расширение принудительно завершается неудачно, опция не действует. Для headers_remove расширенная строка должна состоять из списка имен заголовков, разделенных двоеточиями, без двоеточий в конце. Сравнение не чувствительно к регистру. Например:

headers_remove = return-receipt-to:acknowledge-to

Для headers_add расширенная строка должна быть представлена в виде одной или нескольких строк заголовка RFC 2822, разделенных символами новой строки (кодируется как \n внутри строки в кавычках). Например:

headers_add = "X-added-header: added by Sprimary_hostname\n\
               X-another: added at time $tod_full"

Exim не проверяет синтаксис этих добавленных строк заголовков, за исключением того, что в конце ставится новая строка, если она отсутствует. Если адрес проходит через несколько роутеров в результате операций псевдонимов или переадресации, любые спецификации headers_add или headers_remove являются кумулятивными и сохраняются для использования транспортом. Это позволяет добавлять строки заголовков, в которых записываются операции псевдонимов и переадресации адреса. Например, вы можете добавить:

headers_add = X-Delivered-To: $local_part@$domain

к конфигурации каждого роутера redirect. Сообщение, адресованное на адрес postmaster@example.com, с псевдонимом p.master@example.com, а затем перенаправленное на pat@example.com, будет заканчиваться такими добавленными строками заголовка:

X-Delivered-To: postmaster@example.com
X-Delivered-To: p.master@example.com

Это обеспечивает полную запись того, как адрес был обработан.

Поскольку добавление строк заголовков фактически не происходит до тех пор, пока сообщение не транспортируется, добавленные строки недоступны для последующих роутеров, которые могут обрабатывать адрес. Например, расширение $header_X-Delivered-To: будет пустой строкой в только что рассмотренном примере.

Во время транспортировки удаление применяется только к исходным строкам заголовков, прибывшим вместе с сообщением, а также к тем, которые были добавлены системным фильтром. Невозможно удалить строки заголовков, добавленные роутером. Для каждого адреса все исходные строки заголовков, перечисленные в headers_remove, удаляются, а строки, указанные в headers_add, добавляются в том порядке, в котором они были прикреплены к адресу. Затем добавляются любые дополнительные строки заголовка, указанные транспортом.

6.3.3 Изменение обратного пути

Когда адрес перенаправляется, иногда желательно изменить адрес, на который будут отправляться последующие сообщения о возврате. Это называется обратным путем (return path), адресом ошибки (error address) или отправителем конверта (envelope sender).

Самый распространенный случай — это «взрыв» (exploded) списка рассылки. Сообщения о возврате должны возвращаться менеджеру списка, а не отправителю сообщения. Общий параметр error_to можно использовать на любом роутере для изменения отправителя конверта для доставки любых адресов, которые он обрабатывает или генерирует. Если адрес впоследствии проходит через другие роутеры, у которых есть собственные настройки error_to, они переопределяют любые более ранние настройки. Значение параметра расширяется и проверяется на достоверность путем его проверки. Не используется, если верификация не удалась. роутер, который мы использовали для списков рассылки в предыдущей главе, выглядит следующим образом:

lists:
  driver = redirect
  domains = lists.simple.example
  no_more
  file = /usr/lists/$local_part
  forbid_pipe
  forbid_file
  errors_to = $local_part-request@$domain

Это показывает очень типичное использование errors_to.

6.3.4 Контроль среды для местных поставок

Если роутер ставит в очередь адрес для локального транспорта, можно использовать параметры user и group, чтобы указать uid и gid, под которыми должен выполняться процесс локальной доставки. Один из распространенных случаев, когда требуется параметр user, — это когда файл псевдонима устанавливает доставку в канал или файл. Например, если /etc/aliases содержит эту строку:

majordomo: |/usr/mail/majordomo ...

затем либо роутер system_aliases, либо транспорт, на который он отправляет такие доставки, должны определить uid и gid, под которыми должна выполняться команда pipe. Конфигурация роутера может содержать, например:

user = majordom

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

Предположим, вы запускаете другую программу через канал из файла псевдонимов, помимо Majordomo. Возможно, это автоматизированный способ получения помощи, так что ваши псевдонимы:

majordomo: |/usr/mail/majordomo ...
autohelp:  |/usr/etc/autohelp ...

Каждый канал должен работать под собственным uid, поэтому фиксированное значение, такое как только что показанное, больше невозможно. Расширенную строку можно использовать для выбора правильного пользователя следующим образом:

user = ${if eq {$local_part}{majordomo}{majordom}{autohelp}}

Эта строка расширения проверяет локальную часть на наличие значения majordomo и расширяется до majordom, если оно совпадает. В противном случае она расширяется до autohelp.

Если пользователь указан без group, по умолчанию используется группа, связанная с пользователем. Значение по умолчанию для этих параметров не установлено, если не установлен check_local_user, и в этом случае значения по умолчанию берутся из данных пароля.

Uid и gid, установленные роутером, могут быть переопределены параметрами транспорта. Если ни роутер, ни транспорт не указывают uid или gid, доставка выполняется от имени пользователя или группы Exim соответственно.

Другой способ справиться с предыдущим примером — поместить настройку user на транспорт вместо роутера system_aliases. Однако, если вы сделаете это, вы, вероятно, захотите настроить выделенный транспорт для всех команд канала, генерируемых этим роутером, чтобы другие команды канала (например, из пользовательских файлов .forward) не использовали транспорт с этим настройка пользователя. Вы можете использовать pipe_transport на роутере system_aliases, чтобы указать, какой транспорт используется для команд канала, сгенерированных в этом роутере.

Пользователь может быть членом многих групп, но (по крайней мере, в некоторых операционных системах) это довольно затратная операция, чтобы найти их и настроить их все при смене uid, поэтому Exim не делает этого по умолчанию. Если вы хотите, чтобы это было сделано, то в дополнение к настройке пользователя вы должны установить initgroups, что является логическим параметром, который не принимает данные. Например:

user = majordom
initgroups

Два других параметра, transport_current_directory и transport_home_directory, доступны для установки текущего и домашнего каталога, соответственно, для использования локальными транспортами. Их значения расширяются во время запуска транспорта, но могут быть переопределены настройками самого транспорта. Когда установлен check_local_user, по умолчанию в качестве домашнего каталога используется домашний каталог пользователя.

  1. Exim использует функции операционной системы для поиска пользователей и групп. Они могут обращаться к /etc/passwd и /etc/group, но в более крупных системах данные паролей обычно хранятся в другом месте, например, в базах данных NIS или NIS+.

6.3.5 Указание резервных хостов

Последний параметр, который настраивает передачу данных на транспорт, называется fallback_hosts. Эта опция предоставляет средство «использовать смарт-хост, только если доставка не удалась». Он применяется только к удаленным транспортам, и его значение должно быть списком имен хостов или IP-адресов, разделенных двоеточием. Это не расширенная строка. Если транспорт не может выполнить доставку ни на один из обычных хостов и ошибки не являются постоянным отказом, адреса помещаются в отдельную транспортную очередь, а их списки хостов заменяются резервными хостами.

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

notlocal:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  fallback_hosts = smart.host.example

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

После завершения обычных попыток доставки резервная очередь обрабатывается путем повторного запуска тех же транспортов с новыми списками хостов. Это делается таким образом (вместо того, чтобы пробовать резервные хосты, как только обычные хосты выходят из строя), так что, если несколько неисправных адресов имеют одни и те же резервные хосты (и max_rcpt это разрешает), единственная копия сообщения с несколькими получателями отправляется.

Транспорт smtp также имеет опцию fallback_hosts. Однако настройка fallback_hosts на роутере переопределяет любую настройку на транспорте.

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

6.4 Обработка тайм-аутов DNS

Мы описали множество случаев отказа роутеров (например, когда локальная часть не найдена в файле псевдонимов или когда файл .forward не существует). Существует еще одно возвращаемое значение, называемое «pass» (пропустить), которое может выдать роутер, когда он не может обработать адрес. Это означает, что роутер каким-то образом распознал адрес и знает, что его нужно передать другому роутеру. Роутер проходит только тогда, когда это явно настроено, но не по умолчанию. Два наиболее распространенных случая, когда «pass» полезен, — это обработка тайм-аутов DNS и доменов, которые направляются на локальный хост.

Есть два различия между тем, как обрабатываются «decline» (отклонить) и «pass» (пропустить):

Если время ожидания роутера истекло при попытке поиска записи MX или IP-адреса хоста, это обычно приводит к отсрочке доставки адреса. Однако, если установлен pass_on_timeout, роутер пропускается. Это может быть полезно для систем, которые периодически подключаются к Интернету, или тех, которые хотят передать промежуточному хосту любые сообщения, которые не могут быть доставлены немедленно, как в этом примере:

notlocal_direct:
  driver = dnslookup
  domains = ! +local_ domains
  transport = remote_smtp
  pass_on_timeout
  no_more

notlocal_smarthost:
  driver = manualroute
  transport = remote_smtp
  route_list = ! +local_domains smart.host.example

Первый роутер ищет домен в DNS; если он не найден, роутер отклоняется, но из-за no_more дальнейшие попытки роутеров не предпринимаются, и адрес терпит неудачу. Однако, если время поиска DNS истекает, адрес передается следующему роутеру, который отправляет его на смарт-хост.

Тайм-аут — это только один пример временной ошибки, которая может возникнуть при выполнении поиска DNS. Все такие ошибки обрабатываются так же, как тайм-аут, и ко всем из них применяется pass_on_timeout.

6.5 Домены, направляющие на локальный хост

Обычно роутер dnslookup считает ошибкой появление локального хоста в списке MX с самым низким приоритетом. Точно так же роутер manualroute считает ошибкой, если первый хост в сгенерированном списке является локальным хостом[5].

Эта ситуация может возникнуть в результате ошибки в конфигурации Exim (например, домен является локальным, который не должен обрабатываться этим роутером), или это может быть ошибка в DNS (например, самый низкий номер MX запись не должен указывать на этот хост). В простой конфигурации отправка сообщения вызовет зацикливание почты. Таким образом, действие Exim по умолчанию состоит в том, чтобы отложить доставку и заморозить сообщение, чтобы привлечь к нему внимание администратора.

В более сложных ситуациях может потребоваться другое действие. То, что Exim делает, когда домен маршрутизирует к локальному хосту, контролируется значением общей опции self. Оно может быть установлено в одно из нескольких описательных слов, по умолчанию freeze.

  1. Если локальный хост появляется не в начале списка, он отбрасывается вместе с любыми менее предпочтительными хостами. Тест для локального хоста включает проверку IP-адреса (адресов) предположительно удаленного хоста по сравнению с интерфейсами на локальном хосте. Если установлен параметр local_interfaces, проверяются только перечисленные в нем интерфейсы.

6.5.1 Отсрочка маршрутизации доменов к себе без замораживания

Если параметр self установлен на defer, доставка адреса откладывается, но сообщение не замораживается, поэтому доставка будет повторяться через определенные промежутки времени. Если это будет продолжаться достаточно долго, время ожидания адреса истечет, и он будет возвращен.

6.5.2 Передача доменов, маршрутизируемых на себя, другому роутеру

Если для self установлено значение pass, роутер возвращает «pass», что означает, что адрес передается следующему роутеру или роутеру, указанному в параметре pass_router (6.4). Переменной $self_hostname присваивается имя хоста, которое было первым в списке (то есть имя, которое разрешилось в локальный хост).

Поскольку «pass» имеет приоритет над no_more, комбинация:

self = pass
no_more

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

notlocal:
  driver = dnslookup
  transport = remote_smtp
  self = pass
  no_more

Домены, разрешающие доступ к удаленным хостам, направляются на транспорт remote_smtp обычным способом. Неопознанные домены отбрасываются, потому что no_more предотвращает их предложение любым последующим роутерам, но домены, которые разрешаются на локальный хост, передаются из-за настройки self. Затем последующие роутеры могут предположить, что они имеют дело с набором доменов, записи DNS которых указывают на локальный хост.

Этот подход является альтернативой использованию явного списка в опции доменов, как показано в более ранних примерах роутеров для нелокальных доменов. Если количество доменов невелико, проверка явного списка более эффективна, чем поиск в DNS. Однако, если количество доменов велико и особенно если оно часто меняется, сохранение только одного списка (в DNS) может упростить обслуживание.

6.5.3 Перенаправление доменов, перенаправленных на себя

Если для self установлено значение reroute:, за которым следует имя домена, домен изменяется на заданный домен, а адрес передается обратно для повторной обработки роутерами. Это еще одна форма перенаправления адресов. Например:

self = reroute: newdom.example.com

меняет домен на newdom.example.com и перерабатывает адрес с нуля. Перезаписи строк заголовков не происходит, но есть альтернативная форма, вызывающая перезапись заголовков:

self = reroute: rewrite: newdom.example.com

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

6.5.4 Неудачные домены, перенаправленные на себя

Если для параметра self установлено значение fail, роутер отклоняет вызов, но адрес не передается ни одному из следующих роутеров. Следовательно, доставка завершается неудачно, и создается отчет об ошибке.

6.5.5 Транспортировка доменов, маршрутизируемых к себе

Наконец, если для параметра self установлено значение send, аномалия маршрутизации игнорируется, и адрес передается в транспорт обычным способом. Этот параметр следует использовать с особой осторожностью из-за опасности зацикливания. Для удалённой доставки это имеет смысл только в тех случаях, когда программа, прослушивающая порт TCP/IP локального хоста, не является этой версией Exim. То есть это должен быть какой-то другой MTA, или Exim с другим конфигурационным файлом, который по-другому обрабатывает домен.

6.6 Отладка роутеров

Существует общая опция под названием debug_print, единственной целью которой является помощь в отладке конфигураций Exim. Когда Exim запускается с включенной отладкой (20.10), значение debug_print расширяется и добавляется к выходу отладки, который записывается в стандартный поток ошибок (stderr). Это происходит перед проверкой senders, require_files и condition, но после других проверок предварительных условий. Это средство можно использовать для проверки того, что значения некоторых переменных соответствуют вашим представлениям о них.

Например, если параметр condition не работает, можно использовать debug_print для вывода значений, на которые он ссылается. В предыдущей главе (5.10) мы использовали этот роутер для отправки сообщений программе поиска вирусов:

send_to_scanner:
  driver = accept
  transport = pipe_to_scanner
  condition = ${if eq {$received_protocol}{scanned-ok}{no}{yes}}

Этот роутер следует пропустить, если $received_protocol имеет значение scanned-ok. Предположим, что эта конфигурация не работает, и вы пытаетесь выяснить, почему. Один очевидный подход — проверить значение $received_protocol во время работы роутера. Вы можете сделать это, добавив:

debug_print = received_protocol=$received_protocol

к роутеру, запустив доставку с включенной отладкой и изучив стандартный вывод ошибок.

6.7 Обзор общих опций роутера

В этом разделе приведены общие параметры, применимые ко всем роутерам:

address_data (string, default = unset)

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

caseful_local_part (Boolean, default = false)

Если для этой опции установлено значение true, локальные части адресов обрабатываются роутером с учетом регистра.

check_local_user (Boolean, default = false)

Если для этой опции установлено значение true, роутер пропускается, если только локальная часть адреса не является именем для входа локального пользователя.

condition (string, default = unset)

Этот параметр указывает общее предварительное условие, которое должно быть успешно выполнено для вызова роутера. Строка расширяется. Если результатом является принудительный сбой, пустая строка или одна из строк 0, no или false (проверяется без учета регистра букв), роутер не запускается.

debug_print (string, default = unset)

Если этот параметр установлен и включена отладка, строка расширяется и включается в выходные данные отладки.

domains (domain list, default = unset)

Эта опция ограничивает роутер определенными почтовыми доменами. Роутер пропускается, если текущий домен не соответствует элементу в списке. Если совпадение достигается с помощью поиска файла, данные, возвращенные поиском для домена, помещаются в переменную $domain_data для использования в строковых расширениях частных параметров роутера и в параметрах любого транспорта, который устанавливает роутер.

driver (string, default = unset)

Этот параметр всегда должен быть установлен. Он указывает, какой из доступных драйверов роутера следует использовать.

errors_to (string, default = unset)

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

expn (Boolean, default = true)

Если для этой опции установлено значение false, роутер пропускается при проверке адреса в результате обработки SMTP-команды EXPN.

fail_verify (Boolean, default = false)

Установка этого параметра приводит к тому, что для fail_verify_sender и для fail_verify_recipient устанавливаются указанные значения.

fail_verify_recipient (Boolean, default = false)

Если этот параметр установлен, когда роутер успешно проверяет адрес получателя, проверка завершается неудачно, а не успешно.

fail_verify_sender (Boolean, default = false)

Если этот параметр установлен, когда роутер успешно проверяет адрес отправителя, проверка завершается неудачно, а не успешно.

fallback_hosts (string list, default = unset)

Если роутер ставит в очередь адрес для удаленного транспорта, этот список узлов связывается с адресом и используется вместо резервного списка узлов транспорта. Расширение строки не применяется к этому параметру. Аргумент должен быть списком имен хостов или IP-адресов, разделенных двоеточием.

group (string, default = see description)

Если роутер ставит в очередь адрес для транспорта, а транспорт не указывает группу, указанная здесь группа используется при выполнении процесса доставки. Значение по умолчанию не установлено, если только не установлен check_local_user, и в этом случае значение по умолчанию берется из данных пароля.

headers_add (string, default = unset)

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

headers_remove (string, default = unset)

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

ignore_target_hosts (host list, default = unset)

Хотя эта опция представляет собой список хостов, обычно он должен содержать IP-адреса, а не имена. Если какой-либо хост, который просматривается роутером, имеет IP-адрес, который соответствует элементу в этом списке, Exim ведет себя так, как будто этот IP-адрес не существует.

initgroups (Boolean, default = false)

Если роутер ставит в очередь адрес для транспорта, и этот параметр установлен, а uid, предоставленный роутером, не переопределяется транспортом, при запуске транспорта вызывается функция initgroups(), чтобы гарантировать, что любые дополнительные группы, связанные с uid добавляются в список вторичных групп.

local_part_prefix (string list, default = unset)

Если этот параметр установлен, роутер пропускается, если только локальная часть не начинается с одной из заданных строк или local_part_prefix_optional не имеет значение true. Если префикс начинается со звездочки, это соответствует максимально длинной последовательности произвольных символов в начале локальной части. Префикс удаляется из локальной части и помещается в $local_part_prefix во время работы роутера.

local_part_prefix_optional (Boolean, default = false)

См. local_part_prefix.

local_part_suffix (string list, default = unset)

Если этот параметр установлен, роутер пропускается, если только локальная часть не заканчивается одной из заданных строк или local_part_suffix_optional не является истинным. Если суффикс заканчивается звездочкой, это соответствует максимально длинной последовательности произвольных символов в конце локальной части. Суффикс удаляется из локальной части и помещается в $local_part_suffix во время работы роутера.

local_part_suffix_optional (Boolean, default = false)

См. local_part_suffix.

local_parts (string list, detault = unset)

Эта опция ограничивает роутер определенными локальными частями. Роутер запускается только в том случае, если локальная часть адреса соответствует списку. Если совпадение достигается путем поиска, данные, возвращенные поиском для локальной части, помещаются в переменную $local_part_data для использования в расширениях частных параметров роутера и в параметрах любого транспорта, который устанавливает роутер.

more (Boolean, default = tue)

Если для этого параметра установлено значение false, роутер работает, но отказывается обрабатывать адрес, никакие другие роутеры не используются.

pass_on_timeout (Boolean, default = false)

Если этот параметр установлен, роутер, который обнаруживает тайм-аут при выполнении поиска DNS, передает адрес следующему роутеру (или роутеру, указанному в параметре pass_router), вместо того, чтобы откладывать доставку.

pass_router (string, default = unset)

Когда роутер возвращает «pass», адрес обычно передается следующему роутеру. Это можно изменить, установив для pass_router имя другого роутера. Однако, новый роутер должен быть позже в конфигурации.

redirect_router (string, default = unset)

Когда роутер генерирует дочерние адреса, они обрабатываются независимо, начиная с первого роутера по умолчанию, но в противном случае с роутера, названного этой опцией. В отличие от pass_router, указанный роутер может быть где угодно в конфигурации.

require_files (string list, default = unset

Этот параметр проверяет наличие или отсутствие указанных файлов или каталогов. Если какая-либо строка пуста, это игнорируется.

self (string, default = freeze)

Этот параметр указывает, что происходит, когда роутер, формирующий список узлов, обнаруживает, что первый узел в списке является локальным узлом. Допустимые значения defer, reroute: <domain name>, pass, fail и send.

senders (address list, default = unset)

Этот параметр ограничивает роутер сообщениями с определенными адресами отправителя.

transport (string, default = unset)

Некоторые роутеры требуют предоставления транспортного средства, за исключением случаев, когда установлен параметр verify_only, где это не имеет значения. Строка должна быть именем сконфигурированного транспорта после расширения. Это позволяет динамически выбирать транспорты.

transport_current_directory (string, default = unset)

Если роутер устанавливает локальную доставку для адреса, значение этой опции передается транспорту, где оно расширяется и используется для установки текущего каталога во время доставки, если только это не переопределено настройкой транспорта. Если нет настроек ни на роутере, ни на транспорте, используется домашний каталог; если нет настройки домашнего каталога, используется корневой каталог /.

transport_home_directory (string, default = see description)

Если роутер устанавливает локальную доставку для адреса, значение этой опции передается транспорту, где оно расширяется и используется для установки домашнего каталога во время доставки, если только это не переопределено настройкой транспорта. Значение по умолчанию не установлено, если для роутера не установлено значение check_local_user, в этом случае по умолчанию используется домашний каталог пользователя.

unseen (Boolean, default = false)

Установка этого параметра аналогична квалификатору команды unseen в файлах фильтров. Это приводит к тому, что копия входящего адреса передается последующим роутерам, даже если текущему удается ее обработать.

user (string, default = see description)

Если роутер ставит в очередь адрес для транспорта, а транспорт не указывает пользователя, указанный здесь пользователь используется при выполнении процесса доставки. Этот пользователь также используется роутером redirect при запуске файла фильтра. Значение по умолчанию не установлено, за исключением случаев, когда установлен check_local_user, и в этом случае значение по умолчанию берется из данных пароля.

verify (Boolean, default = true)

Установка этого параметра приводит к установке для verify_sender и verify_recipient указанного значения.

verify_only (Boolean, default = false)

Если этот параметр установлен, роутер используется только при проверке адреса или тестировании с параметром -bv, а не при фактической доставке, тестировании с параметром -bt или выполнении команды SMTP EXPN. Роутер может быть дополнительно ограничен проверкой только отправителей или получателей с помощью verify_sender и verify_recipient.

verify_recipient (Boolean, default = true)

Если этот параметр равен false, этот роутер пропускается при проверке адресов получателей.

verify_sender (Boolean, default = true)

Если этот параметр равен false, этот роутер пропускается при проверке адресов отправителей.

Глава 7
Роутеры

В этой главе мы подробно обсудим каждый из драйверов роутера. Роутеры — это компоненты Exim, которые принимают решения о доставке сообщений. Фактические поставки осуществляются транспортом, который рассматривается в следующих двух главах. Некоторые роутеры могут прикреплять список хостов к адресу для использования транспортом, но ограничений между роутерами и транспортами нет; любой роутер может направить адрес на любой транспорт. Доступные роутеры:

accept

Роутер, который принимает любой переданный ему адрес. Он используется с опцией check_local_user, чтобы принимать сообщения для локальных пользователей. Без каких-либо предварительных условий его можно использовать в качестве «обходного» (catchall) роутера для обработки адресов, которые были отклонены другими роутерами.

dnslookup

Роутер, который ищет домены в DNS и выполняет обработку MX.

ipliteral

Роутер, который обрабатывает «литеральные IP-адреса», такие как user@[192.168.5.6]. Это реликвии раннего Интернета, и больше не используются.

manualroute

Роутер, который маршрутизирует домены, используя локальную информацию, такую как список доменов и соответствующих хостов.

queryprogram

Роутер, который запускает внешнюю программу для маршрутизации адреса.

redirect

Роутер, который обрабатывает несколько различных видов перенаправления, включая файлы псевдонимов, пользовательские файлы .forward и файлы фильтров Exim. Это также может явно вызвать сбой адреса.

7.1 Роутер accept

Роутер accept — самый простой из роутеров Exim. У него нет своих условий. Он всегда принимает адрес и устанавливает доставку, помещая адрес в очередь транспорта. Конечно, роутер может не всегда работать, если в его конфигурации определены предварительные условия. Обычным предварительным условием, которое используется с accept, является check_local_user, как показано в более ранних примерах, например в следующем:

local_users:
  driver = accept
  check_local_user
  transport = local_delivery

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

7.1.1 Транспорт для accept

Для accept всегда должна быть указана опция transport, если только не установлена опция verify_only, в этом случае роутер используется только для проверки адресов. Транспорт не обязательно должен быть местным; можно использовать любой транспорт. Например, предположим, что все локальные пользователи имеют учетные записи в центральной почтовой системе, но их почта доставляется по протоколу SMTP на их индивидуальные рабочие станции. На центральном сервере роутер accept, такой как этот:

checklocals:
  driver = accept
  check_local_user
  transport = workstations

может использоваться с удаленным транспортом, например:

workstations:
  driver = smtp
  hosts = ${lookup{$local_part}cdb{/etc/workstations}{$value}fail}

где файл /etc/workstations содержит сопоставление имени пользователя с именем рабочей станции.

7.2 Роутер dnslookup

Роутер dnslookup использует DNS для поиска хостов, которые обрабатывают почту для домена адреса. Для этого роутера всегда должен быть установлен транспорт, если только не установлен параметр verify_only. Ранее мы показали простую конфигурацию dnslookup как:

notlocal:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp

Роутер dnslookup использует DNS в соответствии со стандартными правилами маршрутизации почты: сначала он ищет MX-записи для домена; если они найдены, они предоставляют список хостов. Если записей MX нет, роутер ищет записи адресов хоста, имя которого совпадает с именем домена.

Если роутер не может определить, есть ли в домене какие-либо записи MX (из-за тайм-аута или другого сбоя DNS), он не может продолжить работу, поскольку в этих обстоятельствах ему не разрешено продолжать поиск адресных записей. Когда это происходит, доставка откладывается, если не установлена общая опция pass_on_timeout (это описано в главе 6).

Роутер dnslookup имеет некоторые частные параметры (описанные ниже), но вряд ли они понадобятся в большинстве установок.

7.2.1 Оптимизация повторной маршрутизации

Адреса с одинаковыми доменами обычно направляются роутером dnslookup на один и тот же список хостов. Однако это нельзя предполагать, поскольку параметры и предварительные условия роутера могут относиться к локальной части адреса. По этой причине Exim маршрутизирует каждый адрес в сообщении независимо, даже если встречается адрес с тем же доменом, что и предыдущий.

DNS-серверы работают с кешем, поэтому повторные поиски DNS одного и того же домена обычно не требуют больших затрат, и в любом случае личные сообщения редко имеют более нескольких получателей. Однако, если вы запускаете списки рассылки со многими подписчиками в одном и том же домене и используете роутер dnslookup, который не зависит от локальных частей адресов, вы можете установить same_domain_copy_routing для обхода повторных запросов DNS, когда это возможно[1].

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

  1. Это невозможно, если установлены headers_add или headers_remove или если роутер «расширяет» домен.

7.2.2 Управление поиском DNS

Есть два параметра, которые контролируют способ поиска DNS. Параметр преобразователя RES_DEFNAMES установлен по умолчанию. Это заставляет резолвер квалифицировать домены, состоящие из одного компонента (то есть не содержащие точек), с доменом по умолчанию. Обычно это имя локального хоста минус его ведущий компонент. Так, например, на хосте с именем dictionary.book.example результатом поиска thesaurus будет поиск thesaurus.book.example. Обычно это полезно для групп хостов в одном и том же вышестоящем домене, поэтому это происходит по умолчанию. Однако при необходимости его можно отключить, задав для параметра qualify_single значение false.

Параметр преобразователя RES_DNSRCH не установлен по умолчанию, но его можно запросить, установив search_parents. В этом случае, если первоначальный поиск не удался, преобразователь ищет домен по умолчанию и его родительские домены. Продолжая предыдущий пример, результат поиска по файлу animal.farm с этой опцией заключается в том, что сначала он ищет его как данное, а если это не удается, то ищет файл animal.farm.book.example. По умолчанию этот параметр отключен, поскольку он вызывает проблемы в доменах с подстановочными записями MX[2]. Допустим следующая запись:

*.example.  MX  6  mail.example.

существует, и есть хост с именем a.book.example, у которого нет собственных записей MX. Что происходит, когда пользователь на каком-либо другом хосте в домене book.example отправляет почту someone@a.book? Если не удается найти MX-запись для a.book, если задано значение search_parents, резолвер переходит к попытке a.book.book.example, которая соответствует подстановочной MX-записи, но, скорее всего, будет совершенно неуместной.

  1. Подстановочные знаки MX полезны в основном для доменов на сайтах, не подключенных к IP. Поскольку их эффекты часто не соответствуют действительности, они редко встречаются.

7.2.3 Условия для записей MX

Два других параметра dnslookup влияют на то, что делается после поиска записей MX в DNS. Если установлен check_secondary_mx, роутер отклоняет запрос, если локальный хост не будет найден в списке хостов, полученном в результате поиска MX[3]. Это идентифицирует домены, для которых локальный хост является резервной копией MX, и поэтому может использоваться для обработки этих доменов каким-то особым образом. Он отличается от общего параметра self, который применяется только в том случае, если запись MX с наименьшим номером указывает на локальный хост.

Другая опция, mx_domains, может использоваться для того, чтобы запретить Exim искать записи адресов DNS для определенных доменов, когда записи MX не найдены.

Одна из проблем, связанная с распространением персональных компьютеров в Интернете, заключается в том, что на многих из них не работают агенты передачи сообщений (MTA), однако, если их доменные имена, зарегистрированные в DNS, появляются в адресах электронной почты, отправляющие агенты передачи сообщений обязаны попытаться доставить их, используя записи их DNS адресов. Отправляющий MTA обычно пытается в течение нескольких дней, прежде чем сдаться. Это может легко произойти, если MUA на рабочей станции настроен неправильно, так что он отправляет почту, содержащую собственное доменное имя в обратных адресах, вместо того, чтобы использовать домен, который ссылается на его почтовый сервер.

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

mx_domains = *.your.domain

на роутере dnslookup. Для любого домена, который соответствует mx_domains, Exim ищет только записи MX. Он не ищет адресные записи, когда нет записей MX. Это означает, что домены без записей MX немедленно возвращаются, а не повторяются.

  1. Локальный хост и любой хост с большими или равными значениями предпочтений MX затем удаляются из списка в соответствии с обычными правилами обработки MX.

7.2.4 Явное расширение поиска

Когда поиск хоста завершается неудачно, dnslookup можно настроить так, чтобы он пытался добавить определенные строки в конец доменного имени, используя параметр widen_domains. Это обеспечивает более контролируемый механизм расширения, чем search_parents, потому что вместо поиска во всех вложенных доменах используются только те расширения, которые вы укажете. Кроме того, поскольку он работает только после того, как поиск записи MX и адресной записи не удался, он позволяет избежать проблемы с записями MX с подстановочными знаками, упомянутой ранее. Например, предположим, что у нас есть:

widen_domains = cam.ac.example : ac.example

на хосте с именем users.mail.cam.ac.example, и пользователь на этом хосте отправляет почту на домен semreh.cam. Во-первых, dnslookup ищет записи MX и адресов для semreh.cam, а поскольку search_parents не установлен, резолвер не выполняет собственного расширения. Поскольку нет домена верхнего уровня с именем cam, поиск завершается ошибкой, поэтому dnslookup пытается найти semreh.cam.cam.ac.example, а затем semreh.cam.ac.example в результате настройки widen_domains, но никакого другого расширения не делается.

7.2.5 Перезапись заголовка

Когда сокращенное имя расширяется до его полной формы либо как часть обработки поиска, либо в результате опции widen_domains, все вхождения сокращенного имени в строках заголовка сообщения заменяются полным именем[4]. Это можно подавить, установив для rewrite_headers значение false, но этот параметр следует отключать только тогда, когда известно, что никакое сообщение никогда не будет отправлено за пределы среды, где аббревиатура имеет смысл.

  1. Когда запись MX просматривается в DNS и соответствует записи с подстановочными знаками, серверы имен обычно возвращают запись, содержащую найденное имя, что делает невозможным определение наличия подстановочного знака или его отсутствия. Однако недавно было замечено, что некоторые серверы имен возвращают саму запись с подстановочными знаками. Если имя, возвращенное поиском DNS, начинается со звездочки, Exim не использует его для перезаписи заголовка.

7.2.6 Обзор параметров dnslookup

Параметры, характерные для dnslookup, перечислены в этом разделе:

check_secondary_mx (Boolean, default = false)

Если этот параметр установлен, роутер отклоняет вызов, если локальный хост не будет найден в списке хостов, полученном при поиске MX.

mx_domains (domain list, default = unset)

Домен, соответствующий mx_domains, должен иметь запись MX, чтобы его можно было распознать.

qualify_single (Boolean, default = true)

Параметр резолвера, который заставляет его уточнять однокомпонентные имена с доменом по умолчанию (RES_DEFNAMES), устанавливается, если этот параметр имеет значение true.

rewrite_headers (Boolean, default = true)

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

same_domain_copy_routing (Boolean, default = false)

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

search_parents (Boolean, default = false)

Параметр резолвера, который заставляет его искать родительские домены (RES_DNSRCH), устанавливается, если этот параметр имеет значение true. Этот параметр отличается от параметра qualify_single тем, что он применяется к доменам, содержащим точки.

widen_domains (string list, default = unset)

Если поиск не удался и эта опция установлена, каждая из его строк по очереди добавляется в конец домена, и поиск повторяется. Обратите внимание, что параметры qualify_single и search_parents вызывают некоторое расширение внутри резолвера DNS.

7.3 Роутер ipliteral

На заре Интернета, до повсеместного развертывания DNS, доменные имена не всегда были доступны. По этой причине RFC 2821 и 2822 разрешают использовать IP литерал домена вместо имени домена в адресе электронной почты. Например:

A.User@[192.168.3.4]

Цель состоит в том, чтобы такой адрес вызывал доставку сообщения на хост с этим IP-адресом. В наши дни многие администраторы не готовы позволить конечным пользователям адресовать сообщения отдельным машинам, а растущее использование брандмауэров часто делает это невозможным. Однако, поскольку эта возможность все еще актуальна в RFC, Exim поддерживает ее, хотя опции, которые ее устанавливают, закомментированы в конфигурационном файле по умолчанию.

Роутер ipliteral не имеет собственных опций. Он просто проверяет, имеет ли домен в адресе формат литерала домена, и если да, то направляет адрес на транспорт, указанный в универсальной опции transport, передавая IP-адрес, на который должно быть отправлено сообщение. Если оказывается, что литерал домена ссылается на локальный хост, то, что происходит, определяется общей опцией self.

7.4 Роутер manualroute

Иногда вы точно знаете, как вы хотите направить определенный нелокальный домен. Например, клиентский хост в коммутируемом соединении обычно отправляет всю исходящую почту на один хост (часто называемый smart host) для дальнейшей передачи. Менее тривиальный пример — шлюз, обрабатывающий всю входящую почту в локальную сеть. В этом случае вы будете знать, что определенные домены должны быть перенаправлены на определенные хосты в вашей сети. Записи MX для этих доменов указывают на шлюз, поэтому требуется другое средство маршрутизации от шлюза.

Роутер manualroute существует для обработки такого рода маршрутизации. Он настроен со списком доменов, которые он должен обрабатывать, вместе с информацией о том, что с ними делать. На самом деле есть два способа сделать это: правила могут быть встроены в файл конфигурации или они могут быть сохранены в файле и доступны для каждого домена.

7.4.1 Встроенные правила маршрутизации

Встроенные правила задаются опцией route_list. Каждое правило состоит из трех частей, разделенных пробелом:

  1. (1) Шаблон, соответствующий доменам, которые должны обрабатываться правилом.

  2. (2) Список хостов. Это расширенный элемент, что означает, что он может содержать внутренние пробелы. В этом случае вы должны заключить список хостов в кавычки. Если при расширении списка хостов происходит принудительный сбой, роутер отказывается.

  3. (3) Параметр, определяющий способ поиска IP-адресов хостов.

Шаблон домена — единственный обязательный элемент правила. (Например, вам не нужен список хостов, если роутер отправляет адреса на локальный транспорт.) Шаблон имеет тот же формат, что и один элемент в списке доменов, что означает, что он расширяется перед использованием. Это может быть домен, который начинается с подстановочного знака, регулярного выражения или поиска в файле или базе данных (18.5).

Если шаблон в начале правила является элементом поиска, данные, которые были найдены, доступны в переменной $value во время развертывания списка хостов. Например, это правило:

route list = dbm;/etc/rdomains $value:backup.example

сопоставляет домены с помощью поиска DBM; данные из поиска используются в списке хостов с дополнительным именем резервного хоста.

Если шаблон в начале правила в route_list является регулярным выражением, числовые переменные $1, $2 и т. д. содержат любые захваченные подстроки во время раскрытия списка хостов. Такая настройка, как:

route_list = \N^(ab\d\d)\.example\N $1.mail.example

направляет почту для домена ab01.example на хост ab01.mail.example (например).

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

smarthost:
  driver = manualroute
  domains = !+local_domains
  transport = remote_smtp
  route_list = * smarthost.example.com

Одна звездочка в качестве шаблона домена соответствует всем доменам, поэтому этот роутер заставляет все сообщения, содержащие удаленные адреса, отправляться на хост smarthost.example.com. Альтернативным способом достижения того же результата является использование этого роутера:

smarthost:
  driver = manualroute
  transport = remote_smtp
  route_list = !+local_domains smarthost.example.com

В нынешнем виде эти две конфигурации работают совершенно одинаково. Однако существует потенциальная разница между тестированием доменов с использованием предварительного условия domains и тестированием доменов с route_list. В первом случае роутер пропускается, если домен не совпадает, и всегда работает следующий роутер. В последнем случае роутер отказывается, когда домен не совпадает, поэтому, если для параметра more задано значение false, роутеры больше не запускаются.

Вы можете указать, как роутер manualroute находит IP-адреса, включив одну из опций bydns или byname после списка хостов, как в следующем примере:

route_list = !+local_domains smarthost.example.com byname

Для bydns Exim выполняет собственный поиск DNS; для byname он вызывает системную функцию для поиска имен хостов[5]. Обычно он обращается к локальным файлам, таким как /etc/hosts, а также может выполнять поиск DNS. Что именно он делает, определяется локальной конфигурацией.

Если вы не укажете ни одну из этих опций, Exim сначала выполнит поиск в DNS. Если это выдает ошибку «хост не найден», Exim пытается использовать функцию системного поиска.

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

decline

Роутер отказывается, и адрес передается следующему роутеру, если в more не установлено значение false.

defer

Доставка отложена, но сообщение не заморожено.

fail

Роутер отказывается, но больше роутеры не используются, поэтому адрес не работает.

freeze

Доставка отложена, и сообщение заморожено. Это действие по умолчанию.

pass

Роутер передает адрес следующему роутеру или роутеру, определенному общей опцией pass_router. Этот параметр переопределяет значение false параметра more.

Параметр host_find_failed применяется только к определенному состоянию «does not exist» (не существует). Если при поиске хоста возникает временная ошибка, доставка откладывается, если не установлена общая опция pass_on_timeout.

  1. Когда поддерживается IPv6, используется getipnodebyname(); в противном случае используется функция gethostbyname().

7.4.2 Рандомизация порядка хостов

Когда список хостов в правиле маршрутизации содержит более одного хоста, они обычно пробуются в том порядке, в котором они перечислены. Однако, если вы установите опцию hosts_randomize, порядок будет изменяться случайным образом при каждом использовании роутера. Транспорт smtp имеет одноименную опцию, которая делает то же самое во время транспортировки.

7.4.3 Множественные встроенные правила маршрутизации

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

private_routes:
  driver = manualroute
  transport = remote_smtp
  route_list = domainl.example    host1.example; \
               *.domain2.example  host2.example: \
                                  host3.example bydns; \
               domain3.example    192.168.45.56

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

7.4.4 Поисковые правила маршрутизации

Иногда удобнее хранить информацию о маршрутизации вручную в файле или базе данных, а не включать ее в файл конфигурации. Если вы хотите сделать это, вы можете установить опцию route_data вместо route_list. Это расширенная строка; чаще всего расширение включает поиск с использованием домена в качестве ключа. Например:

private_routes:
  driver = manualroute
  transport = remote_smtp
  route_data = ${lookup{$domain}cdb{/etc/routes.cdb}}

Это указывает, что данные маршрутизации должны быть получены путем поиска домена в /etc/routes.cdb с использованием поиска cdb. Частичный поиск по одному ключу (16.5) может использоваться для того, чтобы набор доменов использовал одни и те же данные маршрутизации.

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

private_routes:
  driver = manualroute
  transport = remote_smtp
  route_data = ${lookup{$domain}partial-lsearch{/etc/routes}}

с файлом /etc/routes, содержащим:


domain1.example:    host1.example
*.domain2.example:  host2.example:host3.example  bydns
domain3 .example:   192.168.45.56

7.4.5 Маршрутизация до местного транспорта

Экземпляр manualroute, настроенный для перенаправления адресов на локальный транспорт, часто используется для обработки сообщений для удаленных узлов. Вместо того, чтобы оставлять их в очереди Exim, где они будут бесполезно повторяться, они доставляются в файлы, из которых их можно получить при подключении клиентского хоста.

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

route_append:
  driver = manualroute
  transport = batchsmtp_appendfile
  route_list = gated.domain.example

Этот роутер вызывает запуск транспорта batchsmtp_appendfile для адресов в домене gated.domain.example. Обычно вместо одного домена используется какой-то шаблон для сопоставления набора доменов, и для запуска транспорта должен быть указан пользователь. Полная конфигурация может содержать такой транспорт:

dialup_transport:
  driver = appendfile
  batch_max = 100
  use_bsmtp
  file = /var/dialups/$domain
  user = exim

и такой роутер:

route_dialup:
  driver = manualroute
  transport = dialup_transport
  route_list = *.dialup.example.com

Параметр batch_max в транспорте позволяет использовать до 100 получателей в одной копии сообщения, а параметр use_bsmtp требует сохранения конверта сообщения в формате batch (пакетного) SMTP (BSMTP) (9.2, 9.3.2).

Когда локальный транспорт используется таким образом, одно имя хоста может опционально присутствовать в каждом правиле маршрутизации. Оно передается транспорту в переменной $host и может использоваться, например, при построении имени файла. Следующий роутер соответствует двум наборам доменов; для каждого набора в $host появляется другое значение при запуске транспорта:

route_dialup:
  driver = manualroute
  transport = dialup_transport
  route_list = *.dialupl.example.com host1; \
               * dialup2.example.com host2

7.4.6 Использование manualroute для маршрутизации почты на UUCP

Вы можете использовать manualroute для маршрутизации почты непосредственно в программу UUCP. Вот пример одного из способов, которым это можно сделать. Транспорт pipe (9.5) используется для прямого запуска программы UUCP:

uucp:
  driver = pipe
  user = nobody
  command = /usr/local/bin/uux -r - $host!rmail $local_part
  return_fail_output = true

Этот транспорт заменяет значения $host и $local_part в команде:

/usr/local/bin/uux -r - $host!rmail $local_part

а затем запускает его как пользователя nobody. Сообщение передается команде на стандартный ввод. Если команда не удалась, любой вывод, который она производит, возвращается отправителю сообщения. Соответствующий роутер:

uucphost:
  transport = uucp
  driver = manualroute
  route_data = ${lookup{$domain}lsearch{ /usr/local/exim/uucphosts}}

Файл /usr/local/exim/uucphosts содержит такие записи:

darksite.ethereal.example: darksite

Когда роутер обрабатывает адрес someone@darksite.ethereal.example, он передает адрес транспорту uucp, устанавливая $host в строку darksite.

7.4.7 Использование manualroute на почтовом концентраторе

Почтовый концентратор (mail hub) — это узел, который получает почту для ряда доменов (обычно, но не обязательно, через записи MX в DNS) и доставляет ее, используя собственный механизм частной маршрутизации. Часто конечные пункты назначения находятся за брандмауэром, при этом почтовый концентратор является единственной машиной, которая может подключаться к машинам как внутри, так и за пределами брандмауэра. Роутер manualroute на концентраторе можно настроить для обработки входящей почты следующим образом:

through_firewall:
  driver = manualroute
  transport = remote_smtp
  route_data = ${lookup{$domain}lsearch{/internal/host/routes}}

Если имеется только небольшое количество доменов, маршрутизацию можно указать встроенной, используя параметр route_list, но для большего числа поиском управлять проще. Если сам файл маршрутизации становится большим (скажем, более 20–30 записей), рекомендуется преобразовать его в один из индексированных форматов (DBM или cdb) для повышения производительности. В этом примере файл, содержащий внутреннюю маршрутизацию, может содержать такие строки:

abc.ref.example: ml.ref.example:m2.ref.example

DNS будет настроен с записью MX для abc.ref.example, указывающей на почтовый концентратор, который будет пересылать почту для этого домена на один из двух указанных хостов. Они будут опробованы по порядку, потому что hosts_randomize не задан.

Если доменные имена на самом деле являются именами машин, на которые почтовый концентратор должен отправлять почту, конфигурация может быть упрощена. Например:

hub_route:
  driver = manualroute
  transport = remote_smtp
  route_list = *.rhodes.example  $domain

Эта конфигурация направляет домены, оканчивающиеся на .rhodes.example, на хосты с тем же именем. Аналогичный подход может быть использован, если имя хоста может быть получено из имени домена с помощью любого из преобразований, доступных в механизме раскрытия строк Exim'а.

7.4.8 Изменение транспорта

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

route_append:
  driver = manualroute
  route_list = \
    *.gated.domain1  $domain  batch_appendfile; \
    *.gated.domain2  ${lookup{$domain}dbm{/etc/domain2/hosts}\
                       {$value}fail} batch_pipe

Первое правило отправляет любой адрес, который соответствует транспорту batch_appendfile, передавая домен в переменной $host, что мало что дает (поскольку он также находится в $domain). Второе правило выполняет поиск файла, чтобы найти значение, которое нужно передать в $host транспорту batch_pipe, указывая, что роутер должен отказаться от обработки адреса, если поиск не удался.

7.4.9 Оптимизация повторной маршрутизации

Роутер manualroute похож на dnslookup тем, что адреса с одними и теми же доменами обычно перенаправляются на один и тот же список хостов. Exim не может предполагать, что это всегда так, потому что опции и предварительные условия роутера могут ссылаться на локальную часть адреса. Поэтому каждый адрес обычно маршрутизируется независимо. Однако у manualroute есть опция с именем same_domain_copy_routing, которая копирует маршрутизацию для одного адреса на все остальные в том же сообщении, которые имеют тот же домен, так же, как опция с тем же именем делает для dnslookup. Это может дать некоторое улучшение производительности для сообщений с большим количеством получателей.

7.4.10 Обзор параметров manualroute

Параметры, специфичные для manualroute, перечислены в этом разделе:

host_find_failed (string, default = freeze)

Этот параметр определяет, что происходит, когда хост, который пытается найти manualroute (поскольку адрес был специально перенаправлен на него), не существует. Параметр может быть установлен в одно из следующих значений:

decline
defer
fail
freeze
pass

Эта опция применяется только к определенному состоянию «does not exist» (не существует); если при поиске хоста возникает временная ошибка, доставка откладывается, если не установлена общая опция pass_on_timeout.

hosts_randomize (Boolean, default = false)

Если hosts_randomize имеет значение false, то порядок, в котором перечислены хосты, сохраняется как предпочтительный порядок доставки сообщения; если это правда, список перемешивается в случайном порядке каждый раз, когда он используется.

route_data (string, default = unset)

Если этот параметр установлен, он расширяется и должен содержать список хостов и дополнительные параметры. Если расширение принудительно завершится ошибкой или результатом будет пустая строка, роутер отклонит запрос.

route_list (string list, default = unset)

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

same_domain_copy_routing (Boolean, default = false)

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

7.5 Роутер queryprogram

Роутер queryprogram маршрутизирует адрес, выполняя внешнюю команду и воздействуя на ее вывод. Работа команды состоит в том, чтобы принимать решения о маршрутизации адреса. Это не команда для доставки сообщения; эта функция доступна через транспорт pipe.

Запуск внешней команды — дорогостоящий способ маршрутизации, поскольку для каждого адреса, обрабатываемого таким образом, требуется новый процесс, в котором выполняется команда. Если команда на самом деле является сценарием на интерпретируемом языке, таком как Perl или Python, накладные расходы еще больше.

Роутер queryprogram предназначен в основном для использования в малонагруженных системах или для проведения экспериментов. Однако, если можно ограничить этот роутер несколькими малоиспользуемыми адресами с помощью подходящих предварительных условий, он может регулярно использоваться даже в загруженных системах.

7.5.1 Команда queryprogram

Команда, которая должна быть запущена, указывается в параметре command. Эта строка расширена; после расширения он должен начинаться с абсолютного пути к команде. Ошибка раскрытия вызывает отсрочку доставки и замораживание сообщения. Команда запускается в подпроцессе непосредственно из Exim, без использования промежуточной оболочки. Если вам нужна оболочка, вы должны указать ее явно; поскольку это вставляет еще один процесс, увеличивает стоимость и не рекомендуется.

7.5.2 Запуск команды queryprogram

Команда запускается в отдельном процессе под идентификатором uid и gid, указанными пользователем command_user и command_group соответственно. Если последний не установлен, используется gid, связанный с пользователем, как в этом примере:

pgm_router:
  driver = queryprogram
  transport = remote_smtp
  command = /usr/exim/pgmrouter $local_part $domain
  command_user = mail

Каталог, который становится текущим во время выполнения команды, определяется current_directory. По умолчанию это корневой каталог.

7.5.3 Результат команды queryprogram

Роутер queryprogram имеет параметр timeout, который по умолчанию равен 1h (1 часу). Если команда не завершается за это время, ее группа процессов уничтожается, а доставка откладывается. Нулевое время не указывает ограничения по времени, но это не рекомендуется. Доставка также откладывается, если команда завершается с ошибкой (то есть, если ее код возврата не равен нулю).

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

ACCEPT

Маршрутизация выполнена успешно; остальная часть строки указывает, что делать.

DECLINE

Маршрутизация не удалась; предлагать адрес следующему роутеру, если не установлено значение no_more.

DEFER

В настоящее время маршрутизация не может быть завершена; Попробуйте позже.

FAIL

Маршрутизация не удалась; не передавайте адрес никаким другим роутерам.

FREEZE

То же, что и DEFER, за исключением того, что сообщение заморожено.

PASS

Передайте адрес следующему роутеру или роутеру, указанному параметром pass_router, отменяется с помощью no_more.

REDIRECT

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

Если первое слово не ACCEPT или REDIRECT, оставшаяся часть строки представляет собой сообщение об ошибке, объясняющее, что пошло не так. Например:

DECLINE cannot route to unseen.discworld.example

Если первое слово — ACCEPT, остальная часть строки состоит из ряда значений данных с ключами, как показано ниже (здесь строка разделена, чтобы уместиться на странице):

ACCEPT TRANSPORT=<transport-name> HOSTS=<host-list>
       LOOKUP=byname|bydns DATA=<text>

Поля могут отображаться в любом порядке, и все они являются необязательными. Кавычки необходимы, если данные для какого-либо поля содержат пробелы. Если TRANSPORT не указан, используется транспорт, указанный в универсальной опции transport. Список хостов и тип поиска необходимы только в том случае, если транспорт является транспортом smtp, который сам не имеет списка хостов. Типы поиска работают точно так же, как в роутере manualroute, описанном ранее в этой главе. Например:

ACCEPT TRANSPORT=remote_smtp HOSTS=gate.star.example LOOKUP=bydns

вызывает отправку сообщения с использованием транспорта remote_smtp на хост gate.star.example, чей IP-адрес ищется с использованием записей адресов DNS. Если хост оказывается локальным хостом, происходящее контролируется общей опцией self.

Значение поля DATA, если оно присутствует, помещается в переменную $address_data. Например:

ACCEPT HOSTS=x1.y.example:x2.y.example DATA="rule one"

направляет адрес на транспорт по умолчанию, используя список хостов, который содержит два хоста, IP-адреса которых ищутся путем вызова системной функции поиска хоста. Когда транспорт запускается, первое строковое правило находится в $address_data.

7.5.4 Обзор опций программы запроса

В этом разделе приведены параметры, характерные для программы запросов:

command (string, default = unset)

Этот параметр должен быть установлен и после раскрытия должен начинаться с символа косой черты. Он указывает команду, которая должна быть запущена.

command_group (string, default = unset)

Эта опция указывает gid, который будет установлен при выполнении команды. Если он начинается с цифры, он интерпретируется как числовое значение gid. В противном случае он ищется в данных пароля.

command_user (string, default = unset)

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

current_directory (string, default = unset)

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

timeout (time, default = 1h)

Если команда не завершается в течение периода тайм-аута, ее группа процессов уничтожается, а доставка откладывается. Значение времени, равное нулю, указывает на отсутствие тайм-аута.

7.6 Роутер redirect

Роутер redirect обрабатывает несколько видов перенаправления адресов и является самым сложным из роутеров Exim. Обычно он использует локальную часть или полный адрес для поиска списка новых адресов или инструкций по обработке. Чаще всего он используется для разрешения псевдонимов локальных частей из центрального файла псевдонимов (обычно называемого /etc/aliases) и для обработки личных файлов .forward пользователей, но у него есть много других потенциальных применений. Входящий адрес можно перенаправить следующими способами:

Для роутеров redirect нельзя задавать общий параметр transport. Однако есть некоторые частные параметры, определяющие транспорты для доставки в файлы и каналы, а также для создания автоматических ответов.

7.6.1 Получение данных перенаправления

Роутер redirect работает, интерпретируя текстовую строку, полученную либо путем расширения содержимого параметра data, либо путем чтения всего содержимого файла, имя которого указано в параметре file. Эти два варианта взаимоисключающие. Ранее (3.9) мы обсуждали этот роутер для обработки системных псевдонимов:

system_aliases:
  driver = redirect
  data = ${lookup{$local_part}lsearch{/etc/aliases}}

а позже в том же разделе для обработки файлов .forward пользователей используется следующий роутер:

userforward:
  driver = redirect
  check_local_user
  file = $home/.forward

Когда используется data, если раскрытие принудительно завершилось ошибкой или если оно дает пустую строку, роутер отклоняет запрос. При использовании file роутер отклоняет его, если файл не существует, или если он пуст или состоит только из комментариев.

Exim не ограничен одним файлом псевдонимов; у вас может быть столько роутеров redirect, сколько вам нужно, каждый из которых ищет другой набор данных. Однако в такой последовательности роутеров нет ничего особенного; как только любой из них принимает адрес, обработка этого адреса прекращается.

Если caseful_local_part не установлен, локальная часть принудительно переводится в нижний регистр при работе роутера. Таким образом, ключи в файлах псевдонимов обычно должны быть в нижнем регистре. Для файлов с линейным поиском в этом фактически нет необходимости, поскольку поиск выполняется независимо от регистра, но это важно для других форм поиска.

7.6.2 Включение домена в поиск псевдонимов

Все предыдущие примеры используют только локальную часть при поиске псевдонимов, но ничто не мешает вам искать полный адрес, если вы хотите, используя роутер, такой как следующий:

system_aliases:
  driver = redirect
  data = ${lookup{$local_part@$domain}1lsearch{/etc/aliases.full}}

Это позволяет хранить псевдонимы для нескольких доменов в одном файле, например:

postmaster@domain1:  jill@domain1
postmaster@domain2:  jack@domain2

Если вы хотите смешать два типа в одном и том же файле, вы должны определить расширение, которое выполняет второй поиск, если первый терпит неудачу:

system_aliases:
  driver = redirect
  data = ${lookup{$local_part@$domain}1search{/etc/aliases.full}\
         ${$value}\
         {${lookup{$local_part }lsearch{/etc/aliases.full}}}}

7.6.3 Пересылка файлов и проверка адреса

Обычно для роутеров redirect, которые обрабатывают файлы .forward пользователей, для параметра verify устанавливается значение false. Этому есть две причины:

7.6.4 Дочерние адреса в сообщениях об ошибках

Если Exim генерирует сообщение о задержке или возврате для адреса, сгенерированного перенаправлением, он обычно заключает в кавычки дочерний адрес в сообщении, как и для адреса верхнего уровня. Если вы хотите, чтобы информация о перенаправлении оставалась конфиденциальной, вы можете установить для параметра hide_child_in_errmsg значение true. Когда это сделано, сообщения о задержке и возврате относятся к «адресу, сгенерированному из...». Конечно, это относится только к сообщениям, сгенерированным локально. Если сообщение перенаправляется на другой хост, его отказ вполне может указывать сгенерированный адрес.

7.6.5 Интерпретация данных перенаправления

Данные, которые обрабатывает redirect, могут представлять собой простой список элементов, разделенных запятыми или символами новой строки. Однако также возможно потребовать, чтобы Exim обрабатывал данные как фильтр Exim, что означает, что они интерпретируются более сложным способом. В частности, могут быть наложены условия на выполнение поставок. Если на роутере установлена опция allow_filter, и первая строка данных начинается с:

# Exim filter

он интерпретируется как фильтр, а не как простой список адресов. Использование фильтров описано в главе 10. Мы обсудим типы элементов, которые могут появиться в списке перенаправления без фильтров, далее в этой главе.

7.6.6 Дублирующиеся адреса

Exim удаляет повторяющиеся адреса из списка адресов, на которые он доставляет, чтобы доставлять только одну копию на каждый уникальный адрес. Это также относится к любым элементам в списках перенаправления. Например, если сообщение адресовано и postmaster, и hostmaster, и они оба имеют псевдонимы одному и тому же человеку, будет доставлена одна копия сообщения. Эта оптимизация не применяется к доставке, направляемой в каналы, при условии, что непосредственные родительские адреса отличаются. Таким образом, если два разных получателя одного и того же сообщения настроили свои файлы .forward для передачи одной и той же команде[6], выполняются две разные доставки. В файле псевдонима такая схема, как:

localparti1:  |/some/command
localpart2:  |/some/command

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

pipe:       |/some/command
localpart1: pipe
localpart2: pipe

выполняет только одну доставку, потому что промежуточные локальные части (непосредственные родительские команды каналов) идентичны.

  1. /usr/bin/vacation — типичный пример.

7.6.7 Элементы в списках перенаправления

Элементы в списке перенаправления без фильтрации разделяются символами новой строки или запятыми[7]. Пустые элементы игнорируются. Если элемент полностью заключен в двойные кавычки, он удаляется. В противном случае двойные кавычки сохраняются, поскольку некоторые формы почтового адреса требуют их использования (но никогда не заключают весь адрес). В следующем описании «элемент» относится к тому, что остается после удаления любых окружающих двойных кавычек.

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

7.6.8 Включение входящего адреса в список переадресации

Перенаправление адреса на себя безопасно, потому что у Exim есть общий механизм для избежания петель маршрутизации. Роутер автоматически пропускается, если какой-либо предок текущего адреса идентичен ему и был обработан этим роутером. Таким образом, пользователь с логином spgr, который хочет сохранить копию почты, а также переслать ее куда-то еще, может настроить файл .forward, например:

spqr, spqr@st.elsewhere.example

не вызывая зацикливания, потому что при второй обработке spgr роутер redirect пропускается. Обратная косая черта перед локальной частью без домена разрешена. Например:

\spqr, spqr@st.elsewhere.example

Обратная косая черта разрешена для совместимости с другими MTA, но не является обязательной для предотвращения зацикливания. Однако наличие или отсутствие обратной косой черты может иметь значение, когда роутер обрабатывает более одного домена. Если задан параметр qualify_preserve_domain, локальная часть без домена определяется доменом входящего адреса независимо от того, предшествует ли ей обратная косая черта или нет. Если значение qualify_preserve_domain не задано, локальная часть без начального обратного слэша уточняется значением qualify_recipient Exim, тогда как часть с начальным обратным слэшем уточняется входящим доменом.

7.6.9 Плохое взаимодействие между алиасингом и пересылкой

Следует соблюдать осторожность, если существуют псевдонимы для локальных пользователей, у которых могут быть файлы .forward. Например, если системный файл псевдонима содержит:

Sam.Reman: spqr

затем:

Sam.Reman, spqr@reme.elsewhere.example

в файле .forward пользователя spqr не удается получить входящее сообщение, адресованное Sam.Reman. Входящая локальная часть преобразуется в spqr с помощью алиасинга, а затем файл .forward превращает ее обратно в Sam.Reman. Когда этот «внучатый» адрес обрабатывается, системный роутер псевдонимов пропускается, чтобы разорвать цикл, поскольку ранее он обрабатывал Sam.Reman. Это приводит к тому, что Sam.Reman передается последующим роутерам, которые, вероятно, не могут его обработать. Файл .forward действительно должен содержать:

spqr, spqr@reme.elsewhere.example

но поскольку это очень распространенная ошибка пользователя, существует опция check_ancestor, чтобы обойти ее (7.6.13).

7.6.10 Неадресные элементы в списках перенаправления

В списке перенаправления могут появляться следующие типы неадресных элементов:

Пути

Элемент перенаправления интерпретируется как путь, если он начинается с / и не анализируется как действительный адрес RFC 2822, включающий домен. Например:

/home/world/minbari

рассматривается как имя файла, но:

/s=molari/o=babylon/@x400gateway.example

рассматривается как адрес. Если сгенерированный путь — /dev/null, доставка по нему игнорируется на высоком уровне, и в записи журнала вместо имени транспорта отображается **bypassed**. Это позволяет избежать необходимости указывать пользователя и группу, которые необходимы для подлинной доставки файла. Если имя файла отличается от /dev/null, либо роутер, либо транспорт должны указать пользователя и группу, под которыми должна выполняться доставка.

Команды канала

Элемент перенаправления рассматривается как конвейерная команда, если он начинается с | и не анализируется как действительный адрес RFC 2822, который включает домен. Одинарные или двойные кавычки можно использовать для заключения отдельных аргументов команды канала; для одинарных кавычек интерпретация escape-последовательностей не выполняется. Если команда содержит символ запятой, необходимо поместить весь элемент в двойные кавычки, поскольку элементы заканчиваются запятыми. Например:

"|/some/command ready, steady,go"

Однако не цитируйте только команду. Такой элемент, как:

|"/some/command ready, steady,go"

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

Включение подсписка из файла

Если элемент перенаправления принимает форму:

:include:<pathname>

список дополнительных элементов берется из данного файла и включается в этот момент. Если это первый элемент в списке перенаправления, взятом из файла с линейным поиском, необходимо использовать двоеточие для завершения имени псевдонима, иначе первое двоеточие воспринимается как терминатор псевдонима, и элемент не распознается. Этот пример некорректен:

eximlist  :include:/etc/eximlist

Это должно быть написано так:

eximlist:  :include:/etc/eximlist

Использование :include: можно отключить, установив forbid_include на роутере.

Отбрасывание адреса

Иногда вы хотите выбросить почту на определенный адрес. Расширение параметра данных до пустой строки не работает, потому что это приводит к отклонению роутера. Вместо этого специальный элемент:

:blackhole:

делает то, что следует из его названия. Доставка не выполняется, и сообщение об ошибке не генерируется. Это имеет тот же эффект, что и перенаправление на /dev/null, но может быть отключено независимо с помощью параметра forbid_blackhole.

Принудительная отсрочка или сбой доставки

Попытка доставить конкретный адрес может быть отложена или вынуждена провалиться путем перенаправления адреса на:

:defer:

или

:fail:

Однако вам нужно установить allow_defer или allow_fail соответственно, чтобы разрешить использование этих элементов. Если адрес перенаправляется на :defer:, он остается в очереди, чтобы следующая попытка доставки могла произойти позже, но если он перенаправляется на :fail:, он немедленно возвращается. Если адрес откладывается слишком долго, он в конечном итоге не будет работать, потому что применяются обычные правила повторных попыток.

Когда список содержит один из этих двух элементов, все предыдущие элементы в списке игнорируются. Текст, следующий за :defer: или :fail:, помещается в сообщение об ошибке, связанное с ошибкой. Запятая не завершает текст ошибки, в отличие от новой строки[8]. Например, файл псевдонима может содержать следующие строки:

x.employee:  :fail:  Gone away, no forwarding address
j.caesar:    :defer: Mailbox is being moved today

Если вы хотите включить переменную информацию в сообщение, вы можете организовать ее расширение, настроив параметр данных следующим образом:

data = ${expand:${lookup{$local_part}lsearch{/etc/aliases}}}

Это позволит вам использовать записи псевдонимов, такие как:

j-caesar:   :defer: $local_part’s mailbox is being moved today

В случае адреса, который проверяется для команд SMTP RCPT или VRFY, текст включается в ответ об ошибке SMTP, который использует код 451 для отсрочки и 550 для сбоя. Если :fail: встречается во время доставки сообщения, текст включается в рикошетное сообщение, которое генерирует Exim; текст для :defer: появляется в строке журнала для отсрочки, но не используется в других целях.

Обход параметров поиска по умолчанию

Иногда полезно использовать тип поиска по умолчанию для псевдонимов, как в этом примере роутера, который мы использовали ранее для виртуальных доменов:

virtuals:
  driver = aliasfile
  domains = cdb;/etc/virtuals
  data = ${lookup{$local_part}lsearch*{/etc/$domain.aliases}}
  no_more

Однако может возникнуть необходимость в исключениях по умолчанию. С ними можно справиться, перенаправив их на:

:unknown:

Например:

*:          postmaster@virt3.example
postmaster: pat@dom5.example
jill:       jkre@dom4.example
jack:       :unknown:

Это отличается от :fail: тем, что вызывает отклонение redirect, поэтому адрес предлагается следующему роутеру, тогда как :fail: заставляет маршрутизацию немедленно завершиться ошибкой.

  1. Новые строки обычно не присутствуют в расширениях псевдонимов. В поиске 1search они удаляются как часть процесса продолжения, но могут существовать в других видах поиска и во включенных файлах.

7.6.11 Включение и отключение определенных функций

Роутер redirect имеет ряд опций для управления тем, какие типы специальных элементов перенаправления могут появляться в данных перенаправления (7.6.19). Некоторые элементы разрешены по умолчанию, но могут быть отключены параметрами, имена которых начинаются с forbid_ (например, forbid_include). Другие специальные элементы запрещены по умолчанию, но могут быть включены параметрами, имена которых начинаются с allow_ (например, allow_fail).

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

7.6.12 Проверка атрибутов файла

Когда опция file используется для указания данных перенаправления, Exim может быть сконфигурирован для выполнения проверок режима и владельца файла. Если они неверны, доставка откладывается.

Опция modemask указывает биты режима, которые не должны быть установлены. Например:

modemask = 007

указывает, что ни один из «other» битов доступа не должен быть установлен. Значение по умолчанию — 022, что означает, что ни группа, ни «other» бит записи не разрешены.

Если check_local_user установлен, Exim проверяет, что файл принадлежит локальному пользователю и, если значение modemask разрешает бит групповой записи, что владелец группы является первичной группой пользователя. Дополнительные владельцы и группы могут быть указаны с помощью параметров owners и owngroups. Если check_local_user не установлена, это единственные разрешенные владельцы. Например:

owners = mail : root
owngroups = mail : root

Проверку владельца и группы можно отключить, установив check_owner и check_group в false соответственно.

7.6.13 Проверка предков

Антициклическое правило Exim по умолчанию пропускает роутер только тогда, когда предок, который был обработан роутером, совпадает с текущим адресом. Более широкое условие для пропуска роутера redirect предоставляется параметром repeat_use. По умолчанию он имеет значение true, но если установлено значение false, роутер пропускается для дочернего адреса с любым предком, который был маршрутизирован тем же роутером. Этот тест выполняется до того, как будут проверены какие-либо общие предварительные условия.

Ранее (6.2.1) мы показали использование универсальной опции redirect_router, чтобы избежать ненужного запуска роутера redirect во второй раз, передав новый адрес непосредственно следующему роутеру. Установка repeat_use в false имеет аналогичный эффект, но в этом случае новый адрес сначала имеет возможность быть обработанным более ранними роутерами.

В другом более раннем обсуждении (7.6.9) было указано, что файл системного псевдонима, содержащий:

Sam.Reman: spqr

в сочетании с файлом .forward для spgr, содержащим:

Sam.Reman, spqr@reme.elsewhere.example

не сработал, потому что псевдоним Sam.Reman можно было превратить в имя пользователя spqr только один раз. Это настолько распространенная ошибка, что существует возможность ее обойти.

Когда установлен check_ancestor, если сгенерированный адрес совпадает с любым предком текущего адреса, он не используется; вместо этого используется текущий адрес. В этом примере проблемы, если на роутере, который обрабатывает файлы .forward, установлена check_ancestor, это не позволяет роутеру превратить spgr обратно в Sam.Reman. Конфигурация по умолчанию устанавливает check_ancestor на своем роутере userforward, чтобы он с большей вероятностью «делал то, что имеет в виду пользователь».

7.6.14 Транспорты для каналов и файлов

Когда роутер redirect генерирует доставку непосредственно в каналы или файлы, необходимо определить транспорты, которые должны использоваться, установив pipe_transport и file_transport соответственно. Например, конфигурация Exim по умолчанию обрабатывает системные псевдонимы посредством этого роутера:

system_aliases:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup{$local_part}lsearch{/etc/aliases}}
# user = exim
  file_transport = address_file
  pipe_transport = address_pipe

Наличие последних двух опций означает, что такие псевдонимы, как:


fileit:    /some/file
pipeit:    |/some/command

обрабатываются транспортами address_file и address_pipe соответственно. Либо роутер, либо транспорты также должны установить пользователя, под которым должна выполняться доставка. Конфигурация по умолчанию показывает предложение в виде закомментированной настройки пользовательской опции.

7.6.15 Перезапись сгенерированных адресов

Сгенерированные адреса обычно перезаписываются в соответствии с настроенными правилами перезаписи (см. главу 15), но если вы не хотите, чтобы это происходило, вы можете установить rewrite в false в конфигурации роутера.

7.6.16 Одноразовое перенаправление

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

Если одно сообщение доставляется одному подписчику очень долго, а в это время в список добавляются новые адреса, новые подписчики получают копию старого сообщения, даже если оно датировано до их подписки. Этого можно избежать, установив на роутере опцию one_time, которая расширяет список рассылки. Это изменяет поведение Exim так, что после временной ошибки доставки он добавляет недоставленные «дочерние» адреса в список получателей верхнего уровня и помечает исходный адрес как доставленный. Таким образом, последующие изменения в списке рассылки больше не влияют на это сообщение.

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

Установка one_time возможна только тогда, когда в данных перенаправления нет доставок каналов или файлов, потому что их невозможно превратить в адреса верхнего уровня. По этой причине forbid_pipe и forbid_file принудительно должны быть истинными, когда установлено one_time.

7.6.17 Синтаксические ошибки в данных перенаправления

Если Exim обнаруживает синтаксическую ошибку в списке перенаправления, он откладывает доставку оригинального адреса. Это самое безопасное действие. Однако в некоторых случаях это может оказаться нецелесообразным. Например, если список рассылки поддерживается каким-либо автоматическим процессом подписки, вы не хотите, чтобы опечатка одного подписчика задержала доставку остальной части списка.

Если установлено значение skip_syntax_errors, некорректно сформированный элемент пропускается, а запись записывается в основной журнал. Если syntax_errors_to также установлен, на содержащийся в нем адрес отправляется почтовое сообщение с подробной информацией об ошибочном адресе(ах). Часто будет уместно установить syntax_errors_to на тот же адрес, что и для errors_to (адрес для ошибок доставки). Если задан syntax_errors_text, его содержимое разворачивается и помещается в заголовок сообщения об ошибке.

Пока существует хотя бы один действительный адрес, роутер работает успешно. Однако, если все адреса в списке перенаправления пропущены из-за синтаксических ошибок, роутер отказывается. Для списка рассылки это, вероятно, означает, что сообщение возвращается, но отсутствие действительных адресов в списке рассылки встречается редко. Для пользовательского файла .forward сообщение обычно доставляется в почтовый ящик пользователя, что является безопасным действием.

Если при интерпретации фильтра Exim установлен параметр skip_syntax_errors, любая синтаксическая ошибка в фильтре приводит к отказу от фильтрации без выполнения каких-либо действий (за исключением отправки сообщения, если установлен syntax_errors_to). То есть роутер отказывается.

7.6.18 Сообщение пользователям о поврежденных файлах .forward

Пользователи часто вносят синтаксические ошибки в свои файлы .forward. Они также часто проверяют их со своих учетных записей, обычно несколько раз, когда наблюдают, что сообщения не доходят. Используя skip_syntax_errors, можно доставлять сообщения об ошибках в почтовые ящики таких пользователей, тем самым снижая нагрузку на постмастера. Во-первых, вы должны организовать способ доставки сообщений в обход файлов .forward пользователей. Роутер, который это делает, описан ранее (6.1.1):

real_users:
  driver = accept
  check_local_user
  local_part_prefix = real-
  transport = local_delivery

Установка local_part_prefix означает, что этот роутер пропускается, если локальная часть не имеет префикса real-. Если он определен перед роутером, который обрабатывает файлы .forward, он выбирает такие локальные части и устанавливает локальную доставку, тем самым минуя любую пересылку, которая может существовать.

При этом syntax_errors_to можно использовать на роутере userforward для отправки сообщения в папку inbox пользователя при наличии синтаксической ошибки в файле .forward. Поскольку мы хотим включить в текстовую строку символы новой строки, они заключаются в двойные кавычки. Когда значение опции Exim заключено в кавычки, обратная косая черта внутри кавычек интерпретируется как escape-символ. Это обеспечивает средства кодирования непечатаемых символов. В частности, \n становится символом новой строки:

userforwarda:
  driver = redirect
  check local_user
  file = $home/.forward
  skip_syntax_errors
  syntax_errors_to = real-$local_part@sdomain
  syntax_errors_text = "\
    This is an automatically generated message. An error has\n\
    been found in your .forward file. Details of the error are\n\
    reported below. While this error persists, you will receive\n\
    a copy of this message for every message that is addressed\n\
    to you. If your .forward file is a filter file, or if it is\n\
    a non-filter file that contains no valid forwarding\n\
    addresses, a copy of each incoming message will be put in\n\
    your mailbox. If a non-filter file contains at least one\n\
    valid forwarding address, forwarding to the valid addresses\n\
    will happen, and those will be the only deliveries that\n\
    occur."

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

^real-([^@]+)@ $1@$domain h

Средства перезаписи адресов Exim описаны в главе 15; простое правило, показанное здесь, перезаписывает адреса в строках заголовка (оставляя конверты нетронутыми), удаляя real- из начала локальной части.

7.6.19 Обзор опций перенаправления

Параметры, специфичные для перенаправления, перечислены в этом разделе:

allow_defer (Boolean, default = false)

Установка этого параметра позволяет использовать :defer: в данных перенаправления без фильтрации.

allow_fail (Boolean, default = false)

Установка этого параметра позволяет использовать :fail: в данных перенаправления без фильтрации и команду fail в файле фильтра.

allow_filter (Boolean, default = false)

Если эта опция установлена, и данные перенаправления начинаются со строки:

# Exim filter

она интерпретируется как набор команд фильтрации, а не как список элементов перенаправления. Подробности синтаксиса и семантики файлов фильтров описаны в главе 10.

allow_freeze (Boolean, default = false)

Установка этого параметра позволяет использовать команду freeze в фильтре. Это не то, что вы обычно позволяете делать обычным пользователям.

check_ancestor (Boolean, default = false)

Эта опция связана с обработкой сгенерированных адресов, которые совпадают с некоторыми адресами в списке предков текущего адреса. Когда он установлен, если сгенерированный адрес совпадает с любым предком, он заменяется копией текущего адреса.

check_group (Boolean, default = see description)

Когда используется опция file, группа-владелец файла проверяется только при установке этой опции. Если установлен check_local_user, группа пользователя по умолчанию разрешена; в противном случае группа должна быть одной из перечисленных в опции owngroups. По умолчанию для этой опции установлено значение true, если установлена check_local_user и опция modemask разрешает бит групповой записи, или если установлена опция owngroups. В противном случае установлено значение false.

check_owner (Boolean, default = see description)

Когда используется опция file, владелец файла проверяется только при установке этой опции. Если установлен check_local_user, локальный пользователь является действительным владельцем; в противном случае владелец должен быть одним из перечисленных в опции owners. Значение по умолчанию для этого параметра равно true, если установлен check_local_user или owner. В противном случае оно ложно.

data (string, default = unset)

Эта опция должна быть установлена, если file не установлен. Его значение раскрывается и используется как список элементов пересылки или инструкций по фильтрации. Если расширение принудительно завершается ошибкой или если результат расширения пуст, роутер отказывается.

directory_transport (string, default = unset)

Роутер redirect настраивает доставку в каталог, когда имя пути, заканчивающееся косой чертой, указывается в качестве нового «адреса». Используемый транспорт определяется этой опцией, которая после расширения должна быть именем сконфигурированного транспорта.

file (string, default = unset)

Этот параметр указывает имя файла, содержащего данные перенаправления. Он взаимоисключающий с опцией data (то есть может быть установлен только один из них). Строка расширяется перед использованием; если расширение принудительно завершается ошибкой, роутер отказывается. Другие ошибки расширения приводят к отсрочке доставки. Результатом успешного расширения должен быть абсолютный путь. Весь файл читается и используется как данные перенаправления. Если файл не существует, или пуст, или содержит только комментарии, роутер отклоняется[9].

file_transport (string, default = unset)

Роутер redirect настраивает доставку в файл, когда в качестве нового «адреса» указывается путь, не заканчивающийся косой чертой. Используемый транспорт определяется этой опцией, которая после расширения должна быть именем сконфигурированного транспорта.

forbid_blackhole (Boolean, default = false)

Если этот параметр установлен, использование :blackhole: в списке перенаправления отключено.

forbid_file (Boolean, default = false)

Если этот параметр установлен, роутер может не генерировать элемент, указывающий доставку в локальный файл или каталог. Если он попытается это сделать, произойдет сбой доставки.

forbid_filter_existstest (Boolean, default = false)

Если этот параметр установлен, расширения строк в файлах фильтров не могут использовать условие exists.

forbid_filter_logwrite (Boolean, default = false)

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

forbid_filter_lookup (Boolean, default = false)

Если этот параметр установлен, расширения строк в файлах фильтров не могут использовать элементы lookup.

forbid_filter_perl (Boolean, default = false)

Эта опция доступна, только если Exim собран со встроенной поддержкой Perl. Если это так, расширения строк в файлах фильтров не позволяют использовать встроенную поддержку Perl.

forbid_filter_readfile (Boolean, default = false)

Если этот параметр установлен, расширения строк в файлах фильтров не позволяют использовать элемент readfile.

forbid_filter_reply (Boolean, default = false)

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

forbid_filter_run (Boolean, default = false)

Если этот параметр установлен, расширения строк в файлах фильтров не позволяют использовать элемент run.

forbid_include (Boolean, default = false)

Если этот параметр установлен, использование специального элемента :include: не разрешено.

forbid_pipe (Boolean, default = false)

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

hide_child_in_errmsg (Boolean, default = false)

Если эта опция true, это запрещает Exim цитировать дочерний адрес в сообщениях о задержке или отказе.

ignore_eacces (Boolean, default = false)

Если этот параметр установлен и попытка открыть файл, указанный в параметре file, приводит к ошибке EACCES (отказано в доступе), redirect ведет себя так, как будто файл не существует, и отклоняется.

ignore_enotdir (Boolean, default = false)

Если этот параметр установлен и попытка открыть файл, указанный в параметре file, приводит к ошибке ENOTDIR (что-то в пути не является каталогом), redirect ведет себя так, как будто файл не существует, и отклоняется.

include_directory (string, default = unset)

Если этот параметр установлен, пути к любым элементам :include: в списке перенаправления должны начинаться с этого каталога.

modemask (octal-integer, default = 022)

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

one_time (Boolean, default = false)

Если задан one_time, и какие-либо адреса, сгенерированные роутером, не могут быть доставлены с первой попытки, ошибочные адреса добавляются в сообщение как адреса «верхнего уровня» (top level), а родительский адрес, который их сгенерировал, помечается как «доставленный» (delivered). Таким образом, перенаправление не происходит снова при следующей попытке доставки.

owners (string list, default = unset)

Это указывает список разрешенных владельцев для файла, указанного в опции file. Этот список является дополнением к локальному пользователю, когда установлен check_local_user.

owngroups (string list, default = unset)

Это указывает список разрешенных групп для файла, указанного в опции file. Этот список является дополнением к основной группе локального пользователя, когда установлен check_local_user.

pipe_transport (string, default = unset)

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

qualify_preserve_domain (Boolean, default = false)

Если этот параметр установлен и генерируется неквалифицированный адрес (без домена), он уточняется доменом входящего адреса вместо значения qualify_recipient.

repeat_use (Boolean, default = true)

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

reply_transport (string, default = unset)

Роутер redirect настраивает доставку на транспорт autoreply, когда в файле фильтра используется команда mail или vacation. Используемый транспорт определяется этой опцией, которая после расширения должна быть именем сконфигурированного транспорта.

rewrite (Boolean, default = true)

Если для этой опции установлено значение false, адреса, сгенерированные роутером, не подлежат перезаписи адресов. В противном случае они рассматриваются как новые адреса и к ним применяются правила перезаписи (см. главу 15).

skip_syntax_errors (Boolean, default = false)

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

syntax_errors_text (string, default = unset)

См. syntax_errors_to.

syntax_errors_to (string, default = unset)

Этот параметр применяется, только если установлен параметр skip_syntax_errors. Если какие-либо адреса пропущены из-за синтаксических ошибок, на адрес, указанный в syntax_errors_to,, отправляется почтовое сообщение с подробной информацией об ошибочных адресах. Если задан syntax_errors_text, его содержимое расширяется и помещается в заголовок сообщения об ошибке.

  1. Если попытка открыть файл не удалась с ошибкой «does not exist», Exim запускает проверку содержащего его каталога. Если каталог не существует, доставка откладывается. Это может произойти, когда файлы .forward пользователей являются каталогами, смонтированными через NFS, и возникает проблема с монтированием.

Глава 8
Общие параметры, применимые ко всем видам транспортов

Транспорты — это модули в Exim, которые осуществляют реальную доставку сообщений. Несколько примеров показаны в предыдущих главах; в следующей главе подробно рассматривается каждый транспорт. В этой главе мы рассмотрим общие параметры, применимые ко всем транспортам, хотя некоторые из них используются почти исключительно для локальных транспортов.

Единственная необходимая опция — это driver, который определяет, какой транспорт настраивается. Для транспорта smtp это часто единственный вариант, который вам нужно предоставить. В конфигурационном файле Exim по умолчанию это настроено следующим образом:

remote_smtp:
  driver = smtp

В этом случае ожидается, что хост, на который должно быть доставлено сообщение, будет предоставлен роутером (например, в результате поиска DNS в dnslookup), а все остальные параметры транспорта установлены по умолчанию.

8.1 Среда для запуска транспорта

Транспорты запускаются в подпроцессах основного процесса доставки Exim. Перед запуском кода транспорта Exim устанавливает определенный uid и gid для подпроцесса[1]. Exim также устанавливает текущий каталог файла; для некоторых транспортов также важна настройка домашнего каталога. Например, перед запуском appendfile для записи в почтовый ящик локального пользователя, Exim обычно переключается на uid и gid этого пользователя и устанавливает текущий каталог в домашний каталог пользователя. Это гарантирует, что локальные доставки выполняются «от имени пользователя», так что доступ к файлам и программам контролируется обычным механизмом защиты операционной системы.

Значения, используемые для uid, gid и каталогов, могут поступать из разных мест. Во многих случаях роутер, который обрабатывает адрес, связывает настройки с этим адресом. Однако значения также могут быть заданы в собственной конфигурации транспорта, и они переопределяют все, что приходит с адресом.

  1. Это предполагает обычную установку Exim, где Exim является привилегированным в силу того, что он является двоичным файлом setuid. В нетрадиционных конфигурациях это не всегда верно (19.1.3).

8.1.1 Uid'ы и gid'ы

Хотя вы можете изменить uid и gid для удаленных транспортов, вам не следует этого делать, если вы действительно не понимаете, что делаете. По умолчанию эти транспорты работают под uid и gid Exim'а. Это дает им доступ к базам данных подсказок Exim; они необходимы для проверки информации о повторных попытках для удаленных хостов. Следовательно, то, что следует в этом разделе, относится главным образом к местным транспортам.

Если установлен общий параметр group, он переопределяет любую группу, которая может быть установлена в адресе, даже если user не установлен. Это позволяет, например, запускать локальную доставку почты под uid получателя, но в специальной группе, используя такой транспорт:

group_delivery:
  driver = appendfile
  file = /var/spool/mail/$local_part
  group = mail

Возможно, вы захотите сделать это, если все файлы почтовых ящиков созданы заранее и настроены так, чтобы группа mail могла писать в них (9.4.1). В этом примере предполагается, что роутер установил значение для пользователя (что будет иметь место, если это роутер с установленным значением check_local_user), но он переопределяет любые настройки группы, которые мог сделать роутер.

Точно так же, если для транспорта задана общая опция user, ее значение переопределяет то, что уже установлено для адреса. Если user не числовой и group не задана, используется GID, связанный с пользователем. Если user числовой, необходимо установить group.

Каждый процесс Unix работает под определенным uid и gid, но, кроме того, с ним может быть связан ряд других групп. Они хранятся в списке доступа дополнительных групп (supplementary group access list) и дают привилегии процессов, связанные с этими группами. Например, у пользователей, которые являются членами групп staff, network и admin, все эти группы настроены в их процессах входа в систему. Настройка списка дополнительных групп использует ресурсы и обычно не требуется для доставки электронной почты, поэтому Exim не делает этого по умолчанию.

Однако для доставки через канал вы иногда можете захотеть, чтобы Exim установил список дополнительных групп. Общий параметр initgroups позволяет запросить это, но только в том случае, если user также указан в транспорте. Если пользователь связан с адресом роутером, значение параметра initgroups берется из конфигурации роутера.

8.1.2 Текущий и домашний каталоги

Общие параметры current_directory и home_directory можно использовать для указания текущего и домашнего каталогов для транспорта, переопределяя любые значения, установленные роутером. Домашний каталог помещается в $home при расширении частных параметров транспорта. Если ни роутер, ни транспорт не устанавливают текущий каталог, Exim использует значение домашнего каталога, если он установлен. В противном случае он устанавливает текущий каталог в корневой каталог перед запуском локального транспорта.

8.1.3 Переменные расширения, полученные из адреса

Обычно локальная доставка обрабатывает один адрес и во время доставки устанавливает такие переменные, как $domain и $local_part. Это также имеет место, когда параметр max_rcpt установлен равным единице на транспорте smtp. Однако локальные транспорты могут быть настроены для одновременной обработки более чем одного адреса (например, при записи в пакетном формате SMTP для дальнейшей передачи каким-либо другим способом), а транспорт smtp обычно работает с несколькими адресами, когда они перенаправляются на одни и те же хосты.

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

8.2 Отладка транспорта

Опция debug_print работает так же, как одноименная опция роутера. Его единственная цель — помочь отлаживать конфигурации Exim. Когда Exim запускается с включенной отладкой (20.10), строковое значение debug_print расширяется и добавляется к выходным данным отладки при запуске транспорта. Это средство можно использовать для проверки того, что значения некоторых переменных соответствуют вашим представлениям о них. Например, с этой конфигурацией:

remote_smtp:
  driver = smtp
  debug_print = self_hostname = $self_hostname

значение переменной $self_hostname будет добавлено к выходным данным отладки во время запуска транспорта.

8.3 Транспортировка только части сообщения

Обычно задачей транспорта является копирование всего сообщения. Однако для особых целей можно переносить только строки заголовка или только основную часть. Это достигается установкой headers_only или body_only соответственно, но одновременно может быть установлен только один из них. Например, если копии сообщений берутся для какого-то анализа заголовков, headers_only уменьшают объем записываемых данных.

8.4 Управление размером сообщения

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

Заметьте, что есть основная опция конфигурации для ограничения размера всех сообщений, обрабатываемых Exim, имя которой также message_size_limit. Чтобы иметь какой-либо эффект, локальная настройка транспорта, естественно, должна быть меньше глобального предела.

8.5 Добавление и удаление строк заголовков

В процессе доставки транспорт можно настроить для добавления или удаления строк заголовков. Обычно требуется добавить три конкретных заголовка, когда сообщение доставляется в локальный почтовый ящик, и для их запроса предусмотрены отдельные параметры. Они добавляются в самом начале сообщения перед строками заголовка Received:.

Delivery-date

Опция delivery_date_add запрашивает добавление строки заголовка в форме:

Delivery-date: Tue, 29 Feb 2000 16:14:32 +0000

Это записывает дату и время доставки сообщения.

Envelope-to

Параметр envelope_to_add запрашивает добавление строки заголовка вида:

Envelope-to: alex@troy.example

Это записывает исходный адрес получателя конверта, который вызвал доставку. Это может быть адрес, который не отображается в строках заголовка To: или Cc:. В случаях, когда одна пакетная доставка осуществляется для нескольких адресов получателей, в списке может быть более одного адреса. Однако это необычный случай, требующий установки опции batch_max (9.2); обычная локальная доставка предназначена только для одного получателя (даже если у сообщения может быть несколько получателей).

Return-path

Параметр return_path_add запрашивает добавление строки заголовка в форме:

Return-path: <phil@thesa.example>

Это записывает адрес отправителя из конверта сообщения. Для сообщения о возврате добавленный заголовок:

Return-path: <>

RFC 2821 гласит:

Когда SMTP-сервер доставки выполняет «final delivery» (окончательную доставку) сообщения, он вставляет строку обратного пути в начало почтовых данных.

Поэтому строка заголовка Return-path: не должна присутствовать во входящих сообщениях. Два других добавленных заголовка не стандартизированы (хотя они используются некоторыми другими MTA) и также не должны присутствовать во входящих сообщениях. Поэтому Exim удаляет все три из этих заголовков из получаемых им сообщений, чтобы сообщения, которые уже были доставлены, можно было легко повторно отправить[2].

  1. Этого поведения можно избежать, установив для параметров конфигурации return_path_remove, delivery_date_remove и envelope_to_remove значение false соответственно, но обычно этого делать не следует.

Другие строки заголовка

Другие строки заголовка могут быть добавлены к сообщению с помощью опции headers_add. Если это установлено, его содержимое добавляется в конец раздела заголовка во время транспортировки сообщения. Строки заголовков, добавляемые транспортом, следуют за любыми, которые добавляются к адресу роутерами. Можно добавить несколько строк, закодировав \n. Например:

headers_add = X-added: this is a header added at S$tod_log\n\
              X-added: this is another

Exim не проверяет синтаксис этих добавленных строк заголовков; вы должны убедиться, что они соответствуют RFC 2822. В конце ставится новая строка, если ее нет.

Исходные строки заголовков (те, которые были получены вместе с сообщением) можно удалить, установив headers_remove для списка их имен. Например:

headers_remove = return-receipt-to : acknowledge-to

Имена заголовков даются без завершающих двоеточий (двоеточие в примере является символом-разделителем списка). Эти строки заголовков удаляются перед добавлением любых новых, указанных в headers_add, поэтому отдельные строки заголовков можно удалить и заменить чем-то другим. Однако невозможно ссылаться на старое содержимое при определении новой строки.

И headers_add, и headers_remove расширяются перед использованием. Если результатом является пустая строка или раскрытие принудительно завершится ошибкой, никаких действий не предпринимается. Другие виды сбоев (например, ошибка синтаксиса раскрытия) вызывают отсрочку доставки.

Добавление и удаление строки заголовка также можно указать в конфигурациях роутера (6.3.2), и в этом случае они связаны с адресами, которые обрабатывают эти драйверы. Если один роутер связан только с одним транспортом, не имеет значения, указываете ли вы изменения строки заголовка на транспорте или на роутере. Однако в конфигурациях, где несколько роутеров используют один и тот же транспорт, очевидно, что изменение строки заголовка имеет значение.

Во время транспортировки список удаления из адреса объединяется со списком удаления из транспорта до удаления соответствующих строк заголовка. Затем делаются дополнения из адреса и из транспорта. Невозможно использовать опцию транспорта для удаления строк заголовков, добавленных роутером.

8.6 Перезапись адресов в строках заголовка

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

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

Чтобы преодолеть эту трудность, существует общая опция транспорта, называемая headers_rewrite. Она позволяет перезаписывать адреса в строках заголовка во время транспортировки (то есть, когда сообщение копируется в пункт назначения). Это означает, что перезапись затрагивает только те копии сообщения, которые проходят через транспорт, где установлен параметр. Копии, которые доставляются другими транспортами, не затрагиваются.

Это подробно обсуждается в главе 15, так что мы не будем больше говорить об этом здесь.

8.7 Изменение обратного пути

«Return path» (обратный путь) — это другое название адреса отправителя, содержащегося в конверте сообщения. Если для транспорта установлена опция return_path, его содержимое расширяется, а результат заменяет существующий обратный путь. Расширение может ссылаться на существующее значение с помощью переменной $return_path. Если расширение принудительно завершается неудачно, замены не происходит; если это не удается по другой причине, доставка откладывается.

В основном эта опция используется для реализации Variable Envelope Return Paths (переменных путей возврата конверта) (VERP) для сообщений из списков рассылки[3]. Проблема с доставкой по спискам рассылки, которая отбрасывается, заключается в том, что часто бывает трудно обнаружить, какой первоначальный адрес получателя спровоцировал рикошет.

Предположим, кто-то подписывается на список рассылки, используя адрес J.Smith99@alma.mater.example, который перенаправляется на jan@plc.co.example. Все идет хорошо, пока она не меняет работу, ее учетная запись электронной почты на pic.co.example не удаляется, и она не забывает обновить переадресацию, потому что в списке не так много трафика. Когда сообщение публикуется в следующий раз, менеджер списка получает рикошет о невозможности доставки на адрес jan@plc.co.example, которого нет в списке. Узнать, какой исходный адрес вызвал отказ, можно путем анализа строк заголовка Received:, которые были добавлены к сообщению; они иногда содержат адрес получателя в копиях сообщения, у которых есть только один получатель, например:


Received: from [192.168.247.11] (helo=mail.list.example ident=exim)
        by draco.alma.mater.example with esmtp (Exim 4.10)
        id 124h50-0005wn-00
        for J.Smith99@alma.mater.example;
        Wed, 20 Jun 2001 10:46:44 +0100

Однако, если сообщение имеет более одного получателя, их адреса не должны помещаться в заголовок Received:, поскольку это может привести к раскрытию конфиденциальности[4]. В любом случае диагностика адреса проблемы требует много времени и не может быть легко автоматизирована.

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

somelist-request@list.example

После настройки VERP копия сообщения, отправляемого на адрес J.Smith99@alma.mater.example, имеет (например) этого отправителя конверта:

somelist-request=J.Smith99%alma.mater.example@list.example

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

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

VERP может поддерживаться в Exim с помощью опции транспорта return_path для перезаписи отправителя конверта во время транспортировки. Например, можно использовать следующее:

return_path = \
  ${if match {$return_path}{^(.+?)-request@list.example\$}\
  {$1-request=$local_part%$domain@list.example}fail}

Это приводит к перезаписи обратного пути (отправителя конверта), если локальная часть исходного обратного пути заканчивается на -request и доменом является list.example. Перезапись вставляет локальную часть и домен получателя в обратный путь в формате, использованном в предыдущем примере.

Чтобы это работало, вы должны организовать передачу исходящих сообщений, в обратных путях которых есть -request, транспорту только с одним получателем, потому что $local_part и $domain не установлены для сообщений с несколькими получателями. Локальные транспорты по умолчанию работают с одним получателем за раз, но для транспорта smtp вам необходимо установить:

max_rcept = 1

в опциях транспорта. Если ваш хост не обрабатывает много другого трафика, вы можете просто установить это на обычном транспорте remote_smtp, но если вы хотите получить преимущество от нескольких получателей в других случаях, вам нужно настроить два транспорта smtp, например:

normal_smtp:
  driver = smtp

verp_smtp:
  driver = smtp
  max_rept = 1
  return_path = {${local_part:$return_path}=\
  $local_part%$domain@list.example}fail}

а затем направить сообщения списка рассылки на второй из них, используя такой роутер:

verp_router:
  driver = dnslookup
  transport verp_smtp
  condition = ${if match {$return_path}
              {^(.+?)-request@list.example\$}{yes}{no}}

Настройка return_path на транспорте может быть проще, потому что строгая проверка выполняется роутером, поэтому он отправляет на транспорт только адреса с суффиксом -request.

Конечно, если вы начнете рассылать сообщения с таким обратным путем, вы также должны настроить Exim, чтобы он принимал сообщения о возврате, которые возвращаются на эти адреса. Как правило, это делается путем установки опции local_part_prefix или local_part_suffix для подходящего роутера (6.1.1).

Накладные расходы, возникающие при использовании VERP, зависят от размера сообщения, количества адресов получателей, которые сопоставляются с одним и тем же удаленным хостом, и скорости соединения, по которому отправляется сообщение. Если несколько адресов разрешаются на один и тот же хост, а соединение медленное, отправка отдельной копии сообщения для каждого адреса может занять значительно больше времени, чем отправка одной копии многим получателям (для которых нельзя использовать VERP).

  1. См. http://cr.yp.to/proto/verp.txt.

  2. Обычно подписчикам списка рассылки не показываются адреса других подписчиков.

8.8 Транспортные фильтры

Если вы хотите внести более обширные изменения, чем это можно сделать с помощью только что описанных опций, или если вы хотите изменить тело сообщения во время его транспортировки, вы можете использовать транспортный фильтр (transport filter). Это «фильтр» в смысле слова Unix; это не имеет отношения к средствам фильтрации сообщений Exim, которые происходят во время маршрутизации.

Параметр transport_filter указывает команду, которая запускается во время транспорта. Вместо того, чтобы копировать сообщение прямо в место назначения, Exim передает его команде на своем стандартном вводе. Стандартный вывод команды записывается в место назначения. Это дорогостоящая вещь, и она усугубляется тем, что процесс доставки Exim не может одновременно читать и записывать в процесс фильтрации, так как это может привести к взаимоблокировке. Следовательно, Exim должен создать третий процесс для записи, как показано на рисунке 8-1.

Рисунок 8-1: Транспортная фильтрация

Всё сообщение, включая строки заголовка, передается фильтру перед выполнением какой-либо обработки, специфичной для транспорта (например, преобразования \n в \r\n и экранирования строк, начинающихся с точки для SMTP). Фильтр может выполнять любые преобразования, которые он хочет, но, конечно, он должен следить за тем, чтобы не нарушить синтаксис RFC 2822. Проблема может возникнуть, если фильтр увеличивает размер сообщения, отправляемого по SMTP-каналу. Если принимающий SMTP-сервер указал поддержку параметра SIZE, Exim отправит размер сообщения в начале SMTP-сессии. Если то, что на самом деле отправлено, значительно больше, сервер может отклонить сообщение. Вы можете обойти это, установив параметр size_addition в транспорте smtp, чтобы разрешить добавление к сообщению или полностью отключить использование SIZE.

Значение transport_filter — это командная строка для программы, которая запускается в процессе, запускаемом Exim. Эта программа запускается напрямую, а не под оболочкой. Строка анализируется Exim так же, как командная строка для транспорта pipe: Exim разбивает ее на аргументы и расширяет каждый аргумент отдельно. Это означает, что расширение не может случайно изменить количество аргументов. Специальный аргумент $pipe_addresses заменяется рядом аргументов, по одному для каждого адреса, применимого к этой доставке[5].

Переменные $host (содержащие имя удаленного хоста) и $host_address (содержащие IP-адрес удаленного хоста) доступны для удаленных транспортов. Например:

transport_filter = /some/directory/transport-filter.pl \
  Shost $host_address $sender_address $pipe_addresses

Процесс фильтрации запускается под тем же uid и gid, что и обычная доставка. Для удаленной доставки это uid и gid Exim.

  1. $pipe_addresses — не идеальное имя для этой функции здесь, но, поскольку оно уже было реализовано для транспорта pipe, казалось разумным не менять его.

8.8.1 Некоторые варианты использования транспортных фильтров

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

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

encrypt_smtp:
  driver = smtp
  transport_filter = /usr/mail/encrypt/body $sender_address

могут быть выбраны роутерами для определенных адресов назначения, а значение $sender_address может использоваться для управления шифрованием.

Транспортный фильтр можно также использовать как способ прохождения сообщений через программу, которая проверяет их на наличие спама перед доставкой. Программа проверки добавляет строки заголовков к подозрительным сообщениям; впоследствии они могут быть распознаны файлом фильтра получателя или пользовательским агентом[6]. Это альтернативный подход к тому, что было описано ранее (5.10.1), но большая часть конфигурации аналогична.

Чтобы работать таким образом, Exim должен доставлять входящие сообщения самому себе через фильтр, но таким образом, чтобы отфильтрованные сообщения распознавались и доставлялись нормально, когда они приходят во второй раз. Кроме того, конверты сообщений должны быть сохранены во время этого процесса. Для этого вам нужно запустить доставку как доверенный пользователь (trusted user) (19.3). Таким пользователям разрешено подделывать адреса отправителей и устанавливать другие данные сообщения, например протокол, по которому сообщение было получено.

Предположим, вы создали пользователя с именем spamkill для использования программы проверки. Чтобы сделать его доверенным, добавьте его в опцию trust_users в основной части конфигурации Exim. Например:

trusted_users = spamkill : majordom : ...

Первым роутером в конфигурации может быть

spamtest:
  driver = accept
  condition = ${if eq {$received_protocol}{spam-scanned}{no}{yes}}
  transport = spamcheck

Это направляет все адреса на транспорт spamcheck, за исключением случаев, когда значением $received_prococol является spam-scanned. Поскольку протокол приема может устанавливать только доверенный пользователь, можно быть уверенным, что проверку невозможно обойти. Вы можете, конечно, добавить дополнительные условия, такие как проверка того, что сообщение не было создано локально.

Транспорт spamcheck доставляет сообщения обратно в Exim через канал, используя средство проверки спама в качестве фильтра. Транспорт устанавливает полученный протокол, чтобы предотвратить повторное сканирование сообщений. Важными параметрами в конфигурации транспорта являются следующие[7]:

spamcheck:
  driver = pipe
  batch_max = 1000
  use_bsmtp
  command = /usr/sbin/exim -oMr spam-scanned -bS
  transport_filter = /usr/bin/spamc -s 500000
  user = spamkill

Параметр batch_max в транспорте позволяет использовать до 1000 получателей в одной копии сообщения, а параметр use_bsmtp требует, чтобы конверт сообщения сохранялся в формате BSMTP (9.2, 9.3.2).

Опция -oMr в команде Exim указывает, что полученный протокол проверен на наличие спама, а опция -bS указывает Exim ожидать пакетного SMTP на его стандартном вводе.

  1. Подробную информацию об одной такой программе см. на http://spamassassin.org/.

  2. Если вы скопируете этот пример, вы также должны учитывать настройки других параметров pipe, таких как home_directory, current_directory, log_output и return_fail_output.

8.9 Теневые транспорты

Теневой транспорт — это тот, который запускается в дополнение к основному транспорту для адреса. Теневые транспорты могут использоваться для различных целей, включая хранение более подробной информации журнала, чем обычно предоставляет Exim, и реализацию политик автоматического подтверждения на основе заголовков сообщений. Локальный транспорт может установить shadow_transport на имя другого локального транспорта. Теневые удаленные транспорты не поддерживаются.

Если теневой транспорт определен и доставка на основной транспорт успешна, сообщение также передается на теневой транспорт. Однако это происходит только в том случае, если shadow_condition не установлен или его расширение не приводит к сбою принудительного расширения, пустой строке или одной из строк 0, no или false. Это позволяет ограничить теневое копирование сообщениями, соответствующими определенным условиям.

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

Предусмотрен только один уровень затенения; параметр shadow_transport игнорируется на любом транспорте, когда он работает как тень. Опции, связанные с выводом из каналов, также игнорируются. Строка журнала для успешной доставки имеет следующий элемент, добавленный в конце:

ST=<<shadow transport name>>

Если теневой транспорт не удается, сообщение об ошибке помещается в круглые скобки.

8.10 Управление повторными попытками

Если есть временная ошибка для удаленной доставки, Exim вычисляет время повторной попытки, основываясь на идентификаторе удаленного хоста. С другой стороны, для временных ошибок при локальной доставке обычно используется весь адрес (включая локальную часть и домен). В главе 12 подробно обсуждаются временные ошибки и повторные попытки.

Типичным примером временной ошибки локальной доставки является сбой, вызванный переполнением почтового ящика. Exim периодически повторяет эту доставку в соответствии со своими правилами повтора, но это не должно влиять на доставку в другие почтовые ящики. Например, если почтовый ящик для tweedledum@example.com заполнен, Exim задерживает попытки доставки на этот адрес, но это не влияет на доставку на tweedledee@example.com, потому что информация о повторных попытках относится ко всему адресу.

Однако иногда вы можете захотеть обработать временный локальный сбой доставки как сбой, связанный с доменом, а не с конкретной локальной частью. Например, предположим, что вы храните всю почту для некоторого домена в локальном файле, ожидая соединения с коммутируемого хоста. Когда это временное пространство для хранения заполняется, вы хотите отложить все попытки доставки для домена, а не только для первого неудачного адреса. Вы можете сделать это, установив для параметра retry_use_local_part значение false.

8.11 Краткое описание общих вариантов транспортировки

В этом разделе приведены параметры, общие для всех транспортов:

body_only (Boolean, default = false)

Если этот параметр установлен, заголовки сообщения не переносятся. Опция взаимоисключающая с headers_only. Если параметр используется с транспортом appendfile или pipe, следует проверить настройки message_prefix и message_suffix, поскольку эта опция не подавляет их автоматически.

current_directory (string, default = unset)

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

debug_print (string, default = unset)

Если этот параметр установлен и включена отладка, строка расширяется и включается в выходные данные отладки при запуске транспорта.

delivery_date_add (Boolean, default = false)

Если этот параметр установлен, к сообщению добавляется строка заголовка Delivery-date:. Это дает фактическое время доставки.

driver (string, default = unset)

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

envelope_to_add (Boolean, default = false)

Если этот параметр установлен, к сообщению добавляется строка заголовка Envelope-to:. Это дает исходный адрес во входящем конверте, который вызвал эту доставку. Может присутствовать более одного адреса, если значение batch_max больше единицы или если несколько исходных адресов были изменены или переадресованы на один и тот же конечный адрес.

group (string, default = see description)

Эта опция указывает gid для запуска транспортного процесса, переопределяя любое значение, предоставляемое роутером, а также переопределяя любое значение, связанное с user. Если группа не указана иным образом, используется группа Exim.

headers_add (string, default = unset)

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

headers_only (Boolean, default = false)

Если этот параметр установлен, тело сообщения не транспортируется. Этот параметр взаимоисключающийся с body_only.

headers_remove (string, default = unset)

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

headers_rewrite (string, default = unset)

Эта опция позволяет перезаписывать адреса в строках заголовка во время транспортировки (то есть, когда сообщение копируется в место назначения). Значением headers_rewrite является список правил перезаписи, разделенных двоеточиями, как описано в главе 15.

home_directory (string, default = unset)

Это указывает настройку домашнего каталога для транспорта, перекрывая любое значение, которое могло быть установлено роутером.

initgroups (Boolean, default = false)

Если этот параметр установлен и uid для доставки предоставляется транспортом, функция initgroups() вызывается при запуске транспорта, чтобы убедиться, что настроены любые дополнительные группы, связанные с uid.

message_size_limit (integer, default = 0)

Этот параметр управляет размером сообщений, проходящих через транспорт. Если его значение больше нуля и размер сообщения превышает лимит, доставка завершается неудачно.

retry_use_local_part (Boolean, default = see description)

По умолчанию для этого параметра установлено значение true для локальных транспортов и false для удаленных. Когда при доставке происходит временный сбой, информация о повторной попытке указывается в домене плюс локальная часть, если этот параметр установлен. Если оно ложно, используется только домен[8].

return_path (string, default = unset)

Если этот параметр установлен, строка расширяется во время транспортировки и заменяет существующее значение обратного пути (отправитель конверта). Расширение может ссылаться на существующее значение через $return_path. Если расширение принудительно завершается неудачно, замены не происходит; если он терпит неудачу по другой причине, Exim записывает в свой журнал паники и немедленно завершает работу.

return_path_add (Boolean, default = false)

Если этот параметр установлен, к сообщению добавляется строка заголовка Return-path:. Обычно это используется только для транспортов, выполняющих окончательную доставку в почтовый ящик. Если почтовый ящик представляет собой один файл в формате Berkeley, обратный путь обычно доступен в строке-разделителе, но обычно он не отображается MUA, и поэтому у пользователя нет легкого доступа к нему. Другие форматы почтовых ящиков могут вообще не записывать обратный путь.

shadow_condition (string, default = unset)

См. shadow_transport.

shadow_transport (string, default = unset)

Локальный транспорт может установить параметр shadow_transport на имя другого локального транспорта. Всякий раз, когда доставка на основной транспорт успешна, и либо shadow_condition не установлен, либо его расширение не приводит к сбою принудительного расширения, либо пустая строка, либо одна из строк 0, или no, или false, сообщение также передается теневому транспорту.

transport_filter (string, default = unset)

Этот параметр устанавливает процесс фильтрации (в смысле оболочки Unix) для сообщений во время транспортировки. Когда сообщение собирается быть выписанным, в отдельном процессе запускается команда, указанная в transport_filter, и все сообщение, включая заголовки, передается ей на стандартный ввод. Стандартный вывод фильтра считывается и записывается в место назначения сообщения.

user (string, default = see description)

Эта опция указывает пользователя, под чьим uid должен выполняться процесс доставки, переопределяя любой uid, который мог быть установлен роутером. Если пользователь указан как имя, uid ищется из данных пароля, и связанная группа берется как значение gid, которое будет использоваться, если опция группы не установлена.

  1. В текущей реализации эта опция полезна только для локальных транспортов. Ее значение не проверяется удаленными транспортами.

Глава 9
Транспорт

В этой главе мы опишем функции и опции каждого из доступных транспортов. Существует только один транспорт для доставки сообщений на удаленные хосты, который называется smtp. Другие виды транспорта осуществляют местные доставки, а именно:

appendfile

Транспорт, который записывает сообщение в локальный файл.

autoreply

Транспорт, генерирующий автоматический ответ на сообщение.

lmtp

Транспорт, который передает сообщение внешнему процессу по каналу с использованием протокола LMTP.

pipe

Транспорт, который передает сообщение непосредственно внешнему процессу через канал.

autoreply на самом деле является псевдотранспортом, потому что фактически никуда не доставляет сообщение; вместо этого он генерирует новое исходящее сообщение (автоматический ответ). Он включен в число транспортов, потому что его метод работы и конфигурация одинаковы, и он может включать в ответ копию исходного сообщения.

9.1 Транспорт smtp

Поскольку транспорт smtp является единственным удаленным транспортом, он используется для всех доставок на удаленные хосты. Однако при необходимости можно настроить несколько экземпляров с разными параметрами. Наиболее распространенная конфигурация очень проста, обычно это:

remote smtp:
  driver = smtp

В этом примере все параметры, управляющие параметрами SMTP-соединения, принимают значения по умолчанию. Список удаленных хостов создается роутером, который обрабатывает адрес, и передается на транспорт вместе с адресами, которые должны быть доставлены.

Однако использование транспорта smtp не ограничивается роутерами, устанавливающими списки хостов. Для этого у самого транспорта есть опции для указания хостов. Кроме того, характеристики SMTP-соединения можно изменять различными способами. В результате вариантов этого транспорта очень много.

9.1.1 Управление несколькими адресами

Протокол SMTP позволяет передавать любое количество адресов получателей в конверте сообщения с помощью нескольких команд RCPT[1]. Exim обычно делает это, когда сообщение имеет более одного адреса, маршрутизируемого на один и тот же хост, с учетом следующих опций:

Действие по умолчанию, заключающееся в использовании нескольких адресов в одной передаче, рекомендовано RFC 2821. Для личных сообщений (которые редко имеют более двух получателей), которые проходят через хорошо подключенные части современного Интернета, это, вероятно, не имеет большого значения. Однако для списков рассылки с тысячами подписчиков могут возникнуть значительные затраты, если каждому из них будет отправлена отдельная копия, особенно если многие адреса находятся в одном и том же домене.

  1. На практике следует избегать более сотни получателей, так как это может привести к проблемам с некоторыми MTA.

9.1.2 Контроль исходящих соединений

Некоторые крупные домены имеют очень много записей MX, каждая из которых может относиться к нескольким IP-адресам. Когда возникают проблемы с подключением, пробовать каждый из этих адресов нецелесообразно. Если несколько IP-адресов в верхней части списка выходят из строя, разумно предположить, что существует какая-то проблема, которая, вероятно, затронет все из них или, по крайней мере, те, у которых одинаковое значение MX.

Значение параметра hosts_max_try — это максимальное количество IP-адресов, которые были первоначально опробованы. Значение по умолчанию для этого параметра равно пяти. Любые адреса, пропущенные из-за того, что их время повторной попытки не пришло, не учитываются. После того, как было опробовано это количество IP-адресов, Exim просматривает список хостов с разными значениями MX и, если находит, пробует по одному адресу для каждого значения.

Поскольку Exim работает распределенным образом, если несколько сообщений для одного и того же хоста приходят примерно в одно и то же время, может произойти более одного одновременного подключения к удаленному хосту. Обычно это не проблема, за исключением случаев, когда между хостами имеется медленная связь. В этой ситуации может быть полезно ограничить Exim одним соединением за раз с определенными хостами. Это можно сделать, установив опцию serialize_hosts для сопоставления соответствующим удаленным хостам, например:

serialize_hosts = 192.168.4.5 : my.slow.neighbour.example

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

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

Если вы настроили какую-либо сериализацию, вам также следует организовать удаление соответствующей базы данных подсказок при каждой перезагрузке вашей системы. Имена файлов начинаются с misc, и они хранятся в подкаталоге db в каталоге spool Exim. В зависимости от типа используемой библиотеки DBM может быть один или два файла[2].

Если сообщение было успешно доставлено через соединение TCP/IP, Exim просматривает другую базу данных подсказок, чтобы увидеть, есть ли какие-либо другие сообщения, ожидающие соединения с тем же хостом. Если они есть, для одного из них запускается новый процесс доставки, и ему передается текущее соединение TCP/IP. Новый процесс может, в свою очередь, создать еще один процесс. Каждый раз, когда это происходит, счетчик последовательности увеличивается, и если он когда-либо достигает значения параметра connection_max_messages (значение которого по умолчанию равно 500), дальнейшие сообщения не отправляются по тому же соединению TCP/IP.

  1. Те же файлы используются для сериализации ETRN.

9.1.3 Управление каждым соединением TCP/IP

Если исходящее SMTP-соединение выполняется с хоста с несколькими различными интерфейсами TCP/IP (реальными или виртуальными)[3], IP-функции системы выбирают, какой интерфейс использовать для отправляющего IP-адреса, если иное не указано в настройках interface. Вы можете использовать эту опцию, если, например, вы используете определенные IP-адреса только для веб-хостинга и не хотите, чтобы они использовались для почты. Для параметра interface задана строка, которая должна быть IP-адресом. Например, на хосте с IP-адресами 192.168.123.123 и 192.168.9.9 можно установить:

interface = 192.168.123.123

в этом случае все исходящие соединения, сделанные транспортом, будут использовать этот конкретный интерфейс.

Параметр interface представляет собой расширенную строку. Это позволяет использовать разные интерфейсы для разных удаленных хостов, используя в расширении переменные $host или $host_address.

В системе с поддержкой IPv6 указанный тип интерфейса должен быть того же типа, что и адрес, к которому осуществляется подключение. Чтобы разрешить хосты, поддерживающие как IPv4, так и IPv6, параметр interface фактически интерпретируется (после расширения) как строка IP-адресов. Используется первый адрес в списке правильного типа (IPv4 или IPv6). Например, если настройка следующая:

interface = <; 3ffe:ffff£:836£:0a00:000a:0800:200a:c031 ;\
               192.168.241.244

первый адрес используется для подключения к адресам IPv6, а второй используется для подключения к адресам IPv4.

Опция port определяет удаленный порт TCP/IP, к которому подключается Exim для отправки сообщения. Например:

port = 2525

Если значение начинается с цифры, оно принимается за номер порта; в противном случае оно ищется с помощью функции getservbyname(), которая просматривает /etc/services и, возможно, другие источники системной информации, такие как NIS. По умолчанию для port обычно используется smtp, но если для protocol установлено значение lmtp, значение по умолчанию меняется на lmtp. Этот параметр в основном используется для тестирования, но иногда бывает полезен и в других случаях.

По умолчанию Exim устанавливает опцию сокета SO_KEEPALIVE для исходящих сокетных соединений. Это заставляет ядро периодически проверять незанятые соединения, отправляя пакеты со «старыми» порядковыми номерами. Другой конец соединения должен отправить подтверждение, если соединение все еще существует, или сброс, если оно было прервано[4]. Вы можете установить опцию keepalive в false, чтобы отключить это, если это когда-либо понадобится. (Насколько я знаю, никому это не нужно.)

Существуют различные тайм-ауты, связанные с обменом SMTP; обычно они работают хорошо, и вам не нужно их менять. Однако их можно изменить с помощью следующих опций (для последних трех в скобках указаны значения по умолчанию, рекомендованные в RFC 2821):

connect_timeout

Эта опция указывает, как долго ждать, пока системная функция connect() установит соединение с удаленным хостом. Нулевое значение позволяет действовать системному тайм-ауту по умолчанию (обычно несколько минут). Однако из-за проблем с тайм-аутами по умолчанию, не работающими в некоторых операционных системах, Exim имеет значение по умолчанию 5 минут. Излишне говорить, что этот параметр не действует, если его значение меньше системного тайм-аута.

command_timeout (5 minutes)

Этот параметр указывает, как долго ждать ответа на SMTP-команду, а также сколько ждать исходного SMTP-ответа после установления соединения TCP/IP.

data_timeout (5 minutes)

Эта опция указывает, как долго разрешена передача одного блока данных сообщения[5]. Таким образом, общее время ожидания передачи сообщения зависит от размера сообщения.

final_timeout (10 minutes)

Указывает, как долго ждать ответа после отправки всего сообщения.

  1. Такие хосты часто называют многосетевыми хостами.

  2. Причина этого заключается в том, что это имеет положительный эффект, освобождая определенные типы соединений, которые могут застрять, когда удаленный хост отключен без надлежащей очистки соединения TCP/IP. Без SO_KEEPALIVE невозможно отличить бездействующее соединение от соединения, которое было прервано аварийно.

  3. Exim по умолчанию передает сообщения блоками по 8 КБ.

9.1.4 Использование параметра SMTP SIZE

Если удаленный SMTP-сервер указывает, что он поддерживает опцию SIZE команды MAIL, Exim пропускает размер сообщения в начале SMTP-транзакции. Если сообщение слишком велико для хоста-получателя, он может отклонить команду MAIL, которая спасает клиента от передачи большого сообщения только для того, чтобы оно было отклонено в конце.

Значение параметра size_addition (по умолчанию 1024) добавляется к размеру сообщения, чтобы получить аргумент для SIZE. Это позволяет использовать заголовки и другой текст, который может быть добавлен во время доставки с помощью параметров конфигурации или в транспортном фильтре. Возможно, потребуется увеличить это значение, если в сообщения добавляется много текста. В качестве альтернативы, если значение size_addition отрицательное, использование параметра SIZE полностью отключается.

9.1.5 Использование команды SMTP AUTH

Если Exim был собран с поддержкой хотя бы одного из механизмов аутентификации SMTP, опции hosts_require_auth и hosts_try_auth доступны в транспорте smtp. Каждый предоставляет список серверов, к которым Exim будет пытаться аутентифицироваться как клиент при подключении, при условии, что сервер объявит о поддержке аутентификации.

Разница между этими двумя вариантами заключается в том, что происходит, если аутентификация не удалась. Если сервер указан в hosts_require_auth, Exim откладывает доставку и пытается позже. Эту ошибку можно обнаружить в правилах повтора, что означает, что время ожидания повтора может быть установлено локальным администратором. С другой стороны, если сервер указан в hosts_try_auth, Exim пытается доставить сообщение без аутентификации.

Подробная информация об аутентификации SMTP и о том, как настроить Exim для ее использования, даны в главе 13.

9.1.6 Использование шифрования TLS

Транспорт smtp имеет несколько параметров управления шифрованием в исходящих соединениях, но мы оставляем их описание до общего обсуждения шифрования SMTP (13.1.4).

9.1.7 Использование протокола LMTP

LMTP (RFC 2033) — это протокол для передачи сообщений между MTA и методом «черного ящика» для хранения почты, таким как хранилище сообщений Cyrus IMAP. Позже в этой главе (9.6) мы обсудим предысторию LMTP и опишем, как его можно использовать для передачи сообщений локальным процессам. Однако LMTP похож на SMTP, и в некоторых случаях требуется передавать сообщения в хранилище сообщений через соединение TCP/IP с использованием LMTP вместо SMTP. Вы можете настроить Exim для этого, настроив транспорт smtp со следующей опцией:

protocol = lmtp

Если вы сделаете это, значение по умолчанию для port изменится на lmtp, но все остальное в транспорте будет работать точно так же, как и раньше. Конечно, при этом вы должны настроить специальный транспорт; LMTP не используется для обычной передачи сообщений между MTA.

9.1.8 Указание хостов

Есть две опции для smtp, которые могут указывать списки хостов: hosts и fallback_hosts.

Указание основного списка хостов

Наиболее распространенная ситуация, когда задаются hosts, — это когда роутер, который не может настроить список хостов, используется для удаленной доставки определенных адресов. Пример такого использования был приведен ранее (5.6), где мы рассмотрели корпоративный почтовый шлюз, который доставляет некоторые локальные части в своем локальном домене в локальные почтовые ящики, а другие отправляет на персональные рабочие станции. В версии примера, где используется роутер accept, параметр hosts на транспорте указывает рабочую станцию.

Переопределение списка хостов роутера

Когда транспорт smtp вызывается из роутера, который устанавливает список хостов, хосты, настроенные роутером, обычно переопределяют хосты, установленные в транспорте. Однако иногда вы можете захотеть, чтобы роутер проверил действительное место назначения, но сообщение было отправлено на другой хост. Если установлено hosts_override, игнорируются хосты роутера[6].

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

dnslookup:
  driver = dnslookup
  transport = smarthost

с этим транспортом:

smarthost:
  driver = smtp
  hosts = the.smart.host
  hosts_override

Роутер использует DNS для маршрутизации адресов обычным способом, поэтому любые несуществующие домены не могут быть маршрутизированы, что приводит к сбою их адресов. Однако список хостов, настроенный роутером, игнорируется транспортом, поскольку установлено hosts_override, что приводит к доставке всех адресов с маршрутизируемыми доменами на смарт-хост.

  1. Если hosts_override установлен без hosts, это не действует.

Указание резервного списка хостов

Опция fallback_hosts предоставляет возможность «использовать смарт-хост только в случае сбоя доставки». Ранее (6.3) мы обсуждали одноименную опцию для роутеров. Параметр для транспорта smtp имеет тот же эффект, но переопределяется, если резервные узлы предоставляются роутером. Опция hosts_override не применяется к fallback_hosts. После завершения обычной доставки резервная очередь доставляется путем повторного запуска тех же транспортов с новыми списками хостов. Если несколько ошибочных адресов имеют одни и те же запасные хосты (и max_rcpt это допускает), одна копия сообщения отправляется нескольким получателям.

Рандомизация списков хостов

Если параметр hosts или fallback_hosts в транспорте smtp указывает более одного хоста, они пробуются в том порядке, в котором они перечислены, если только не установлен hosts_randomize. В этом случае порядок списка рандомизируется при каждом запуске транспорта. Нет возможности использовать хосты в циклическом режиме.

Опция hosts_randomize также применяется к спискам хостов (как основным, так и резервным), которые настраиваются роутером и передаются на smtp с адресом, за исключением случая списка хостов, полученного из записей MX, где порядок определяют предпочтения MX.

У роутера manualroute также есть опция hosts_randomize. Это заставляет его переупорядочивать список хостов, который он генерирует во время маршрутизации, а это означает, что адреса, списки хостов которых указаны как одинаковые, могут оказаться в списках с разным порядком. Это предотвращает их отправку на транспорт smtp в пакетном режиме. Бывают ситуации, когда такое поведение может быть желательным.

Поиск IP-адресов

Три параметра в транспорте smtp управляют способом поиска IP-адресов для имен хостов (от hosts или от fallback_hosts). Параметр gethostbyname указывает, что IP-адреса ищутся путем вызова системной функции поиска хоста, а не путем прямого вызова резолвера DNS[7].

Две другие опции работают точно так же, как и одноименные опции в роутере dnslookup (7.2). Эти параметры следующие:

dns_qualify_single

Включает параметр RES_DEFNAMES резолвера DNS, из-за чего он не определяет домены, состоящие только из одного компонента.

dns_search_parents

Включает параметр RES_DNSRCH резолвера DNS, заставляя его искать неизвестные имена в родительских доменах.

  1. Исходная функция для этого называется gethostbyname(), откуда и происходит название опции. Однако в современных системах [IPv6] функция gethostbyname() была заменена функцией getipnodebyname().

Работа с локальным хостом

Есть еще один последний вариант, связанный с хостами, указанными в транспорте smtp. Когда какой-либо хост, указанный в hosts или fallback_hosts, оказывается локальным хостом, доставка по умолчанию откладывается. Однако, если установлено значение allow_localhost, Exim все равно продолжит доставку. Это следует использовать только в особых случаях, когда конфигурация гарантирует отсутствие зацикливания (например, по-разному сконфигурированный Exim прослушивает порт, на который отправляется сообщение).

9.1.9 Контроль повторных попыток

Большая часть логики повторных попыток Exim основана на хосте, а не на адресах или сообщениях. Это требует, чтобы он запоминал информацию об отказавших хостах, и очевидным местом для реализации логики для этого является транспорт smtp, где обнаруживаются такие отказы. В результате есть две опции, связанные с повторной попыткой: retry_include_ip_address и delay_after_cutoff.

Повторные попытки обычно основываются как на имени хоста, так и на IP-адресе, поэтому каждый IP-адрес многосетевого хоста обрабатывается независимо. Однако в некоторых средах клиентским узлам при каждом подключении к Интернету назначается другой IP-адрес. В этой ситуации использование IP-адреса как части ключа повторной попытки на узле сервера приводит к нежелательному поведению. Установка retry_include_ip_address в false заставляет Exim использовать только имя хоста. Обычно это следует делать на отдельном экземпляре транспорта smtp, настроенного специально для обработки этих нестандартных клиентских хостов.

Чтобы понять delay_after_cutoff, вам нужно знать, как Exim обрабатывает временные ошибки и повторные попытки. Это объясняется позже (12,10), поэтому здесь можно найти описание этой опции.

9.1.10 Обзор параметров smtp

В этом разделе кратко описаны параметры, характерные для транспорта smtp. Подробности о тех, кто связан с шифрованием TLS, можно найти позже (13.1.4). Конечно, для smtp также можно установить любую общую опцию транспорта (8.11).

allow_localhost (Boolean, default = false)

Если любой хост, указанный в hosts или fallback_hosts, оказывается локальным хостом, Exim по умолчанию откладывает доставку. Однако, если установлено значение allow_localhost, доставка продолжается.

command_timeout (time, default = 5m)

Эта опция устанавливает тайм-аут для получения ответа на отправленную SMTP-команду. Она также используется при ожидании исходной строки баннера с удаленного хоста. Ее значение не должно быть равно нулю.

connect_timeout (time, default = 5m)

Это устанавливает тайм-аут для функции connect(), которая устанавливает соединение TCP/IP с удаленным хостом. Нулевое значение позволяет использовать системный тайм-аут (обычно несколько минут). Чтобы иметь какой-либо эффект, значение этого параметра должно быть меньше системного времени ожидания.

connection_max_messages (integer, default = 500)

Это определяет максимальное количество отдельных доставок сообщений, которые могут осуществляться через одно соединение TCP/IP. Если значение равно нулю, ограничений нет.

data_timeout (time, default = 5m)

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

delay_after_cutoff (Boolean, default = true)

Эта опция управляет тем, что происходит, когда все удаленные IP-адреса для данного домена были недоступны так долго, что прошло время отсечки повторных попыток (12.10).

dns_qualify_single (Boolean, default = true)

Если используется параметр hosts или fallback_hosts, а gethostbyname имеет значение false, параметр, заставляющий резолвер DNS уточнять однокомпонентные имена с локальным доменом, устанавливается, если этот параметр имеет значение true.

dns_search_parents (Boolean, default = false)

Если используется параметр hosts или fallback_hosts, а gethostbyname имеет значение false, параметр резолвера DNS для включения поиска родительских доменов устанавливается, если этот параметр имеет значение true.

fallback_hosts (string list, default = unset)

Значение должно быть списком имен хостов или IP-адресов, разделенных двоеточиями. Расширение строки не применяется. Резервные хосты также могут быть указаны на роутерах; они связывают такие хосты с адресами, которые они обрабатывают. Резервные хосты, указанные в транспорте, используются только в том случае, если адрес не имеет собственного связанного списка резервных хостов.

final_timeout (time, default = 10m)

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

gethostbyname (Boolean, default = false)

Если этот параметр имеет значение true, когда используются параметры hosts или fallback_hosts, IP-адреса ищутся путем вызова системной функции поиска хоста вместо прямого использования DNS.

hosts (string list, default = unset)

Эта опция задает список хостов, которые используются, если обрабатываемый адрес не имеет связанных с ним хостов или если установлена опция hosts_override.

hosts_avoid_tls (host list, default = unset)

В этом параметре перечислены хосты, для которых не следует использовать шифрование TLS.

hosts_max_try (integer, default = 5)

Этот параметр указывает максимальное количество IP-адресов, к которым будет пытаться подключиться транспорт.

hosts_nopass_tls (host list, default = unset)

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

hosts_override (Boolean, default = false)

Если этот параметр установлен, а также установлен параметр hosts, любые узлы, подключенные к адресу, игнорируются, а вместо них используются узлы, указанные параметром hosts.

hosts_randomize (Boolean, default = false)

Если hosts_randomize имеет значение false, порядок, в котором хосты перечислены в hosts или fallback_hosts, сохраняется как предпочтительный порядок доставки сообщения; если опция true, список перемешивается в случайном порядке каждый раз, когда он используется. Списки хостов, связанные с адресами (роутерами), также рандомизируются, если только они не были получены из записей MX.

hosts_require_auth (host list, default = unset)

Эта опция предоставляет список серверов, для которых аутентификация должна пройти успешно, прежде чем Exim передаст сообщение. Ошибка аутентификации приводит к отсрочке доставки.

hosts_require_tls (host list, default = unset)

В этом параметре перечислены хосты, для которых требуется шифрование TLS.

hosts_try_auth (host list, default = unset)

Этот параметр предоставляет список серверов, для которых выполняется попытка аутентификации перед передачей сообщения. Если аутентификация не удалась, Exim пытается доставить без аутентификации.

interface (string list, default = unset)

Этот параметр указывает, к какому локальному интерфейсу привязываться при установлении исходящего SMTP-подключения. Если интерфейс не установлен, IP-функции системы выбирают, какой интерфейс использовать, если хост имеет более одного. Используется первый в списке интерфейс правильного типа (IPv4 или IPv6).

keepalive (Boolean, default = true)

Этот параметр управляет настройкой SO_KEEPALIVE для исходящих сокетных соединений.

max_rept (integer, default = 100)

Этот параметр ограничивает количество команд RCPT, отправляемых в одной транзакции сообщения SMTP. Каждый набор адресов обрабатывается независимо и может вызывать параллельные соединения с одним и тем же хостом, если это позволяет remote_max_parallel.

multi_domain (Boolean, default = true)

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

port (string, default = see description)

Этот параметр указывает порт TCP/IP, который используется для отправки сообщения. Если он начинается с цифры, он принимается за номер порта; в противном случае он ищется с помощью getservbyname(). По умолчанию используется smtp, если для параметра protocol не установлено значение lmtp, в этом случае порт по умолчанию изменяется на lmtp.

protocol (string, default = smtp)

Если для этого параметра установлено значение lmtp вместо smtp, значение по умолчанию для параметра port изменится на lmtp, и транспорт будет использовать протокол LMTP вместо SMTP.

retry_include_ip_address (Boolean, default = true)

Установка этой опции в false приводит к тому, что Exim использует только имя хоста вместо имени и IP-адреса при построении записей повторов (12.1).

serialize_hosts (host list, default = unset)

Эта опция перечисляет хосты, к которым одновременно должно быть установлено только одно соединение TCP/IP.

size_addition (integer, default = 1024)

Значение size_addition добавляется к размеру сообщения, чтобы создать значение, которое Exim отправляет в опции SIZE команды SMTP MAIL. Это позволяет добавлять заголовки и другой текст во время доставки в результате определенных параметров конфигурации, а также текст, добавляемый транспортным фильтром. Если значение size_addition отрицательное, использование опции SIZE отключено.

tls_certificate (string, default = unset)

Эта опция дает имя файлу, в котором хранится сертификат клиента.

tls_privatekey (string, default = unset)

Эта опция дает имя файлу, в котором хранится закрытый ключ клиента. Если он не установлен, когда установлен tls_certificate, предполагается, что закрытый ключ находится в том же файле, что и сертификат.

tls_require_ciphers (string, default = unset)

Эта опция содержит список разрешенных шифров TLS.

tls_tempfail_tryclear (Boolean, default = true)

Если хост сервера не находится в hosts_require_tl1s, и есть проблема с настройкой сеанса TLS, эта опция определяет, должен ли Exim пытаться доставить сообщение в чистом виде или нет.

tls_verify_certificates (string, default = unset)

Этот параметр указывает имя файла, в котором хранятся разрешенные сертификаты сервера.

9.2 Пакетная обработка адресов в транспортах appendfile, lmtp и pipe

Если сообщение имеет несколько получателей, которым требуется локальная доставка, транспорты обычно обрабатывают по одному адресу за раз. То есть для каждого адреса, который на него маршрутизируется, запускается отдельный экземпляр транспорта, и в каждом случае записывается отдельная копия сообщения. (Это отличается от транспорта smtp, который по умолчанию обрабатывает несколько адресов, маршрутизируемых на одни и те же удаленные хосты, как одну доставку.)

Однако бывают ситуации, когда полезно иметь возможность запускать локальный транспорт с несколькими адресами получателей. Например:

Транспорты appendfile, lmtp и pipe имеют одни и те же параметры для управления множественными («пакетными») доставками. Чтобы не повторять описание для каждого транспорта, мы рассмотрим эти параметры в этом разделе.

Параметр batch_max указывает максимальное количество адресов, которые могут быть доставлены вместе за один запуск транспорта. Его значение по умолчанию равно единице. Если более одного адреса получателя в сообщении перенаправляются на локальный транспорт, у которого batch_max больше единицы, адреса доставляются пакетом (то есть за один запуск транспорта), но с учетом этих условий:

Переменная $local_part никогда не устанавливается, если передается более одного адреса, а переменная $domain устанавливается, только если все адреса имеют один и тот же домен. Если для транспорта установлена общая опция envelope_to_add, строка заголовка Envelope-to:, которая добавляется к сообщению, содержит все адреса.

Пакетная локальная доставка чаще всего используется в сочетании с пакетным SMTP. Некоторые примеры показаны в главе 5, а еще один пример приведен в разделе 9.3.2. В качестве примера использования локальной пакетной доставки без BSMTP предположим, что вы хотите собрать все сообщения для определенных доменов в файлы. Во-первых, настройте роутер, который отбирает домены и направляет их на специальный транспорт:

filed_domains_router:
  driver = accept
  domains = first.filed.example : second.filed.example :
  transport = filed_domains_transport

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

filed_domains_transport:
  driver = appendfile
  batch_max = 100
  file = /var/savedmail/$domain
  envelope_to_add
  return_path_add
  user = mail

Должна быть настройка пользователя либо на роутере, либо на транспорте, чтобы указать uid, под которым выполняется процесс доставки. Установка envelope_to_add и return_path_add гарантирует, что соответствующие части конверта сообщения будут сохранены в строках заголовка.

В этом примере предполагается, что все сообщения для одного домена хранятся в одном файле. Это не должно быть так; appendfile также может работать, записывая каждое сообщение в виде отдельного файла (9.4.4),

9.3 Параметры, общие для транспортов appendfile и pipe

Транспорт appendfile, который записывает сообщения в файлы, и транспорт pipe, который записывает сообщения в каналы, подключенные к другим процессам, имеют ряд общих параметров. Чтобы не повторяться, они собраны вместе в этом разделе.

9.3.1 Управление форматом сообщения

Поскольку сообщение записывается в файл или по конвейеру, транспорт может вносить определенные изменения. Общие параметры добавления или удаления строк заголовков рассматриваются в главе 8; здесь мы описываем некоторые опции, применимые только к местной доставке.

Разделение сообщений в одном файле

Если почтовый ящик состоит из одного файла, содержащего связанные сообщения, должен быть какой-то способ определить, где заканчивается одно сообщение и начинается другое. Для этого использовалось несколько схем, наиболее распространенной из которых является формат почтового ящика Беркли (Berkeley mailbox format), в котором сообщение начинается со строки, начинающейся со слова From, за которой следует пробел и другие данные, и заканчивается пустой строкой.

Этот формат, кажется, был принят в далеком прошлом, потому что сообщения, полученные UUCP, используют такую строку для содержания адреса отправителя конверта[8]. Однако для него не существует стандарта в качестве разделителя сообщений, и на практике встречались несколько вариантов. Это самый неудачный выбор строки-разделителя, потому что строки в теле сообщения, начинающиеся со слова From, не редкость. Это означает, что такие строки должны быть каким-то образом экранированы (как описано ниже), иначе они будут восприняты как начало нового сообщения.

Exim поддерживает разделение сообщений, предоставляя две опции, называемые message_prefix и message_suffix. Их содержимое расширяется и записывается в начале и в конце каждого сообщения соответственно. Значения по умолчанию зависят от настроек других параметров транспорта. Если транспорт appendfile настроен на добавление сообщений в один файл почтового ящика, а use_bsmtp не задан, значения по умолчанию следующие:

message_prefix = "From ${if def:return_path{$return_path}\
         {MAILER-DAEMON}} S$tod_bsdinbox\n"
message suffix = "\n"

Они определяют разделение сообщений в формате Беркли. Параметр message_prefix помещает отправителя конверта (return path) в строку-разделитель, если только сообщение не является рикошетом (где нет обратного пути). Для рикошетов используется MATLER-DAEMON, потому что некоторые программы, читающие почтовые ящики, не работают, если ничего не вставлено. Строка заканчивается датой и временем в определенном формате, требуемом этой формой разделителя, которая доступна в переменной $tod_bsdinbox. Параметр message_suffix гарантирует, что после каждого сообщения будет пустая строка[9].

Если appendfile настроен для доставки каждого сообщения в отдельный файл или если задан use_bsmtp, настройки по умолчанию для message_prefix и message_suffix пусты, поскольку в этих случаях разделители не нужны. Однако во всех случаях значения по умолчанию могут быть переопределены явными настройками конфигурации.

Разделение сообщений не является проблемой в случае транспорта pipe. Однако некоторые реализации часто используемой команды /usr/ucb/vacation ожидают увидеть строку From в начале передаваемых им сообщений. По этой причине значения параметров message_prefix и message_suffix по умолчанию такие же, как и для appendfile. Они определяют разделение сообщений в формате Berkeley, за исключением случаев, когда установлено значение use_bsmtp.

Однако во многих других приложениях транспорта pipe строка From не ожидается и должна быть отключена. Пример этого был приведен ранее (5.4).

Формат Беркли — не единственный, в котором между сообщениями используются текстовые разделители. В формате MMDF начало и конец сообщения отмечаются строками, содержащими ровно четыре непечатаемых символа, числовое значение кода которых равно 1. Транспорт appendfile можно настроить для поддержки этого, установив следующие параметры:

message_prefix = "\1\1\1\1\n"
message_suffix = "\1\1\1\1\n"

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

  1. См. RFC 976, UUCP Mail Interchange Format Standard.

  2. Само сообщение всегда заканчивается новой строкой, потому что протокол SMTP определяется строками. Для сообщений, отправленных локально, Exim добавляет последний перевод строки, если его нет.

Экранирование строк в сообщении

Механизм, предназначенный для обработки строк в сообщении, которые выглядят как разделители сообщений, представляет собой пару опций, называемых check_string и escape_string. Если настроена пакетная доставка SMTP, содержимое check_string и escape_string задаются значениями, реализующими протокол экранирования SMTP для строк, начинающихся с точки. В этом случае любые настройки, сделанные в файле конфигурации, игнорируются.

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

check_string = "From "
escape_string = ">From "

Когда транспорт записывает сообщение, начало каждой строки проверяется на соответствие check_string. Если он совпадает, начальные совпадающие символы заменяются содержимым escape_string. Значение контрольной строки представляет собой литеральную строку, а не регулярное выражение. Поэтому по умолчанию вставляется один символ угловой скобки перед любой строкой, начинающейся с From, за которой следует пробел. Например, если сообщение содержит строку:

From the furthest reaches of the Galaxy, ...

appendfile на самом деле пишет:

>From the furthest reaches of the Galaxy, ...

Если вы используете разделители почтовых ящиков MMDF, вам необходимо изменить настройки по умолчанию на[10]:


check_string  = "\1\1\1\1\n"
escape_string = "\1\1\1\1 \n"
  1. Если сообщение содержит двоичные данные, такое изменение может повредить данные, но это, вероятно, лучше, чем поврежденный файл почтового ящика.

Контроль терминаторов линии

Последний общий параметр для appendfile и pipe, влияющий на содержимое сообщения, — use_crlf. Когда он установлен, строки завершаются CRLF, а не просто переводом строки. Некоторые внешние программы локальной доставки требуют этого. Это также может быть полезно в случае пакетного SMTP, потому что записанная последовательность байтов является точным изображением того, что будет отправлено по реальному SMTP-соединению.

9.3.2 Использование пакетного SMTP (BSMTP)

Пакетная доставка по протоколу SMTP упоминалась в нескольких предыдущих разделах, и было показано несколько примеров (7.4.5). BSMTP — это удобный способ сохранить конверты в сообщениях, которые временно хранятся в файлах, или передать конверты таким процессам, как сканеры вирусов, которые могут обрабатывать много получателей одновременно. Это особенно удобно, если сообщение впоследствии будет передано обратно в Exim или в любую программу, которая передает его по SMTP-соединению.

В транспортах appendfile и pipe параметр use_bsmtp запрашивает доставку в пакетном формате SMTP. Затем сообщения записываются так, как будто они передаются по SMTP-соединению. Каждое сообщение начинается с команды MAIL для отправителя конверта, за которой следует команда RCPT для каждого получателя, а затем DATA и само сообщение, заканчивающееся точкой (в пустрой строке). Например, файл, написанный таким образом, может содержать:

MAIL FROM: <tom@abcd.example>
RCPT TO:<jerry@pgrs.example>
RCPT TO: <bugs@albuquerque.example>
DATA
<<message content>>
.

Значения по умолчанию параметров message_prefix и message_suffix не установлены, если установлено значение use_bsmtp. Однако некоторые программы, читающие файлы BSMTP, ожидают, что перед каждым сообщением будет стоять команда HELO; для этого можно использовать параметр message_prefix. Например:

filed_domains:
  driver = appendfile
  file = /var/savedmail/$host
  batch_max = 100
  use_bsmtp
  message_prefix = HELO $primary_hostname
  user = mail

Настройки check_string и escape_string принудительно устанавливаются на следующие значения, когда установлено use_bsmtp:

check_string = .
escape_string = ..

Эти значения реализуют стандартный механизм экранирования для SMTP. Любые значения, установленные в конфигурации, игнорируются.

9.3.3 Обзор параметров, общих для appendfile и pipe

В этом разделе обобщены параметры, общие для транспортов appendfile и pipe:

check_string (string, default = see description)

Когда транспорт записывает сообщение, начало каждой строки проверяется на соответствие check_string, и если это так, начальные совпадающие символы заменяются содержимым escape_string. Значение check_string представляет собой литеральную строку, а не регулярное выражение. Для транспорта appendfile значением по умолчанию является From, за которым следует пробел, если транспорт настроен на добавление сообщений в один файл почтового ящика, а use_bsmtp не задан. Для транспорта pipe значение по умолчанию всегда отключено.

escape_string (string, default = see description)

См. check_string. По умолчанию установлено значение >From, если значение по умолчанию для check_string равно From, и не задано в противном случае.

message_prefix (string, default = see description)

Указанная здесь строка расширяется и выводится в начале каждого сообщения. Если установлено use_bsmtp, значение по умолчанию не установлено. В противном случае, за исключением случаев, когда appendfile настроен на доставку каждого сообщения в отдельный файл, значение по умолчанию:

message_prefix = "From ${if def:return_path{$return_path}\
  {MAILER-DAEMON}} ${tod_bsdinbox}\n"
message_suffix (string, default = see description)

Указанная здесь строка расширяется и выводится в конце каждого сообщения. Значение по умолчанию — \n, если значение по умолчанию для message_prefix не пустое, и не задано в противном случае.

use_bsmtp (Boolean, default = fails)

Этот параметр запрашивает доставку сообщений в пакетном формате SMTP. Это влияет на значения по умолчанию check_string, escape_string, message_prefix и message_suffix.

use_crlf (Boolean, default = false)

Этот параметр приводит к тому, что строки заканчиваются двухсимвольной последовательностью CRLF (возврат каретки, перевод строки), а не просто символом перевода строки.

9.4 Транспорт appendfile

Запись сообщений в файлы — самая сложная операция локального транспорта Exim'а; следовательно, у appendfile есть большое количество опций. Этот транспорт может работать двумя совершенно разными способами:

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

9.4.1 Настройка файла мультисообщения для добавления

Когда appendfile вызывается в результате элемента имени файла в пользовательском файле .forward или в результате команды save в файле фильтра, роутер передает имя файла на транспорт вместе с доставляемым адресом. В других случаях, когда имя файла уже не связано с адресом, опция file указывает имя файла, к которому должно быть добавлено сообщение. Пример, который несколько раз использовался для доставки в обычные почтовые ящики:

file = /var/mail/$local_part

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

Местоположение почтового ящика

На хостах, где у пользователей есть доступ для входа, некоторые установки предпочитают размещать почтовые ящики пользователей в их домашних каталогах. Это имеет три преимущества:

Если вы хотите настроить Exim таким образом, вы можете использовать настройку параметра file следующим образом:

file = /home/$local_part/inbox

Недостатки этого подхода заключаются в следующем:

Альтернативный и, возможно, более распространенный подход — разместить все почтовые ящики в одном каталоге, отдельном от домашних каталогов. Каталог обычно называется /var/mail или /var/spool/mail, и большинство пользовательских агентов ожидают найти почтовые ящики в одном из этих каталогов по умолчанию. Эта схема работает достаточно хорошо для небольшого количества почтовых ящиков, но когда число становится большим, обычно приходится искать способ разбить каталог, чтобы избежать потери производительности (4.12).

Со всеми почтовыми ящиками в одном каталоге должен быть способ для процесса доставки Exim, работающего от имени пользователя-получателя, создать новый почтовый ящик, если он не существует, и иметь возможность писать в существующий почтовый ящик[11]. Предоставление всем пользователям полного доступа к каталогу не является решением, потому что это позволит одному пользователю удалить почтовый ящик другого пользователя. Unix содержит средство разрешения доступа к файлам, предназначенное именно для этой ситуации. По историческим причинам он известен как sticky bit (или липкий бит). Когда для каталога установлено это разрешение, наряду с обычным разрешением на запись, любой пользователь может создать новый файл в каталоге, но файлы могут быть удалены только их владельцами. Буква t используется для обозначения этого разрешения, поэтому каталог почтового ящика, настроенный таким образом, выглядит следующим образом:

drwxrwxrwt   3 root   mail   512 Jul  9 13:48 /var/mail/

Локальные процессы доставки Exim, которые запускаются с использованием uid и gid принимающего пользователя, теперь могут создавать новые почтовые ящики, если это необходимо. Каждый почтовый ящик будет принадлежать локальному пользователю и по умолчанию будет доступен только этому пользователю, который может изменять его, а также удалять.

Использование «липких» каталогов имеет некоторые недостатки:

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

drwxrwx--x   3 root   mail   512 Jul  9 13:48 /var/mail/

Транспорт appendfile настроен для запуска под группой mail, а не под группой пользователя, например:

local_delivery:
  driver = appendfile
  file = /var/mail/$local_part
  group = mail
  mode = 660

Однако он по-прежнему работает с uid пользователя. С этой конфигурацией Exim может создавать новые почтовые ящики из-за разрешения группы на запись. Каждый почтовый ящик принадлежит соответствующему пользователю, но его группа настроена как mail. Exim может обновить существующий почтовый ящик, потому что настройка режима позволяет группе mail писать в файл. MUA пользователя может получить доступ к файлу как владелец. Тем не менее, недостаток все же есть: пользователь не может удалить файл. Некоторые MUA пытаются удалить файл, когда все содержащиеся в нем сообщения удалены или перемещены, что может привести к появлению сообщений об ошибках.

  1. Exim также может понадобиться для создания и удаления файлов блокировки (9.4.3).

Символические ссылки для файлов почтовых ящиков

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

Доставка в именованные каналы (FIFO)

Как и в случае с символическими ссылками, appendfile не будет доставляться в FIFO (именованный канал) по умолчанию, но его можно настроить для этого, установив allow_fifo. Если ни один процесс не читает именованный канал во время доставки, доставка откладывается.

Создание несуществующего файла

Действие по умолчанию — попытаться создать файл и все вышестоящие каталоги, если они не существуют. Несколько вариантов дают контроль над этим процессом:

Владелец существующего файла

Если файл уже существует, выполняется проверка его владельца и разрешений. Владельцем должен быть uid, под которым запущен процесс доставки, если для check_owner не задано значение false. UID либо задается опцией user на транспорте, либо передается роутером вместе с адресом. Для обычных случаев доставки в почтовые ящики пользователей и доставки в файлы, указанные в файлах .forward, пользователем обычно является локальный пользователь, соответствующий локальной части адреса. Если установлено значение check_group, также проверяется групповое владение файлом. Это не значение по умолчанию, поскольку режим файла по умолчанию — 0600 (владелец только для чтения/записи), для которого группа не имеет значения.

Режим файла

Если доставка является результатом команды save в файле фильтра, который указывает конкретный режим для файла, новый файл создается с этим режимом, а существующий файл изменяется, чтобы иметь этот режим. В противном случае, если файл создается, его режим устанавливается равным значению, указанному в параметре mode, который по умолчанию равен 0600. Если файл уже существует и имеет более широкие разрешения (установлено больше битов), чем те, которые указаны в mode, они уменьшаются до значения mode. Если он имеет более узкие разрешения, возникает ошибка, и доставка откладывается, если для параметра mode_fail_narrower не задано значение false, и в этом случае попытка доставки выполняется в существующем режиме.

9.4.2 Формат добавляемых сообщений

Способ добавления сообщения к файлу по умолчанию состоит в том, чтобы записать содержимое параметра message_prefix, за которым следует сообщение, а затем message_suffix сообщения (9.3.1). Значения по умолчанию поддерживают традиционные почтовые ящики Berkeley Unix. Другие форматы, основанные на текстовых разделителях, могут использоваться путем изменения message_prefix и message_suffix, как было показано ранее в примере MMDF (9.3.1).

Почтовые ящики формата MBX

Другой формат почтового ящика, поддерживаемый appendfile, — это MBX, который запрашивается установкой mbx_format[12]. Этот формат однофайлового почтового ящика поддерживается Pine 4 и связанными с ним демонами IMAP и POP и реализуется библиотекой c-client, которую все они используют.

Дополнительная информация о длине сообщений хранится в начале почтового ящика MBX. Это ускоряет доступ к отдельным сообщениям. Также возможен одновременный совместный доступ к почтовым ящикам MBX разными пользователями. Однако требуется особая форма блокировки файлов, и mbx_format не следует использовать, если какая-либо программа, не использующая эту форму блокировки, собирается получить доступ к почтовому ящику. Блокировку MBX нельзя использовать, если файл почтового ящика смонтирован по NFS, поскольку этот тип блокировки работает только при доступе к почтовому ящику с одного хоста. Далее в этой главе более подробно рассматривается блокировка MBX.

Чтобы поддерживать почтовый ящик в формате MBX, Exim должен записать сообщение во временный файл перед его добавлением, чтобы он мог получить его точную длину, включая любые строки заголовков, которые добавляются в процессе доставки. Это делает этот вид доставки немного дороже.

Параметры message_prefix, message_suffix и check_string не изменяются автоматически при использовании mbx_format; обычно они должны быть пустыми, потому что формат MBX не зависит от использования разделителей сообщений. Таким образом, типичный транспортный файл appendfile для доставки MBX может выглядеть следующим образом:

local_delivery:
  driver = appendfile
  file = /var/mail/$local_part
  delivery_date_add
  envelope_to_add
  return_path_add
  mbox_format
  message_prefix =
  message_suffix =
  check_string =
  1. Код для этого не встроен в Exim по умолчанию; это должно быть запрошено в конфигурации во время сборки.

Проверка формата существующего файла

Когда Exim добавляет сообщение в существующий файл почтового ящика, предполагается, что файл имеет правильный формат, потому что обычно на любом хосте используется только один формат. Однако иногда, особенно при переходе от одного формата к другому, могут сосуществовать файлы разных форматов. В таких ситуациях можно заставить appendfile проверять формат файла перед записью в него и, при необходимости, передавать управление другому транспорту. Например, предположим, что в системе существуют почтовые ящики Berkeley и MBX. К только что определенному нами транспорту local_delivery можно добавить следующее:

file_format = "*mbx*\r\n : local_delivery :\
               From                 : local_bsd_delivery"

Элементы в списке file_format берутся парами. Первая из каждой пары — это текстовая строка, которая сравнивается с символами в начале файла. Если они совпадают, вторая строка в паре является именем используемого транспорта. В этом примере, если файл начинается с *mbx*\r\n, требуется транспорт local_delivery. Так как это текущий транспорт, доставка продолжается. Однако, если файл начинается с From, управление передается транспорту с именем local_bsd_delivery, который может быть определен следующим образом:

local_bsd_delivery:
  driver = appendfile
  file = /var/mail/$local_part
  delivery_date_add
  envelope_to_add
  return_path_add

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

9.4.3 Блокировка файла для добавления

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

Файлы почтовых ящиков, содержащие несколько сообщений, изменяются как MTA, так и MUA. MTA добавляют сообщения, но MUA могут как добавлять, так и удалять сообщения, хотя удаление является их наиболее распространенным действием. Когда MTA обновляет файл, добавляя сообщение, он должен гарантировать, что он имеет исключительный контроль над файлом, чтобы никакой другой процесс не мог попытаться обновить его в то же время. Из-за распределенной природы Exim возможно одновременное выполнение нескольких доставок одного и того же файла. Это означает, что Exim должен координировать свои собственные процессы, а также блокировать любые MUA, которые могут работать с файлом.

Блокировка в Unix — это чисто добровольное действие со стороны процесса. Его успех зависит от совместного поведения всех процессов, обращающихся к общему файлу. Для блокировки файлов почтовых ящиков возникли два разных соглашения, и, поскольку нельзя предположить, что все MUA в системе используют одно и то же, Exim обычно получает оба вида блокировки перед обновлением файла.

Блокировка с помощью файла блокировки

Первый метод блокировки заключается в использовании файла блокировки (lock file). Unix содержит примитивную операцию, которая создает файл, если он не существует, или возвращает ошибку, если он существует. Например, процесс, которому требуется монопольный доступ к файлу с именем /a/b/c, пытается создать файл с именем /a/b/c.lock с помощью этого метода[13]. Если создание прошло успешно, процесс получает право собственности на блокировку; если создание завершается ошибкой, потому что файл уже существует, значит, какой-то другой процесс имеет блокировку, и первый процесс должен подождать некоторое время, а затем повторить попытку. Как только процесс завершил обновление исходного файла, он удаляет файл блокировки. Для работы этой схемы необходимо соблюдение следующих условий:

Есть две основные проблемы с файлами блокировки:

Несмотря на эти проблемы, файлы блокировки важны, поскольку они являются единственным надежным способом блокировки, который работает через NFS, когда несколько хостов обращаются к одной и той же файловой системе NES.

  1. На практике не так просто создать файл через NFS, как по сравнению с обычным созданием файла, но принцип тот же.

Использование функции блокировки

Другой метод блокировки работает только с открытым файлом. По историческим причинам для этого существует несколько различных вызовов Unix; Exim использует функцию fcntl(). Этот метод блокировки не имеет проблем с файлами блокировки, потому что:

Зачем использовать файлы блокировки?

Почему не все используют только блокировки fcntl(), поскольку они кажутся намного лучше, чем файлы блокировки? Одной из причин является история: файлы блокировки появились первыми, и некоторые старые программы могут по-прежнему не использовать ничего другого. Гораздо более важной причиной является широкое использование NFS. Когда несколько хостов обращаются к одной и той же файловой системе NFS, блокировки fcntl() не работают. Причина в том, что размер файла сохраняется на хосте клиента NFS при открытии файла, поэтому, если два процесса, запущенные на разных хостах, открывают файл одновременно, они оба получают одинаковый размер. Если затем один получает блокировку и обновляет файл, пока другой ждет, второй будет иметь неправильный размер, когда он в конечном итоге получит блокировку. Чтобы безопасно обновить файл NFS с разных хостов, необходимо получить блокировку перед открытием файла,

Параметры блокировки для почтовых ящиков, отличных от MBX

Транспорт appendfile поддерживает оба типа блокировки для традиционных форматов почтовых ящиков, а также третий вариант для почтовых ящиков MBX, о котором мы вскоре расскажем. По умолчанию используются как файлы блокировки, так и блокировка fcntl(), чтобы учесть все возможные ситуации, но предусмотрены опции для выбора того или иного типа блокировки, а также для изменения некоторых параметров блокировки. Режим любого созданного файла блокировки устанавливается параметром lockfile_mode.

Если для use_lockfile установлено значение false, файлы блокировки не используются, а если для use_fcnt1_lock установлено значение false, блокировка с помощью fcntl() отключена. Одновременно можно отключить только один вид блокировки, но в большинстве случаев не следует делать ни того, ни другого. Вам следует подумать об отключении одного из них только в том случае, если вы абсолютно уверены, что оставшейся блокировки достаточно для всех программ, обращающихся к почтовому ящику.

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

По умолчанию используются неблокирующие вызовы fcntl(). Если вызов терпит неудачу, Exim засыпает на время lock_interval, а затем пытается снова, повторить до количества раз lock_retries. Неблокирующие вызовы используются для того, чтобы файл не оставался открытым во время ожидания блокировки; причина этого состоит в том, чтобы сделать его максимально безопасным для доставки NFS в случае, когда процессы могут обращаться к почтовому ящику NFS без использования файла блокировки[14].

В загруженной системе ожидание и повторные попытки не дают такой хорошей производительности, как использование блокирующего вызова fcntl() с тайм-аутом. Это особенно актуально, когда используются только блокировки fcntl() (для файлов блокировок единственная возможность — приостановить работу и повторить попытку). Одним из симптомов проблемы с блокировкой почтового ящика является частое появление сообщения об ошибке «failed to lock mailbox» (не удалось заблокировать почтовый ящик) в журнале Exim. Если за этим сообщением следует «(fcntl)», указывающее, что проблема связана с блокировкой fcntl(), вам следует попробовать изменить способ использования fcntl(), установив lock_fcnt1_timeout. Если для этой опции установлено ненулевое время, используется блокировка вызовов с заданным тайм-аутом. Все еще могут быть повторные попытки: максимальное количество повторных попыток вычисляется как:

(lock_retries * lock_interval) / lock_fсntl_timeout

округляется до следующего целого числа. Другими словами, общее время, в течение которого appendfile пытается получить блокировку, примерно одинаково для блокирующих и неблокирующих вызовов, если только lock_fcnt1l_timeout не установлен очень большим.

  1. Этого делать не следует, но бывают случаи, и неопытный администратор может столкнуться с такой конфигурацией.

Варианты блокировки для почтовых ящиков MBX

Если appendfile настроен для почтовых ящиков MBX (путем установки mbx_format), правила блокировки по умолчанию отличаются. Если в конфигурации не указан ни один из параметров блокировки, предполагается использование use_mbx_lock, а для других параметров блокировки по умолчанию установлено значение false. Можно использовать другие виды блокировки с mbx_format, но use_fcntl_lock и use_mbx_lock являются взаимоисключающими. Блокировка MBX следует правилам блокировки библиотеки c-client.

Exim снимает общую блокировку fcntl() с файла почтового ящика и эксклюзивную блокировку с файла, имя которого /mp/.<device-number>.<inode-number>, где номера устройства и индекса являются номерами файла почтового ящика. Общая блокировка почтового ящика не позволяет любому другому клиенту MBX получить эксклюзивную блокировку для него и удалить ее (удаляя сообщения). Эксклюзивная блокировка файла /tmp не позволяет любому другому клиенту MBX каким-либо образом обновлять почтовый ящик. По завершении записи, если может быть получена эксклюзивная блокировка самого почтового ящика, указывающая на отсутствие текущих пользователей, файл /tmp отсоединяется (удаляется). В противном случае он остается, потому что кто-то из других участников может ждать, чтобы заблокировать его. Вызовы fcntl() для получения этих блокировок по умолчанию не блокируются, но их можно заблокировать, установив параметр lock_fcntl_timeout.

Блокировка MBX корректно взаимодействует с библиотекой c-client, обеспечивая общий доступ к почтовому ящику. Его не следует использовать, если какая-либо программа, не использующая эту форму блокировки, собирается получить доступ к почтовому ящику, и его не следует использовать, если файл почтового ящика смонтирован через NFS, поскольку он работает только тогда, когда доступ к почтовому ящику осуществляется с одного хоста. .

Если вы установите use_fcnt1_lock для почтового ящика формата MBX, вы не сможете использовать MUA, использующий стандартную версию библиотеки c-client, потому что, пока у него открыт почтовый ящик (это означает, что для всего сеанса Pine или IMAP, например), Exim не может добавлять к нему сообщения.

9.4.4 Доставка каждого сообщения в отдельный файл

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

Недостатки следующие:

Как следствие последнего пункта, системы, использующие этот вид доставки для локальных почтовых ящиков, обычно являются системами, в которых доступ к почтовым ящикам тщательно контролируется, например, путем предоставления доступа только по протоколам POP или IMAP[16]. Однако доставка в отдельные файлы также обычно используется в совершенно отдельной ситуации: когда сообщения временно сохраняются для коммутируемого хоста (12.12).

Транспорт appendfile можно настроить для доставки каждого сообщения в отдельный файл, заменив параметр file параметром directory. Параметр create_directory определяет, может ли каталог быть создан, если он не существует, а если он создан, то directory_mode определяет его режим. Записываемые данные идентичны однофайловому почтовому ящику; в частности, данные message_prefix и message_suffix записываются, если они настроены, и соблюдается check_string. Обычно эти функции не требуются, когда каждое сообщение находится в отдельном файле, поэтому значения по умолчанию для этих параметров не устанавливаются, когда задан directory.

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

  1. Разные операционные системы и даже разные файловые системы имеют разные пороговые значения, за которыми снижается производительность.

  2. Используемые демоны POP и IMAP, разумеется, должны быть способны работать с многофайловыми почтовыми ящиками.

9.4.5 Формат почтового каталога

Один режим доставки отдельных файлов для локальных почтовых ящиков, который получает всеобщее признание (не только с Exim), называется maildir. Это сложнее, чем просто доставка в отдельные файлы, и работает следующим образом:

Файлы в каталоге tmp, которые старше, скажем, 36 часов, должны быть удалены; они представляют неудачные попытки доставки.

Exim доставляет в формате maildir, если установлена опция maildir_format. Это типичная конфигурация:

maildir_delivery:
  driver = appendfile
  directory = /home/$local_part/maildir
  maildir_format
  delivery_date_add
  envelope_to_add
  return_path_add

Наличие directory, а не file указывает на один файл для каждого сообщения, а maildir_format указывает, что должны соблюдаться правила maildir. Сообщения, доставляемые этим транспортом, попадают в файлы с такими именами, как:

/home/caesar/maildir/new/955429480.9324.host.example

К имени можно добавить дополнительную строку, установив maildir_tag. Содержимое этой опции расширяется и добавляется к имени файла, когда файл перемещается в новый каталог. Тег может содержать любые печатные символы, кроме косой черты; если он начинается с буквенно-цифрового символа, вставляется начальное двоеточие. К этому моменту файл уже записан в каталог tmp (используется только основное имя), поэтому известен его точный размер. Это значение доступно в переменной $message_size. Пример того, как это можно использовать, описан в следующем разделе.

Имя файла, указанное в доставке maildir, всегда должно быть уникальным; однако, в маловероятном случае, когда такой файл уже существует, или если ему не удается создать файл, Exim ждет две секунды и пытается снова с новым именем файла. Количество попыток контролируется параметром maildir_retries (по умолчанию 10). Если это превышено, доставка откладывается.

9.4.6 Квоты почтовых ящиков

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

В некоторых конфигурациях может быть невозможно использовать системные квоты. Чтобы в таких случаях можно было контролировать размеры почтовых ящиков, в appendfile предусмотрен собственный механизм квот[17]. Опция quota устанавливает ограничение на размер файла, к которому Exim добавляет, или на общее пространство, используемое в дереве каталогов, если опция directory установлена. В последнем случае вычисление используемого пространства является дорогостоящим, поскольку все файлы в каталоге (и любых подкаталогах) необходимо проверять по отдельности и суммировать их размеры[18]. Также нет блокировки против двух одновременных поставок. Если есть возможность, предпочтительнее использовать механизм квот операционной системы.

Стоимость суммирования размеров отдельных файлов может быть уменьшена в системах, где используется доставка maildir и пользователи не имеют прямого доступа к файлам. Установка параметра:

maildir_tag = ,S=$message_size

заставляет размер каждого сообщения добавляться к его имени, что приводит к файлам сообщений с такими именами, как:

/home/caesar/maildir/new/955429480.9324.host.example,S=3265

Когда Exim'у нужно найти размер файла, он сначала проверяет quota_size_regex. Это должно быть установлено на регулярное выражение, которое соответствует имени файла. Если он захватывает одну строку (с помощью подвыражения в скобках), эта строка интерпретируется как представление размера файла. Например:

quota_size_regex = S=(\d+)$

может использоваться с только что показанным maildir_tag. Этот подход небезопасен, если у пользователей есть какой-либо доступ, позволяющий им переименовывать файлы, но в средах, где это не так, он избавляет от необходимости запускать stat() для каждого файла, что дает значительные преимущества. Значение quota_size_regex не расширяется.

Значение самой опции quota расширяется, и результатом должно быть числовое значение (допускается десятичная точка), за которым может следовать одна из букв K или M. Таким образом, это устанавливает фиксированную квоту в 10 МБ для всех пользователей:

quota = 10M

Расширение происходит, когда Exim работает от имени пользователя root, до того, как он изменит uid и gid для выполнения доставки, поэтому файлы или базы данных, недоступные для конечного пользователя, могут использоваться для хранения значений квот, которые ищутся при расширении. Например:

quota = ${lookup pgsql {select quota from users \
        where id='${quote_pgsql:$local_part}'}{$value}{500K}}

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

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

  1. Конечно, если также действуют системные квоты, вы не можете указать квоту, превышающую системную квоту.

  2. Это делается с помощью системной функции stat().

9.4.7 Инклюзивные и эксклюзивные квоты

Как уже было сказано, система квот Exim работает аналогично системным дисковым квотам: она предотвращает превышение почтового ящика определенного размера. Это означает, что когда почтовый ящик почти заполнен, в него можно доставлять небольшие сообщения, но не большие. Некоторым администраторам такой способ работы не нравится; они предпочли бы жесткое прекращение доставки всей почты при достижении квоты. Опция quota_is_inclusive позволяет изменить поведение Exim'а. По умолчанию установлено значение true, что сохраняет поведение по умолчанию. Если вы измените значение по умолчанию, установив:

quota_is_inclusive = false

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

9.4.8 Предупреждения о квотах

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

Опция quota_warn_threshold расширяется так же, как и quota. Если результирующее значение больше нуля и успешная доставка текущего сообщения приводит к тому, что размер файла или общее пространство в дереве каталогов превышает заданный порог, отправляется предупреждающее сообщение. Нет необходимости устанавливать quota, чтобы использовать эту опцию[19], но если quota установлена, порог можно указать в процентах, поставив после значения знак процента. Например:

quota = 10M
quota_warn_threshold = 75%

Само предупреждающее сообщение определяется опцией quota_warn_message, которая должна начинаться со строки заголовка To:, содержащей получателя(ей). Строка Subject: также обычно должна быть указана. По умолчанию:

quota_warn_message = \
  To: $local_part@$domain\n\
  Subject: Your mailbox\n\
  \n\
  This message is automatically created by mail delivery\
  software.\n\n\
  The size of your mailbox has exceeded a warning threshold that\n\
  is set by the system administrator.\n
  1. Поэтому его можно использовать с системными квотами.

9.4.9 Уведомление comsat

comsat — это серверный процесс, который прослушивает отчеты о входящей почте и уведомляет вошедших в систему пользователей, которые запросили уведомление о поступлении почты. Он делает это, отправляя сообщения «You have mail» (У вас есть почта) на их терминалы. Если установлено значение notify_comsat, appendfile информирует comsat об успешной доставке.

9.4.10 Обзор параметров appendfile

В этом разделе приведены параметры, характерные для транспорта appendfile. Другие параметры, которые можно установить для appendfile, описаны ранее (c= 9.2, 9.3). Конечно, любой общий параметр транспорта (s= 8.11) также может быть установлен для appendfile.

allow_fifo (Boolean, default = false)

Если вы хотите доставлять сообщения в FIFO (именованные каналы), вы должны установить для этого параметра значение true, потому что appendfile по умолчанию не будет доставлять сообщения в FIFO. Если ни один процесс не читает именованный канал во время доставки, доставка откладывается.

allow_symlink (Boolean, default = false)

Если вы хотите доставлять сообщения в файлы с помощью символических ссылок, вы должны установить эту опцию в true, потому что по умолчанию appendfile не будет доставлять сообщения в такие файлы.

check_group (Boolean, default = false)

Группа владельца файла проверяется, чтобы убедиться, что она совпадает с группой, в которой выполняется процесс доставки, когда этот параметр установлен. Значение по умолчанию не задано, поскольку режим файла по умолчанию — 0600, что означает, что группа не имеет значения.

check_owner (Boolean, default = true)

Если этот параметр отключен, право собственности на существующий файл почтового ящика не проверяется.

create_directory (Boolean, default = true)

Если эта опция установлена, Exim создает все отсутствующие родительские каталоги для файла, который он собирается записать. Режим созданного каталога задается параметром directory_mode.

create_file (string, default = anywhere)

Этот параметр ограничивает расположение файлов, создаваемых транспортом. Он должен быть установлен на одно из значений anywhere, inhome или belowhome.

directory (string, default = unset)

Этот параметр является взаимоисключающим с параметром file. Когда он установлен, строка расширяется, и сообщение доставляется в новый файл или файлы в указанном каталоге или ниже, а не добавляется в один файл почтового ящика.

directory_mode (octal integer, default = 0700)

Если appendfile создает какие-либо каталоги в результате использования опции create_directory, режим определяется этой опцией.

file (string, default = unset)

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

file_format (string, default = unset)

Эта опция требует от транспорта проверки формата существующего файла перед добавлением к нему. Проверка состоит в сопоставлении определенной строки в начале файла.

file_must_exist (Boolean, default = false)

Если этот параметр имеет значение true, файл, указанный параметром file, должен существовать, в противном случае возникает ошибка. Иначе он создается, если он не существует.

lock_fcntl_timeout (time, default = 0s)

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

lock_interval (time, default = 3s)

Указывает время ожидания между попытками заблокировать файл.

lock_retries (integer, default = 10)

Указывает максимальное количество попыток блокировки файла. Нулевое значение рассматривается как 1.

lockfile_mode (octal integer, default = 0600)

Это определяет режим созданного файла блокировки, когда файл блокировки используется.

lockfile_timeout (time, default = 30m)

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

maildir_format (Boolean, default = false)

Если этот параметр установлен вместе с параметром directory, доставка осуществляется в новый файл в формате maildir, который используется другим почтовым программным обеспечением.

maildir_retries (integer, default = 10)

Этот параметр указывает количество повторных попыток записи файла в формате maildir.

maildir_tag (string, default = unset)

Этот параметр применяется только к доставке в формате maildir. Он расширяется и добавляется к именам вновь доставленных файлов сообщений.

mbx_format (Boolean, default = false)

Если mbx_format задан с параметром file, сообщение добавляется к файлу почтового ящика в формате MBX вместо традиционного формата Berkeley Unix. Если ни один из параметров блокировки не упоминается в конфигурации, предполагается использование use_mbx_lock, а для других параметров блокировки по умолчанию установлено значение false.

mode (octal integer, default = 0600)

Если создается файл почтового ящика, ему присваивается этот режим. Если он уже существует и имеет более широкие разрешения, они сводятся к этому режиму. Если он имеет более узкие разрешения, возникает ошибка, если только для режима mode_fail_narrower не установлено значение false. Однако, если доставка является результатом команды save в файле фильтра, указывающего определенный режим, режим выходного файла всегда принудительно принимает это значение, и этот параметр игнорируется.

mode_fail_narrower (Boolean, default = true)

Этот параметр применяется, когда существующий файл почтового ящика имеет более узкий режим, чем указанный параметром mode. Если mode_fail_narrower имеет значение true, доставка откладывается («mailbox has the wrong mode» (у почтового ящика неправильный режим)); в противном случае Exim продолжит попытку доставки, используя существующий режим файла.

notify_comsat (Boolean, default = false)

Если этот параметр установлен, демон comsat уведомляется после каждой успешной доставки в почтовый ящик пользователя. Это демон, который уведомляет зарегистрированных пользователей о входящей почте.

quota (string, default = unset)

Эта опция накладывает ограничение на размер файла, к которому Exim добавляет, или на общее пространство, используемое в дереве каталогов, если опция directory установлена. После раскрытия строка должна быть числовой, за которой может следовать K или M.

quota_filecount (integer, default = 0)

Этот параметр применяется, когда установлен параметр directory. Он ограничивает общее количество файлов в каталоге (например, ограничение inode в системных квотах). Его можно использовать только в том случае, если установлена quota. Нулевое значение указывает отсутствие ограничения.

quota_is_inclusive (Boolean, default = true)

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

quota_size_regex (string, default = unset)

Эта опция используется, когда Exim вычисляет количество места, используемого в каталоге, путем сложения размеров всех файлов сообщений в нем. Она не раскрывается, а интерпретируется как регулярное выражение, которое применяется к каждому имени файла. Если оно соответствует и захватывает одну строку, эта строка интерпретируется как текстовое представление размера файла.

quota_warn_message (string, default = see description)

Эта строка расширяется и вставляется в начале предупреждающих сообщений, которые генерируются в результате настройки quota_warn_threshold. Она должна начинаться со строки заголовка To:, определяющей получателя сообщения.

quota_warn_threshold (string, default = 0)

Эта опция расширяется так же, как и quota. Если результирующее значение больше нуля и доставка сообщения приводит к тому, что размер файла или общее пространство в дереве каталогов превышает заданный порог, отправляется предупреждающее сообщение. Содержание и получатели сообщения определяются параметром quota_warn_message. Если также установлена quota, порог можно указать в процентах от нее, поставив после значения знак процента.

use_fcntl_lock (Boolean, default = true)

Этот параметр управляет использованием функции fcntl() для блокировки файла для монопольного использования при добавлении сообщения.

use_lockfile (Boolean, default = true)

Если эта опция выключена, Exim не пытается создать файл блокировки при добавлении к файлу.

use_mbx_lock (Boolean, default = see description)

Установка этого параметра указывает, что должны использоваться специальные правила блокировки MBX. Он устанавливается по умолчанию, если установлен mbx_format и ни один из параметров блокировки не упоминается в конфигурации. Правила блокировки такие же, как и в библиотеке c-client, лежащей в основе Pine4, и входящих в ее состав демонах IMAP4 и POP. Правила разрешают общий доступ к почтовому ящику. Однако этот тип блокировки не работает, когда почтовый ящик смонтирован через NFS.

9.5 Транспорт pipe

Транспорт pipe доставляет сообщение, создавая канал и новый процесс, который запускает данную программу. Сообщение записывается в канал транспортом и считывается с другого конца канала внешней программой. Есть много распространенных применений этого механизма. Например:

  1. Некоторые из этих функций также доступны в файлах фильтров Exim.

9.5.1 Определение запускаемой команды

Если канал настраивается с помощью псевдонимов, переадресации или из файла фильтра, запускаемая команда определяется этим механизмом. Например, файл псевдонима может содержать строку:

majordomo: |/usr/local/mail/majordomo

что приводит к тому, что сообщения, адресованные локальной части majordomo, передаются процессу, выполняющему команду /usr/local/mail/majordomo. В качестве другого примера, файл фильтра пользователя может содержать команду:

pipe /usr/bin/vacation

которая передает сообщение в /usr/bin/vacation. Подходящим транспортом для использования в этих случаях является транспорт в конфигурации по умолчанию:

address_pipe:
  transport = pipe
  ignore_status
  return_output

где команда не указана, потому что команда поставляется с адресом, который доставляется (другие параметры объясняются позже в этой главе).

Если имя команды не является абсолютным путем, Exim ищет его в каталогах, перечисленных с помощью опции пути, разделенной двоеточиями, настройка по умолчанию:

path = /usr/bin

Таким образом, пример фильтра можно было бы представить так:

pipe vacation

Если роутер передает сообщение транспорту pipe напрямую, без использования псевдонимов или переадресации, команда задается параметром command на самом транспорте. Например, если вы хотите, чтобы ваш хост выполнял все локальные доставки с помощью procmail, вы можете настроить транспорт следующим образом:

procmail_pipe:
  driver = pipe
  command = /opt/local/bin/procmail -d $local_ part
  return_path_add
  delivery_date_add
  envelope_to_add
  check_string = "From "
  escape_string = ">From "
  group = mail

Роутер для приема адресов для этого транспорта может выглядеть так:

procmail:
  driver = accept
  check_local_user
  transport = procmail_pipe

В этом примере канал запускается от имени локального пользователя, но с группой, настроенной как mail. Альтернативой является запуск канала от имени конкретного пользователя, такого как mail или exim. Однако в этом случае вы должны сделать так, чтобы procmail доверял этому пользователю в предоставлении правильного адреса отправителя. Если вы не укажете ни параметр group, ни параметр user, команда конвейера запускается от имени локального пользователя. Домашний каталог — это домашний каталог пользователя по умолчанию.

9.5.2 uid и gid для команды

Процесс, который pipe устанавливает для своей команды, работает под тем же uid и gid, что и сам транспорт pipe. Как и в случае с любой локальной доставкой, этот пользователь и группа могут быть указаны параметрами user и group либо на транспорте, либо на вызывающем его роутере, либо они могут быть взяты из данных пароля локального пользователя роутером, который установлен check_local_user.

9.5.3 Запуск команды

Exim по умолчанию не запускает команду для транспорта pipe под оболочкой. Это имеет два преимущества:

В документации к программам, которые предполагается запускать из MTA через конвейер (например, procmail), часто можно найти рекомендацию размещать:

IFS=" "

в начале команды. Это предполагает, что команда будет выполняться в оболочке, и это гарантирует, что переменная оболочки IFS (которая определяет разделитель аргументов) установлена на пробел. При использовании транспорта pipe в режиме по умолчанию (то есть без использования оболочки) этот параметр не только не требуется, но и приведет к сбою команды, поскольку оболочка не используется.

Разбор командной строки

Расширение строки применяется к командной строке, за исключением случаев, когда она исходит из традиционного файла .forward (расширяются команды из файла фильтра). Однако перед выполнением расширения командная строка разбивается на имя команды и список аргументов. Аргументы без кавычек разделяются пробелами. Если аргумент содержит пробелы, он должен быть заключен в одинарные или двойные кавычки. В аргументах, заключенных в двойные кавычки, обратная косая черта обычно интерпретируется как управляющий символ. Этого не происходит для аргументов в одинарных кавычках.

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

command = /some/path ${if eq{$local_part}{ab123}{xxx}{yyy}}

не получится, потому что она разбита на три пункта:

/some/path
S{if
eq{$local_part}{ab123}{xxx}{yyy}}

и второй и третий не являются допустимыми элементами расширения. Вы должны написать:

command = /some/path "${if eq{$local_part}{ab123}{xxx}{yyy}}"

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

Особая обработка имеет место, когда аргумент состоит именно из текста $pipe_addresses. Это не общая переменная расширения; единственное место, где эта строка распознается, — это когда она появляется в качестве аргумента для команды канала или транспортного фильтра. Это приводит к тому, что каждый обрабатываемый адрес вставляется в список аргументов как отдельный аргумент. Это упрощает для команды обработку отдельных адресов и позволяет избежать проблем с пробелами или метасимволами оболочки. Это полезно, когда транспорт pipe обрабатывает группы адресов в пакете (9.2).

Использование оболочки

Если для запуска команды требуется оболочка, ее, конечно, можно явно указать как часть команды. Существуют также обстоятельства, при которых существующие команды (например, в существующих файлах .forward) предполагают выполнение в оболочке и не могут быть легко изменены. Чтобы учесть эти случаи, существует опция use_shell, которая изменяет способ работы транспорта pipe. Вместо того, чтобы разбивать командную строку, как только что было описано, она расширяет ее как единую строку и передает результат в /bin/sh. Этот механизм по своей сути менее безопасен и, кроме того, использует дополнительный процесс.

9.5.4 Командная среда

Доставляемое сообщение доставляется команде в стандартный поток ввода, а стандартный поток вывода и стандартный поток ошибок подключаются к одному каналу, который считывается Exim. Обработка вывода описана далее в этом разделе. Переменные среды, устанавливаемые при вызове команды, показаны в таблице 9-1.

Табл. 9-1: Переменные среды для команд конвейера
Переменная среды Содержание
DOMAIN Домен адреса
HOME «Домашняя» директория
HOST Имя хоста, если оно предоставляется роутером
LOCAL_PART См. описание далее в этом разделе
LOCAL_PART_PREFIX См. описание далее в этом разделе
LOCAL_PART_SUFFIX См. описание далее в этом разделе
LOGNAME См. описание далее в этом разделе
MESSAGE_ID Идентификатор сообщения
PATH Как указано в опции path
QUALIFY_DOMAIN Настроенный квалификационный домен
RECIPIENT Полный адрес получателя
SENDER Отправитель сообщения (пусто, если отказ)
SHELL /bin/sh
TZ Значение параметра timezone, если установлено
USER См. описание далее в этом разделе

Параметр environment можно использовать для добавления дополнительных переменных в эту среду; его значение представляет собой список разделенных двоеточием настроек <name>=<value>, например:

environment = SENDER_HOST=$sender_host_address

Когда более чем один адрес обрабатывается одним запуском транспорта pipe (как следствие того, что значение batch_max установлено больше единицы), переменные, зависящие от локальной части, не устанавливаются, а DOMAIN устанавливается только в том случае, если все адреса имеют один и тот же домен.

В случае одного адреса получателя LOCAL_PART устанавливается на локальную часть адреса с удаленным любым префиксом или суффиксом. Аффиксы помещаются в LOCAL_PART_PREFIX и LOCAL_PART_SUFFIX соответственно. LOGNAME и USER имеют то же значение, что и LOCAL_PART, для совместимости с другими MTA. Переменная RECIPIENT содержит полный адрес, включая аффиксы локальной части и домен.

HOST устанавливается только тогда, когда транспорт pipe вызывается с роутера, который предоставляет имя хоста (например, при обработке пакетного SMTP).

Если параметр home_directory транспорта установлен, его значение используется для переменной среды HOME. В противном случае используется любое значение, установленное роутером (6.3.4).

Параметр маски создания файла (umask) в процессе канала берется из значения параметра umask, которое по умолчанию равно 022. Это значение означает, что для любых файлов, создаваемых процессом, не установлены биты разрешения на запись для группы или для всех остальных.

9.5.5 Синхронизация команды

Тайм-аут по умолчанию в один час накладывается на процесс, выполняющий команду. Если команда не завершается в течение этого времени, она уничтожается. Обычно это приводит к сбою доставки. Значение тайм-аута может быть изменено опцией timeout. Нулевой временной интервал не указывает время ожидания, но это не рекомендуется. Чтобы убедиться, что любые последующие процессы, созданные командой, также будут уничтожены, Exim делает начальный процесс лидером группы процессов и уничтожает всю группу процессов по тайм-ауту. Однако это действие нарушается, если какой-либо из процессов запускает новую группу процессов.

9.5.6 Ограничение на выполнение команд

Если пользователям разрешено настраивать команды конвейера из файлов пересылки или фильтрации, вы можете захотеть ограничить, какие команды они могут указывать. Параметры allow_commands и restrict_to_path предоставляют два разных способа сделать это. Если ни один из них не установлен, нет ограничений на выполнение команд; в противном случае допускаются только команды, разрешенные той или иной из этих опций.

Значение allow_commands расширяется, а затем интерпретируется как список разрешенных имен команд, разделенных двоеточиями. Они не обязательно должны быть абсолютными путями; опция path используется для разрешения относительных путей. Если установлено restrict_to_path, любое имя команды, не указанное в allow_commands, не должно содержать косых черт (то есть это должно быть простое имя команды); оно ищется только в каталогах, перечисленных в опции path. Например, рассмотрим этот параметр:

allow_commands = /usr/ucb/vacation

Если restrict_to_path не установлено, разрешена только команда /usr/ucb/vacation. Однако, если конфигурация:

allow_commands = /usr/ucb/vacation
path = /usr/local/bin
restrict_to_path

то помимо /usr/ucb/vacation можно указать любую команду из /usr/local/bin.

Применение ограничений, указанных в параметрах allow_commands и restrict_to_path, может быть выполнено только тогда, когда команда запускается непосредственно из транспорта pipe без промежуточной оболочки. Следовательно, эти параметры могут быть не установлены, если установлен use_shell.

9.5.7 Обработка ошибок команд

Если ignore_status равен true, статус (код выхода), возвращаемый процессом, запускающим команду, игнорируется, и Exim всегда ведет себя так, как если бы был возвращен ноль (успех). Если ignore_status имеет значение false (значение по умолчанию), статус, возвращаемый процессом, может указывать на временный или постоянный сбой.

Временные сбои — это значения, перечисленные в опции temp_errors. Он содержит список чисел, разделенных двоеточием, или звездочку, что означает «все ошибки». Список по умолчанию содержит значения ошибок EX_TEMPFAIL и EX_CANTCREAT, которые обычно определяются в /usr/include/sysexits.h. После одной из этих ошибок Exim откладывает доставку и пытается позже.

Любое другое значение статуса рассматривается как постоянная ошибка, и адрес возвращается. Невыполнение команды в транспорте pipe по умолчанию рассматривается как постоянный сбой. Наиболее распространенными причинами этого являются несуществующие команды и команды, доступ к которым невозможен из-за их настроек разрешений. Однако, если установлено freeze_exec_fail, сбой выполнения обрабатывается особым образом и приводит к зависанию сообщения независимо от значения параметра ignore_status.

9.5.8 Обработка вывода команды

Все, что команда записывает в свои стандартные потоки ошибок или стандартные потоки вывода, «перехватывается» Exim. Максимальный объем вывода, который может создать команда, ограничен параметром max_output (по умолчанию 20 КБ) в качестве защиты от программ, вышедших из-под контроля. Если предел превышен, процесс, выполняющий команду, уничтожается. Обычно это приводит к сбою доставки. Из-за эффектов буферизации объем вывода может немного превысить лимит до того, как Exim заметит.

Вы можете управлять тем, что происходит с выводом, устанавливая ряд параметров. Три опции задают условия, при которых первая строка любого вывода записывается в основной журнал Exim:

log_defer_output log   # если доставка отложена
log _fail_output log   # если доставка не удалась
log_output             # всегда

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

Вы также можете организовать возврат вывода отправителю сообщения в составе отчета о сбое доставки. Если вы установите для return_output значение true, любой вывод будет считаться ошибкой доставки, независимо от кода возврата команды. Параметр return_fail_output работает таким же образом, но применяется только в том случае, если процесс выполнения команды возвращает невременный код ошибки (то есть код возврата не равен нулю и не входит в список временных ошибок).

Параметры return_output и return_fail_output применяются только в том случае, если у сообщения есть непустой отправитель (то есть, когда оно само не является рикошетом). Если ни один из них не установлен, выходные данные отбрасываются (после необязательной регистрации). Однако даже в этой конфигурации, если объем вывода превышает max_output, команда уничтожается на том основании, что она, вероятно, ведет себя неправильно.

9.5.9 Обзор параметров pipe

В этом разделе обобщены параметры, характерные для транспорта pipe. Другие параметры, которые можно задать для pipe, описаны ранее (9.2, 9.3). Конечно, любой общий параметр транспорта (8.11) также может быть установлен для pipe.

allow_commands (string, default = unset)

Строка расширяется, а затем интерпретируется как список разрешенных команд, разделенных двоеточиями. Если restrict_to_path не установлено, разрешены только команды из списка allow_commands. Они не обязательно должны быть абсолютными путями; опция path используется для относительных путей.

command (string, default = unset)

Этот параметр не нужно устанавливать, когда pipe используется для доставки в каналы, полученные из адресных расширений (обычно под именем экземпляра address_pipe). В других случаях параметр должен быть установлен, чтобы предоставить команду для запуска. Он не обязательно должен давать абсолютный путь (см. параметр path).

environment (string, default = unset)

Этот параметр используется для добавления дополнительных переменных в среду, в которой выполняется команда. Его значение представляет собой расширенную строку, которая затем интерпретируется как список параметров среды, разделенных двоеточиями, в форме <name>=<value>.

freeze_exec_fail (Boolean, default = false)

Сбой при выполнении команды в транспорте pipe по умолчанию рассматривается как любой другой сбой при выполнении команды. Однако, если установлен параметр freeze_exec_fail, сбой выполнения обрабатывается особым образом и приводит к зависанию сообщения независимо от значения параметра ignore_status.

ignore_status (Boolean, default = false)

Если эта опция истинна, статус, возвращаемый процессом, настроенным для запуска команды, игнорируется, и Exim ведет себя так, как будто был возвращен ноль.

log_defer_output (Boolean, default = false)

Если этот параметр установлен и статус, возвращаемый командой, является одним из перечисленных в temp_errors, и был произведен какой-либо вывод, первая его строка записывается в основной журнал.

log_fail_output (Boolean, default = false)

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

log_output (Boolean, default = false)

Если этот параметр установлен и команда возвращает какие-либо выходные данные, первая строка выходных данных записывается в основной журнал независимо от кода возврата.

max_output (integer, default = 20K)

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

path (string list, default = /usr/bin)

Этот параметр указывает строку, которая устанавливается в переменной окружения PATH подпроцесса. Если параметр команды не дает абсолютного пути, команда ищется в каталогах PATH.

restrict_to_path (Boolean, default = false)

Когда этот параметр установлен, любое имя команды, не указанное в allow_commands, не должно содержать косых черт. Команда ищется только в каталогах, перечисленных в опции path.

return_fail_output (Boolean, default = false)

Если этот параметр имеет значение true, а команда завершается с кодом возврата, отличным от нуля или одного из перечисленных в temp_errors, все выходные данные возвращаются в сообщении об ошибке доставки. Однако, если у сообщения нет отправителя (т. е. это рикошет), выходные данные команды отбрасываются.

return_output (Boolean, default = false)

Если этот параметр имеет значение true и команда выдает какие-либо выходные данные, считается, что доставка не удалась, независимо от кода возврата из команды, и выходные данные возвращаются в сообщении о возврате. В противном случае вывод просто отбрасывается. Однако, если у сообщения нет отправителя (т. е. оно само является рикошетом), выходные данные команды всегда отбрасываются, независимо от настройки этого параметра.

temp_errors (string, default = see description)

Эта опция содержит список чисел, разделенных двоеточием. Если ignore_status имеет значение false и команда завершается с кодом возврата, который соответствует одному из чисел, сбой рассматривается как временный и доставка откладывается. Параметр по умолчанию содержит коды, определенные EX_TEMPFAIL и EX_CANTCREAT в sysexits.h. Если Exim скомпилирован в системе, которая не определяет эти макросы, он принимает значения 75 и 73 соответственно.

timeout (time, default = 1h)

Если команда не завершается в течение этого времени, она уничтожается. Обычно это приводит к сбою доставки. Нулевой временной интервал не указывает тайм-аут.

umask (octal integer, default = 022)

Это указывает параметр umask для процесса, выполняющего команду.

use_shell (Boolean, default = false)

Если этот параметр установлен, это приводит к тому, что команда передается в /bin/sh вместо того, чтобы запускаться непосредственно из транспорта. Это менее безопасно, но необходимо в некоторых ситуациях, когда ожидается, что команда будет выполняться в оболочке, и ее нельзя легко изменить. Вы не можете использовать параметры allow_commands и limited_to_path или средство $pipe_addresses, если вы установили для параметра use_shell значение true. Команда раскрывается как одна строка и передается в /bin/sh в качестве данных для опции -c.

9.6 Транспорт lmtp

Если установка поддерживает очень большое количество учетных записей, хранение отдельных почтовых ящиков в виде отдельных файлов или каталогов становится невозможным из-за проблем с масштабированием. Одним из решений этой проблемы является использование независимого хранилища сообщений, представляющего собой программный продукт для управления почтовыми ящиками. Он предоставляет интерфейсы для добавления, чтения и удаления сообщений, но то, как они хранятся внутри, не определено, а доступ к почтовым ящикам как к обычным файлам невозможен. Одним из таких продуктов является сервер Cyrus IMAP[21].

Когда MTA доставляет сообщение в хранилище сообщений этого типа, он должен передать конверт точно так же, как и при доставке на удаленный хост, и существующий протокол SMTP кажется хорошим кандидатом для этого средства. Однако для сообщений с несколькими получателями существуют некоторые технические проблемы при использовании SMTP таким образом, что привело к определению нового протокола под названием LMTP[22]. Он очень похож на SMTP, и транспорт smtp Exim'а имеет возможность использовать LMTP вместо SMTP через соединение TCP/IP (9.1.7).

LMTP также предназначен для связи между двумя процессами, работающими на одном хосте, и именно здесь вступает в дело транспорт lmtp[23]. Фактически это нечто среднее между транспортом pipe и smtp с дополнительной возможностью доступа к сокету домена Unix. Когда он используется как pipe, он запускает команду в новом процессе и отправляет ему сообщение, но вместо того, чтобы просто записывать сообщение в канал, он взаимодействует с командой, используя протокол LMTP. Вот пример типичного транспорта lmtp, который использует команду следующим образом:

local_lmtp:
  driver = lmtp
  command = /some/local/lmtp/delivery/program
  batch_max = 20
  user = exim

Он доставляет до 20 адресов одновременно, в смеси доменов, если необходимо, работая от пользователя exim.

У транспорта lmtp есть только три собственных параметра: command, timeout и socket. Первые два работают точно так же, как и одноименные опции pipe, поэтому обсуждение здесь не повторяется.

Опция socket несовместима с command. Она указывает путь к сокету домена Unix, который будет использоваться для обмена данными LMTP вместо выполнения команды.

Поскольку весь смысл LMTP заключается в том, чтобы иметь возможность передать одну копию сообщения более чем одному получателю, для параметра batch_max обычно должно быть установлено значение, отличное от значения по умолчанию. Что касается других локальных транспортов, если user или group не заданы, значения должны быть установлены роутером, который передает адреса этому транспорту.

  1. См. http://asg.web.cmu.edu/cyrus.

  2. Если вас интересуют подробные аргументы, прочтите RFC 2033, определяющий LMTP.

  3. Поскольку LMTP не требуется в большинстве инсталляций, код для транспорта lmtp не включен в Exim, если это специально не запрошено во время сборки.

9.7 Транспорт autoreply

Транспорт autoreply не является истинным транспортом в том смысле, что он не обеспечивает доставку сообщения в обычном смысле. Вместо этого он генерирует другое, новое почтовое сообщение. Однако исходное сообщение может быть включено в сгенерированное сообщение.

Этот транспорт обычно запускается в результате фильтрации почты, где типичным примером является отправка сообщения «vacation» (отпуск, каникулы). Что пользователь хочет сделать, так это отправить автоматический ответ на входящую почту, сообщая, что он или она отсутствует и, следовательно, некоторое время не сможет читать почту. В следующей главе мы углубимся в фильтрацию почты, а вот выдержка из файла фильтра, выполняющего эту работу:

if personal and not error_message then
   mail
   to $reply_address
   subject "Re: $h_subject"
   text "I'm away this week, but I’ll get back to you asap."
endif

При активации из файла фильтра пользователя, подобного этому, autoreply запускается под uid и gid локального пользователя с соответствующими текущим и домашним каталогами.

В попытке уменьшить вероятность каскадов сообщений сообщения, созданные транспортом autoreply, всегда принимают форму сообщений об ошибках доставки (отказов). То есть поле отправителя конверта пусто. Это должно помешать хостам, которые их получают, генерировать новые автоматические ответы по очереди.

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

Если какие-либо общие параметры для управления заголовками (например, headers_add) установлены для транспорта autoreply, они применяются только к копии исходного сообщения, которая включается в сгенерированное сообщение, когда установлено значение return_message. Они не применяются к самому сгенерированному сообщению и поэтому не очень полезны.

Отсутствие получателей для сообщения, которое генерирует транспорт autoreply, не считается ошибкой. Сообщение просто отбрасывается. Это означает, что автоответы, адресованные $sender_address, когда он пуст (поскольку входящее сообщение является рикошетом), не вызывают проблем.

9.7.1 Параметры сообщения

Есть два возможных источника данных для построения нового сообщения: входящий адрес и параметры транспорта. Когда файл фильтра пользователя содержит команду mail или vacation, все данные из команды (получатели, строки заголовков, тело) прикрепляются к адресу, который передается транспорту autoreply. В этом случае опции транспорта игнорируются.

С другой стороны, когда транспорт активируется непосредственно роутером (то есть не из файла фильтра), данные для сообщения берутся из параметров транспорта. Другими словами, параметры в конфигурации транспорта используются только тогда, когда он получает адрес, который не содержит никакой собственной ответной информации. Таким образом, сообщение полностью определяется файлом фильтра или полностью транспортом; он никогда не строится из смеси данных.

Если данные не поступили из команды фильтра, параметры транспорта, определяющие сообщение, следующие:

bcc, cc, to

Эти параметры определяют получателей и соответствующие строки заголовка.

from

Этот параметр определяет строку заголовка From:; если это не соответствует пользователю, запускающему транспорт, Exim по умолчанию добавляет заголовок Sender:, но это поведение можно изменить, установив опции local_from_check и local_from_prefix (20.2.1).

reply_to

Этот параметр определяет строку заголовка Reply-To:. Обратите внимание, что имя параметра содержит подчеркивание, а не дефис.

subject

Этот параметр определяет строку заголовка Subject:.

text

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

file

Этот параметр указывает файл, содержимое которого составляет тело сообщения. Если установлено file_optional, ошибка не генерируется, если файл не существует или не может быть прочитан. Если установлено file_expand, содержимое файла передается через расширитель строк, строка за строкой, по мере добавления к сообщению. Это позволяет изменять содержание сообщения в зависимости от обстоятельств.

headers

Эта опция указывает дополнительные строки заголовка, которые должны быть добавлены к сообщению. Можно добавить несколько заголовков, заключив текст в кавычки и используя \n для их разделения.

return_message

Этот параметр указывает, что исходное сообщение должно быть добавлено в конец вновь созданного сообщения. На возвращаемую сумму распространяется общая опция return_size_limit.

Например, вот транспорт, который можно использовать для отправки обратно сообщения, объясняющего, что конкретный список рассылки больше не существует:

auto_message:
  driver = autoreply
  to = $sender_address
  subject = The mailing list $local_part is no more
  text = Your message to $local_part@$domain is being returned\n\
    because the $local_part mailing list is no longer in use.
  return_message
  user = exim

Вы можете отправлять сообщения на этот транспорт с помощью роутера, например:

old_lists:
  driver = accept
  domains = mailing.list.domain
  local_parts = /etc/dead/lists
  transport = auto_message

Это выполняется только для одного домена, для локальных частей, перечисленных в файле.

9.7.2 Однократные сообщения

Традиционное «vacation» использование autoreply заключается в отправке сообщения только один раз или не более одного раза за определенный интервал времени каждому отправителю. Это можно настроить, установив параметр once для имени файла, который затем используется для записи получателей создаваемых сообщений вместе с временем отправки сообщений. По умолчанию одному получателю отправляется только одно сообщение. Однако если для параметра once_repeat задано время больше нуля, может быть отправлено другое сообщение, если с момента отправки предыдущего сообщения прошло столько времени. Например:

once_repeat = 10d

Настройки once и once_repeat используются только в том случае, если данные для сообщения берутся из собственных опций транспорта. Для вызовов autoreply, исходящих из фильтров сообщений, используются настройки из фильтра.

9.7.3 Ведение журнала отправленных сообщений

Опция log называет файл, в котором регистрируется запись каждого сообщения, обрабатываемого транспортом. Значение опции расширяется, и при необходимости создается файл с режимом, заданным mode.

Настройка log используется только в том случае, если данные для сообщения берутся из собственных опций транспорта. Для вызовов autoreply, исходящих из фильтров сообщений, используются настройки из фильтра.

9.7.4 Обзор параметров autoreply

В этом разделе кратко описаны параметры, характерные для транспорта autoreply. Конечно, любой общий параметр транспорта (8.11) также может быть установлен для autoreply.

bcc (string, default = unset)

Эта опция указывает адреса, которые должны получать «слепые копии» (blind carbon copies) сообщения, когда сообщение указано транспортом. Строка расширяется.

cc (string, default = unset)

Этот параметр указывает получателей сообщения и содержимое заголовка Cc:, когда сообщение определяется транспортом. Строка расширяется.

file (string, default = unset)

Содержимое файла отправляется как тело сообщения, если сообщение указано транспортом. Строка расширяется. Если установлены и file, и text, текстовая строка идет первой.

file_expand (Boolean, default = false)

Если это установлено, содержимое файла, указанного параметром file, подвергается расширению строки по мере добавления к сообщению.

file_optional (Boolean, default = false)

Если этот параметр имеет значение true, ошибка не генерируется, если файл, указанный в параметре file, не существует или не может быть прочитан.

from (string, default = unset)

Этот параметр указывает содержимое заголовка From:, когда сообщение определяется транспортом. Строка расширяется.

headers (string, default = unset)

Этот параметр указывает дополнительные заголовки RFC 2822, которые должны быть добавлены к сообщению, когда сообщение определяется транспортом. Строка расширяется.

log (string, default = unset)

Эта опция дает имя файлу, в котором регистрируется запись о каждом отправленном сообщении, когда сообщение указано транспортом. Строка расширяется.

mode (octal integer, default = 0600)

Если необходимо создать файл журнала или файл «once», используется этот режим.

once (string, default = unset)

Эта опция дает имя базе данных DBM, в которой хранится запись о каждом получателе, когда сообщение указывается транспортом. Строка расширяется.

once_repeat (time, default = 0s)

Этот параметр указывает временной интервал, после которого тому же получателю может быть отправлено другое сообщение, если сообщение указано транспортом.

reply_to (string, default = unset)

Этот параметр указывает содержимое заголовка Reply-To:, когда сообщение определяется транспортом. Строка расширяется.

return_message (Boolean, default = false)

Если это установлено, копия исходного сообщения возвращается с новым сообщением, в соответствии с максимальным размером, установленным в параметре общей конфигурации return_size_limit.

subject (string, default = unset)

Этот параметр указывает содержимое строки заголовка Subject:, когда сообщение определяется транспортом. Строка расширяется.

text (string, default = unset)

Это указывает одну строку, которая будет использоваться в качестве тела сообщения, когда сообщение указано транспортом. Строка расширяется. Если заданы и text, и file, текст идет первым.

to (string, default = unset)

Этот параметр указывает получателей сообщения и содержимое заголовка To:, когда сообщение определяется транспортом. Строка расширяется.

Глава 10
Фильтрация сообщений

Когда сообщение проходит через Exim, оно может быть проверено несколькими различными фильтрами сообщений (message filters). Мы уже обсуждали транспортные фильтры, возникающие при транспортировке сообщения (8.8). В этой главе мы рассмотрим фильтрацию другого типа, которая происходит, когда сообщение обрабатывается, чтобы определить, куда оно должно быть доставлено. Фильтрация на этом этапе предоставляет еще один способ управления доставкой сообщения. Мы уже упоминали пользовательские и системные фильтры без особых пояснений; пора исправить это упущение. Эти фильтры работают следующим образом:

Перед подробным описанием синтаксиса команд фильтрации мы рассмотрим несколько простых примеров, чтобы дать вам представление о том, как работает фильтрация.

  1. Имя файла .forward используется чаще всего, но оно не встроено в Exim. В конфигурации может быть указано другое имя.

10.1 Примеры команд фильтра

Эти примеры написаны с точки зрения файла фильтра пользователя. Во-первых, простая переадресация:

# Exim filter
deliver baggins@rivendell.middle-earth.example

Первая строка указывает, что файл является файлом фильтра, а не традиционным файлом .forward. Единственная команда в этом файле — это инструкция доставить сообщение по определенному адресу. Этот конкретный файл не делает ничего такого, чего не мог бы сделать традиционный .forward, и точно эквивалентен файлу, содержащему:

baggins@rivendell.middle-earth.example

В следующем примере показана обработка отпуска с использованием традиционных средств (то есть путем запуска /usr/ucb/vacation), предполагая, что .vacation.msg и другие файлы были настроены в домашнем каталоге:

# Exim filter
unseen pipe "/usr/ucb/vacation $local_part"

Команда pipe — это инструкция для запуска данной программы и передачи ей сообщения через канал. Аргументы, передаваемые командам фильтрации, всегда расширяются, чтобы можно было включить ссылки на такие переменные, как $local_part.

Слово unseen, которое предшествует команде, указывает Exim'у не рассматривать эту команду как значимую. Это означает, что после фильтрации Exim продолжает доставлять сообщение обычным способом. То есть одна копия сообщения отправляется в пайп, а другая добавляется в почтовый ящик пользователя. Без unseen доставка канала была бы единственной доставкой, которая была бы осуществлена.

Этот пример также не делает ничего такого, чего не мог бы сделать традиционный .forward. Для пользователя spqr это эквивалентно:

\spqr, |/usr/ucb/vacation spqr

Однако обработка отпуска может быть выполнена полностью внутри фильтра Exim, без запуска другой программы. Если в домашнем каталоге есть файл с именем .vacation.msg, этого файла фильтра достаточно:

# Exim filter
if personal then vacation endif

Этот пример показывает то, что традиционный файл .forward не может сделать. Команда if позволяет фильтру проверять определенные условия, прежде чем предпринимать какие-либо действия. В данном примере это проверка состояния personal. Мы подробно объясним, что это значит позже, а сейчас вам просто нужно знать, что это различие между сообщениями, которые адресованы лично пользователю, и теми, которые не являются (например, сообщения из списка рассылки). Если входящее сообщение является личным, запускается команда фильтра vacation. Эта команда создает автоматический ответ отправителю входящего сообщения так же, как это делает /usr/ucb/vacation.

Помимо отправки сообщения об отпуске, мы хотим, чтобы Exim продолжил обработку сообщения и продолжил обычную доставку. В данном случае это происходит автоматически, потому что команда vacation не является существенным действием. Нет необходимости использовать unseen, потому что он используется по умолчанию.

Есть несколько условий, которые вы можете проверить с помощью команды if. В следующем примере проверяется содержимое строки заголовка Subject: и для определенных тем организуется доставка сообщения в определенный файл вместо обычного почтового ящика:

# Exim filter
if $header_subject: contains "empire" or
   $header_subject: contains "foundation"
then
  save $home/mail/f+e
endif

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

Следующий пример иллюстрирует использование регулярного выражения для немного необычной цели. Оно извлекает день недели из переменной $tod_full, которая содержит дату и время в следующем формате:

Wed, 16 Oct 2002 09:51:40 +0100

Команда фильтра ищет сообщения, не отмеченные как «urgent» (срочные), и сохраняет их в файлы, имена которых содержат день недели:

# Exim filter
if $header_subject: does not contain "urgent" and
  $tod_full matches "^(...),"
then
  save $home/mail/$1
endif

Условие matches проверяет строку с помощью регулярного выражения. В этом примере регулярное выражение всегда совпадает, а его круглые скобки извлекают название дня (первые три символа $tod_full) в переменную $1. Затем это можно использовать в следующей команде.

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

# Exim filter
if $sender_address contains "@spam.site.example" and
   $sender address does not contain "postmaster@"
then
  seen finish
endif

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

Этот последний пример является напоминанием о том, как пользователь может обращаться с несколькими личными почтовыми ящиками, как описано в разделе 5.5:

# Exim filter
if $local_part_suffix is "-foo"
then
  save $home/mail/foo
elif $local_part_suffix is "-bar"
then
  save $home/mail/bar
endif

Оставшаяся часть этой главы подробно описывает формат файлов фильтров и отдельные команды фильтрации.

10.2 Фильтрация по сравнению с внешним агентом доставки

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

Если пользователю необходимо проверить результат доставки каким-либо автоматическим способом, вместо фильтра Exim должен использоваться внешний агент доставки, такой как procmail (5.4). На первый взгляд кажется, что фильтры procmail и Exim обеспечивают почти одинаковую функциональность, хотя и с очень разным синтаксисом. Однако есть несколько важных отличий:

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

10.3 Настройка пользовательского фильтра

Пользовательские фильтры обрабатываются роутером redirect как альтернативная форма пересылки информации. Если для роутера установлен параметр allow_filter, а первая строка данных перенаправления начинается с текста:

# Exim filter

в любом регистре и с любым интервалом файл интерпретируется как файл фильтра, а не как обычный список пересылки. Остальная часть фильтра должна соответствовать синтаксису фильтрации, описанному далее в этой главе.

Как и в случае обычного списка перенаправления, если фильтр устанавливает какие-либо доставки в каналы или файлы, роутер должен иметь настройки pipe_transport или file_transport, чтобы указать, как должны выполняться такие доставки. Третий транспорт, указанный в reply_transport, требуется для фильтров, которые генерируют автоматические ответы на сообщения (чего не может сделать обычный список перенаправления).

Некоторые администраторы могут не захотеть поддерживать автоматические ответы или доставку файлов и каналов в пользовательских фильтрах. Кроме того, в некоторых средах может потребоваться отключить определенные типы расширения строки. Например, на хосте, где пользователи не имеют учетных записей для входа, но могут предоставлять свои собственные фильтры через FTP, администратор может захотеть заблокировать чтение произвольных файлов и выполнение команд. Для отключения некоторых функций пользовательских фильтров предусмотрены следующие опции роутера redirect:

forbid_file

Этот параметр отключает использование команды save в фильтрах. Он также отключает использование имен путей в данных перенаправления без фильтрации.

forbid_filter_existstest

Этот параметр отключает условие exists в строках расширения в фильтрах.

forbid_filter_lookup

Этот параметр отключает использование lookup в строках расширения в фильтрах.

forbid_filter_perl

Этот параметр отключает использование встроенного Perl в строках расширения в фильтрах[2].

forbid_pipe

Этот параметр отключает использование команды pipe в фильтрах. Он также отключает использование канала в данных перенаправления без фильтрации.

forbid_filter_readfile

Этот параметр отключает использование readfile в строках расширения в фильтрах.

forbid_filter_run

Этот параметр отключает использование run в строках расширения в фильтрах.

forbid_filter_reply

Этот параметр отключает использование команд mail и vacation в фильтрах.

  1. Поддержка Embedded Perl в любом случае доступна только в том случае, если Exim собран специально для ее включения.

10.4 Настройка системного фильтра

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

Системный фильтр включается путем установки для параметра system_filter пути к файлу, содержащему инструкции фильтра. Например:

system_filter = /etc/mail/exim.filter

Существует несколько других вариантов управления системным фильтром. Они следующие:

system_filter_directory_transport (string, default = unset)

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

system_filter_file_transport (string, default = unset)

Этот параметр задает имя транспорта, который используется, когда команда save в системном фильтре указывает путь, не заканчивающийся на /.

system_filter_group (string, default = unset)

Эта опция устанавливает gid, под которым запускается системный фильтр. Один и тот же gid используется для любых доставок каналов, файлов или автоответов, настроенных фильтром, если транспорт не переопределяет их.

system_filter_pipe_transport (string, default = unset)

Этот параметр задает имя транспорта, который используется, когда команда pipe используется в системном фильтре.

system_filter_reply_transport (string, default = unset)

Этот параметр задает имя транспорта, который используется, когда команда mail используется в системном фильтре.

system_filter_user (string, default = unset)

Эта опция устанавливает uid, под которым запускается системный фильтр. Один и тот же uid используется для любых доставок каналов, файлов или автоответов, настроенных фильтром, если транспорт не переопределяет их. Если параметр system_filter_user не установлен, системный фильтр запускается от имени пользователя root.

10.5 Тестирование файлов фильтров

Файлы фильтров, особенно сложные, всегда нужно тестировать, так как легко допустить ошибку. Exim предоставляет средство для предварительного тестирования файла фильтра перед его установкой. Оно проверяет синтаксис файла и его основные операции, а также может использоваться с обычными (не фильтрующими) файлами .forward.

Поскольку фильтр может проверять содержимое сообщений, требуется тестовое сообщение. Предположим, у вас есть новый файл пользовательского фильтра с именем new-filter и тестовое сообщение в файле с именем test-message. Для проверки фильтра можно использовать следующую команду:

exim -bf new-filter <test-message

Опция -bf сообщает Exim'у, что следующий пункт в командной строке — это имя файла фильтра, который нужно протестировать. Само тестовое сообщение подается на стандартный ввод. Если в фильтре нет тестов, зависящих от сообщения, можно использовать пустое тестовое сообщение; в противном случае сообщение должно начинаться со строк заголовка или строки-разделителя сообщений From, которая находится в традиционных файлах папок с несколькими сообщениями. Предупреждение выдается, если ни одна строка заголовка не прочитана.

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

Deliver message to: gulliver@lilliput.example
Save message to: /home/lemuel/mail/archive

означает, что одна копия сообщения будет отправлена на gulliver@lilliput.example, а другая будет добавлена в файл /home/lemuel/mail/archive.

Сами действия не предпринимаются при тестировании файла фильтра таким образом; например, нет проверки того, что все адреса переадресации действительны. Если вы хотите узнать, почему выполняется конкретное действие, добавьте к команде параметр -v. Это заставляет Exim выводить результаты любых условных тестов и делать отступ в выводе в соответствии с глубиной вложенности команд if в файле фильтра. Дальнейшие дополнительные выходные данные теста фильтра могут быть сгенерированы с помощью команды testprint, которая описана ниже.

Когда Exim выводит список действий, которые он должен предпринять, если какие-либо текстовые строки включены в вывод, непечатаемые символы в них преобразуются в управляющие последовательности. В частности, если какая-либо текстовая строка содержит символ новой строки, это отображается как \n в результатах тестирования.

При тестировании фильтра Exim создает конверт для сообщения. Получателем по умолчанию является пользователь, запускающий команду, как и отправитель, но команду можно запустить с параметром -f, чтобы указать другого отправителя. Например:

exim -bf new-filter -f islington@neverwhere.example <test-message

В качестве альтернативы, если параметр -f не используется, но первая строка предоставленного сообщения является разделителем From из файла папки сообщения (не то же самое, что строка заголовка From:), отправитель берется оттуда. Если присутствует -f, содержимое любой строки From игнорируется.

Обратный путь такой же, как у отправителя конверта, если только сообщение не содержит заголовок Return-path:, и в этом случае оно берется оттуда. Вам не нужно беспокоиться ни о чем из этого, если только вы не хотите протестировать функции файла фильтра, которые зависят от адреса отправителя или обратного пути.

Можно изменить получателя конверта, указав дополнительные параметры. Опция -bfd изменяет домен адреса получателя, а опция -bfl изменяет локальную часть. Консультант может использовать эти параметры для проверки чужого файла фильтра.

Параметры -bfp и -bfs задают префикс или суффикс для локальной части. Это может быть актуально при тестировании обработки нескольких адресов пользователей (5.5).

Если фильтр проверяет информацию об источнике сообщения (например, имя или IP-адрес хоста, с которого оно было получено), вы можете установить определенные значения для проверки. Это можно сделать, используя несколько параметров командной строки, имена которых начинаются с -oM. Например, -oMa задает адрес удаленного хоста. Подробную информацию об этих опциях см. в главе 20.

10.5.1 Тестирование файла системного фильтра

Системный фильтр можно протестировать так же, как и пользовательский фильтр, но следует использовать параметр командной строки -bF вместо -bf. Это позволяет Exim'у распознавать те команды и другие функции системного фильтра, которые недоступны в пользовательских фильтрах.

10.5.2 Проверка установленного файла фильтра

Тестирование файла фильтра перед установкой не может выявить все потенциальные проблемы; например, он фактически не запускает команды, которым передаются сообщения. Поэтому некоторые «живые» тесты также следует проводить после установки фильтра.

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

Разумной мерой предосторожности против такого случая является включение строки:

if error_message then finish endif

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

10.6 Формат файлов фильтров

Помимо начальных пробелов, первый текст в файле фильтра должен быть:

# Exim filter

Это то, что отличает его от обычного файла .forward (при условии, что в конфигурации включена фильтрация). Если в файле нет этой начальной строки, он рассматривается как обычный файл .forward как при доставке почты, так и при использовании механизма проверки -bf. Пробелы в строке необязательны, и можно использовать любые заглавные буквы. Дальнейший текст в той же строке рассматривается как комментарий. Например, у вас может быть:

# Exim filter  <<== do not edit or remove this line!

Оставшаяся часть файла представляет собой последовательность команд фильтрации. Они состоят из ключевых слов и значений данных, разделенных пробелами или разрывами строк, за исключением условий для команды if, где круглые скобки также действуют как разделители. Например, в команде:

deliver gulliver@lilliput.example

ключевое слово — deliver, а значение данных — gulliver@lilliput.example. Команды имеют свободный формат и могут располагаться более чем в одной строке; нет специальных терминаторов. Если символ # следует за разделителем, все, начиная с # и заканчивая следующей новой строкой, игнорируется. Это обеспечивает способ включения комментариев в файл фильтра.

Существует два способа ввода значения данных:

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

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

if $h_subject: contains \$\$\$\$ then seen finish endif

проверяет строку $$$$. Если используются кавычки, необходим дополнительный уровень экранирования:

if $h_subject: contains "\\$\\$\\$\\$" then
  seen finish
endif

Если в строке данных в кавычках требуется обратная косая черта, что может случиться, если строка должна интерпретироваться как регулярное выражение, необходимо ввести \\\\[3].

  1. Функция \N раскрытия строки, которая отключает раскрытие части строки, иногда более удобна.

10.7 Значащие действия

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

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

Команды доставки delivery, save и pipe по умолчанию являются значащими действиями. Например, если команда:

deliver hatter@wonderland.example

соблюдается в пользовательском файле фильтра, адрес не предлагается последующим роутерам. Однако, если команде предшествует слово unseen, например:

unseen deliver hatter@wonderland.example

доставка не считается значащей. По сути, фильтр, содержащий только «невидимую» (unseen) доставку, берет копию сообщения, не влияя на обычную доставку. Это можно использовать в системном фильтре для создания архивных копий сообщений.

Другие команды фильтра (те, которые не определяют доставку сообщения) по умолчанию не являются значащими действиями, но их можно сделать значащими, поместив перед ними слово seen. Например, выполнение команды finish прекращает работу фильтра; это само по себе не является значащим действием, поэтому, предпринял ли фильтр в целом какое-либо значащее действие, зависит от ранее выполненных команд (если они были). Однако, если команда:

seen finish

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

10.8 Команды фильтра

Команды фильтра, описанные в последующих разделах, следующие:

add Увеличить пользовательскую переменную
deliver Доставить на адрес электронной почты
fail Ошибка доставки (только системный фильтр)
finish Завершить обработку
freeze Заморозить доставку (только системный фильтр)
headers Добавить/удалить строки заголовков (только системный фильтр)
if Условие проверки
logfile Определить файл журнала
logwrite Запись в файл журнала
mail Отправить ответное сообщение
pipe Канал к команде
save Сохранить в файл
testprint Печать во время тестирования
vacation Индивидуальная форма mail

10.9 Команда добавления

Команда add предоставляет примитивные средства подсчета в файле фильтра.

add <number> to <user variable>

Например:

add 2 to n3

Имена пользовательских переменных состоят из буквы n, за которой следует одна цифра. Таким образом, имеется 10 пользовательских переменных этого типа, и их значения могут быть получены с помощью обычного синтаксиса раскрытия (например, $n4) в других командах. В начале фильтрации. все эти переменные содержат нуль. В конце системного фильтра их значения копируются в $sn0 до $sn9, чтобы на них можно было ссылаться в файлах фильтров пользователей. Таким образом. системный фильтр может устанавливать «баллы» для сообщения, на которое может ссылаться пользовательский фильтр. Оба аргумента команды add раскрываются перед использованием, что позволяет добавлять переменные друг к другу. Вычитание можно получить, сложив отрицательные числа. Следующий пример не соответствует действительности, но показывает, что можно сделать:

if $h_subject: does not match "(?-i) [a-z]" then
  add 1 to n1
endif
if $h_subject: contains "make money" then
  add 1 to n2
endif
add $n2 to n1
if $n1 is above 3 then seen finish endif

Первая команда проверяет тему сообщения на наличие строчных букв. Если их нет (т. е. если подлежащее полностью в верхнем регистре), переменная $n1 инкрементируется. Вторая команда увеличивает $n2, если тема содержит определенную строку. Затем содержимое $n2 добавляется к $n1 и проверяется сумма.

Обратите внимание, что имена переменных даются без начального знака доллара для переменных, которые увеличиваются. Если вы напишете:

add 1 to $n1

расширение строки, которое применяется к каждому аргументу, превратит его в команду, например:

add 1 to 0

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

10.10 Команды deliver

Следующие команды фильтра настраивают доставку сообщений (то есть они организуют отправку копий сообщения куда-либо). Такие доставки являются значимыми действиями, если команде не предшествует unseen.

10.10.1 Команда доставки

deliver <mail address>

Например:

deliver "Dr Livingstone <David@darkest.africa.example>"

Эта команда обеспечивает операцию переадресации. Сообщение отправляется на указанный адрес. Для пользовательского фильтра это то же самое, что поместить адрес в традиционный файл .forward. Чтобы доставить копию сообщения в обычный почтовый ящик пользователя, можно указать имя пользователя для входа, как и в традиционном файле .forward. Это не вызывает петлю, потому что Exim замечает, что адрес уже был обработан роутером, который запускает фильтр, и пропускает роутер в следующий раз.

Необязательным дополнением к команде deliver является:

errors to <mail address>

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

errors_to $local_part@$domain

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

Команде deliver может быть присвоен только один адрес, но можно использовать несколько экземпляров команды, чтобы сообщение было доставлено более чем на один адрес. Однако повторяющиеся адреса отбрасываются.

10.10.2 Команда save

save <file name>

Например:

save $home/mail/bookfolder

Эта команда вызывает добавление копии сообщения к данному файлу (то есть файл используется как почтовая папка). Может появиться более одной команды save; каждая вызывает запись копии сообщения в файл аргументов, при условии, что они различны (дублирующиеся команды save игнорируются).

Возможность использования команды save в пользовательском фильтре контролируется системным администратором; это можно запретить, установив forbid_file на роутере.

Необязательное значение режима может быть указано после имени файла, например:

save /some/folder 0640

Это позволяет пользователям переопределять общесистемный режим доставки файлов (обычно 0600). Если существующий файл не имеет правильного режима, он изменяется. Значение режима интерпретируется как восьмеричное число, даже если оно не начинается с нуля.

Для системного фильтра имя файла в команде save должно быть абсолютным путем. Для пользовательского фильтра, если имя файла не начинается с символа косой черты, к нему добавляется каталог, указанный в переменной $home. Пользователь, конечно, должен иметь разрешение на запись в файл, и (в обычной конфигурации) запись файла происходит в процессе, работающем с uid пользователя и первичным gid пользователя. Любые вторичные группы, к которым может принадлежать пользователь, обычно не принимаются во внимание, хотя системный администратор может настроить Exim для их настройки (6.3.4).

Можно включить альтернативную форму доставки, при которой каждое сообщение доставляется в новый файл в заданном каталоге. Для системного фильтра требуется настройка транспорта system_filter_directory, тогда как для пользовательского фильтра требуется настройка directory_transport на роутере redirect. В этом случае функциональность можно запросить, указав имя каталога, заканчивающееся косой чертой после команды save, например:

save separated/messages/

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

10.10.3 Команда pipe

pipe <command>

Например:

pipe "$home/bin/countmail $sender_address"

Эта команда запускает отдельный процесс, и копия сообщения передается ему на стандартный ввод. Может появиться более одной команды pipe; каждая из них вызывает запись копии сообщения в свой канал аргументов, при условии, что они различны (дублирующиеся команды pipe игнорируются).

Команда, подаваемая в pipe, разбивается Exim на имя команды и количество аргументов, разделенных пробелами, за исключением аргументов, заключенных в двойные кавычки (в этом случае обратная косая черта интерпретируется как escape) или в одинарные кавычки (в этом случае обратная косая черта интерпретируется как экранирование). Обратите внимание, что, поскольку вся команда обычно предоставляется в двойных кавычках, для внутренних двойных кавычек требуется второй уровень кавычек. Например:

pipe "$home/myscript \"size is $message_size\""

Расширение строки выполняется на отдельных компонентах после того, как строка была разделена, и затем команда запускается непосредственно Exim'ом; она не запускается под оболочкой. Это означает, что замены в расширениях не могут изменить количество аргументов, равно как и замененные кавычки, обратные косые черты или другие метасимволы оболочки не могут вызвать путаницу. В документации к некоторым программам, которые обычно запускаются через этот pipe, часто предлагается, чтобы команда начиналась с:

IFS=" “

Это команда оболочки, и ее не должно быть в файлах фильтров Exim, потому что Exim обычно не запускает команду под оболочкой.

В предыдущем абзаце предполагается конфигурация по умолчанию для транспорта pipe, которая используется для канальных доставок из фильтров. Если на транспорте установлено use_shel1, все по-другому. Ряд других параметров транспорта может влиять на способ выполнения команды, включая наложение ограничений на то, какие команды могут быть запущены, и как обрабатывается любой вывод команды (9.5).

10.10.4 игнорирование ошибок доставки

Как объяснялось ранее в этой главе, фильтрация просто устанавливает адреса для доставки; никаких доставок фактически не происходит, пока файл фильтра активен. Если какой-либо из сгенерированных адресов впоследствии терпит неудачу доставки, сообщение об ошибке генерируется обычным способом. Однако, если команде фильтра, устанавливающей доставку, предшествует слово noerror, локально обнаруженные ошибки для этой доставки и любые последующие доставки (т. е. из вызванных ею файлов псевдонимов, пересылки или фильтров) игнорируются. Например, предположим, что пользователь хочет сканировать все входящие сообщения с помощью какой-либо программы, а также доставлять их в обычный почтовый ящик. Такая команда, как:

unseen noerror pipe $home/bin/mailscan

может быть использованна; unseen гарантирует, что нормальная доставка не будет затронута, а noerror гарантирует, что сбой канала не приведет к созданию сообщения о возврате.

10.11 Команды mail

Есть две команды, которые вызывают создание нового почтового сообщения, ни одна из которых не является существенным действием, если команде не предшествует слово seen. Автоматическая отправка сообщений — мощное средство, но им следует пользоваться с осторожностью из-за опасности создания бесконечных последовательностей сообщений. Системный администратор может запретить использование этих команд в файлах фильтров пользователей, установив forbid_filter_reply на роутере redirect.

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

mail to <address-list>
     cc <address-list>
     bee <address-list>
     from <address>
     reply_to <address>
     subject <text>
     text <text>
     [expand] file <filename>
     return message
     log <log file name>
     once <note file name>
     once_repeat <time interval>

Например:

mail text "Got your message about $h_subject:"

Все ключевые слова, которые могут следовать за mail, являются необязательными; вам нужно указать только те, чьи значения по умолчанию вы хотите изменить. Для удобства использования в одном общем случае есть еще команда под названием vacation. Он ведет себя так же, как mail, за исключением того, что значения по умолчанию для ключевых слов file, log, once и once_repeat:

expand file .vacation.msg
log  .vacation.log
once .vacation
once_repeat 7d

соответственно. Это те же самые имена файлов и период повторения, которые используются в традиционной команде Unix vacation. Значения по умолчанию могут быть переопределены явными настройками, например:

vacation once_repeat 14d

Команда vacation обычно используется условно, в соответствии с условием personal, чтобы не отправлять автоматические ответы на неличные сообщения из списков рассылки или где-либо еще.

Для обеих команд пары аргументов ключ/значение могут появляться в любом порядке. Должен появиться хотя бы один text или file (кроме vacation, где file может быть по умолчанию); если присутствуют оба, текстовая строка появляется в сообщении первой. Если expand предшествует file, каждая строка файла подлежит расширению строки, поскольку она включена в сообщение.

Если ключевое слово to не появляется, сообщение отправляется по адресу в переменной $reply_address, которая является содержимым строки заголовка Reply-To:, если она существует, или, в противном случае, содержимым строки заголовка From:. Заголовок In-Reply-To: автоматически включается в созданное сообщение, давая ссылку на идентификацию входящего сообщения.

В text можно добавить несколько строк текста, включив управляющую последовательность \n в строку, где требуются символы новой строки, например:

mail text
  "This is an automatic response to your message.\n\
  I am very busy, but will look at it eventually."

Если команда выводится во время тестирования файла фильтра, символы новой строки в тексте отображаются как \n.

Обратите внимание, что ключевое слово для создания строки заголовка Reply-To:reply_to, потому что ключевые слова Exim могут содержать символы подчеркивания, но не дефисы. Если присутствует ключевое слово from и данный адрес не соответствует пользователю, под которым работает фильтр, Exim по умолчанию добавляет строку заголовка Sender: к сообщению. Это поведение можно изменить, установив параметры local_from_check и local_from_prefix (20.2.1). Если from не указан, заголовок From: создается из имени входа и значения qualify_domain.

Если указано значение return_message, входящее сообщение, вызвавшее запуск файла фильтра, добавляется в конец вновь созданного сообщения с учетом ограничения максимального размера возвращаемых сообщений, которое контролируется опцией return_size_limit в основной конфигурации Exim.

Если указан файл журнала, в него добавляется запись для каждого отправленного сообщения. Запись содержит несколько строк, как в этом примере:

2000-05-01 14:04:06
To: John Doe <jd@somewhere.example>
Subject: Re: wish list for exim

Если указан файл once, он используется для хранения базы данных для запоминания того, кто получил сообщение, и на какой-либо конкретный адрес никогда не отправляется более одного сообщения, если не задано once_repeat. Это указывает временной интервал, после которого может быть отправлена другая копия сообщения. Например:

once_repeat = 5d4h

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

Имя файла, указанное для once, используется в качестве базового имени для операций с файлами прямого доступа (DBM). Exim автоматически создает и поддерживает файл или файлы DBM. Существует несколько различных библиотек DBM. Некоторые операционные системы предоставляют его по умолчанию, но даже в этом случае при сборке Exim мог использоваться другой. В некоторых библиотеках DBM указание once приводит к созданию двух файлов с добавлением суффиксов .dir и .pag к заданному имени. В некоторых других используется один файл с суффиксом .db или имя используется без изменений.

Если once используется в сценарии «vacation», файл DBM должен быть удален пользователем, когда отпуск закончится, а файл фильтра будет изменен, чтобы больше не отправлять сообщения.

За один запуск фильтра может быть выполнено более одной команды mail или vacation; все они почитаются, даже если они принадлежат одному и тому же получателю.

10.12 Команды ведения журнала

Можно вести журнал действий, предпринятых файлом фильтра. Для пользовательских фильтров системный администратор может отключить эту функцию, установив forbid_filter_logwrite на роутере redirect. Ведение журнала происходит во время интерпретации файла фильтра. Он не ставится в очередь на потом, как это делают команды доставки. Причина этого в том, что файл журнала нужно открывать только один раз для нескольких операций записи.

Есть две команды, ни одна из которых не является значимым действием. Первая определяет файл, в который впоследствии записываются выходные данные журнала:

logfile <file name>

Например:

logfile $home/filter.log

За именем файла может дополнительно следовать режим файла, который используется, если файл должен быть создан. Например:

logfile $home/filter.log 0644

Число интерпретируется как восьмеричное, даже если оно не начинается с нуля. Значение по умолчанию для этого режима — 0600. Предполагается, что команда logfile должна обычно отображаться как первая команда в файле фильтра. После того, как logfile будет выполнен, можно использовать команду logwrite для записи в файл журнала:

logwrite <"some text string">

Например:

logwrite "$tod_log $message_id processed"

Возможно иметь более одной команды logfile, чтобы указать запись в разные файлы журнала в разных обстоятельствах. Запись происходит в конец файла, и в конец каждой строки добавляется символ новой строки, если его еще нет. Новые строки можно поместить в середину строки с помощью управляющей последовательности \n. Строки из одновременных доставок могут чередоваться в файле, так как нет блокировки, поэтому вы должны планировать ведение журнала с учетом этого. Однако данные не должны быть потеряны.

10.13 Команда testprint

Иногда полезно иметь возможность распечатать значения переменных при тестировании файлов фильтров. Команда:

testprint <"text">

Например:

testprint "home=$home reply_address=$reply_address"

ничего не делает, когда почта доставляется. Однако, когда фильтрующий код проверяется с помощью опций -bf или -bF, значение строки записывается в стандартный вывод.

10.14 Команда finish

Команда finish, не имеющая аргументов, заставляет Exim прекратить интерпретацию файла фильтра. Что произойдет дальше, зависит от того, были ли предприняты какие-либо существенные действия. Сама команда finish не является значимым действием, если только ей не предшествует слово seen. Достижение конца файла фильтра имеет тот же эффект, что и выполнение команды finish.

10.15 Условное подчинение командам фильтра

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

if    <condition>
then  <commands>
elif  <condition>
then  <commands>
else  <commands>
endif

Может быть любое количество разделов elif-then (включая отсутствие), а раздел else также является необязательным. Любое количество команд, включая вложенные команды if, может появиться в любом из разделов <commands>.

############ выше не использовал текст - надо проверить

Условия можно комбинировать, используя слова and и or, а также можно использовать круглые скобки, чтобы указать, как следует комбинировать несколько условий. Без круглых скобок and имеет более сильную обязательную силу, чем or. Вот пример, в котором используются скобки:

if $h_subject: contains [EXIM] and
  (
  $return_path is exim-users-admin@exim.org or
  $h_to:$h_cc: contains exim-users@exim.org
  )
then
  save $home/Mail/exim-list
endif

Если какая-либо строка в условии содержит круглые скобки, она должна быть заключена в кавычки, чтобы избежать неправильного толкования.

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

if not personal and $h_subject: does not contain [EXIM]
  then save $home/Mail/other-lists
endif

Следующие описания показывают только отдельные условия, а не полные команды if.

10.15.1 Условия тестирования строк

Существует ряд условий, которые работают с текстовыми строками, используя слова begins, ends, is, contains и matches. Если имена условий написаны строчными буквами, проверка букв производится без учета регистра; если они написаны прописными буквами (например, CONTAINS), регистр букв имеет значение.

Тест начала строки

<text1> begins <text2>
<text1> does not begin <text2>

Например:

if $header_from: begins "Friend@"

Тест begins проверяет наличие второй строки в начале первой, при этом обе строки были расширены.

Тест конца строки

<text1> ends <text2>
<text1> does not end <text2>

Например:

if $header_from: ends "@public.example.com"

Тест ends проверяет наличие второй строки в конце первой, при этом обе строки были расширены.

Точный тест строки

<text1> is <text2>
<text1> is not <text2>

Например:

if $local_part_suffix is "-foo"

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

Частичный тест строки

<text1> contains <text2>
<text1> does not contain <text2>

Например:

if $header_subject: contains "evolution"

Тест contains проверяет частичное совпадение строк, расширяя обе строки.

Проверка регулярного выражения в строке

<text1> matches <text2>
<text2> does not match <text2>

Например:

if $sender_address matches "(Bill|John)@"

Для теста matches после раскрытия обеих строк вторая интерпретируется как регулярное выражение и сопоставляется с первой. Следует соблюдать осторожность, если вам нужна обратная косая черта в регулярном выражении, потому что обратная косая черта интерпретируется в качестве escape-символа как кодом раскрытия строки, так и нормальным кодом чтения строки Exim, когда строка дается в кавычках. Например, если вы хотите проверить адрес отправителя для домена, оканчивающегося на .com, используйте регулярное выражение:

\.com$

Обратная косая черта и знак доллара в этом выражении должны быть экранированы при использовании в команде фильтра, потому что в противном случае они будут интерпретированы кодом расширения. Вы можете избежать каждого из них по отдельности следующим образом:

if $sender_address matches \\.com\$

Кроме того, вы можете использовать средство \N, чтобы отключить расширение в части строки. Обычно это удобнее для регулярных выражений, поскольку само выражение не изменяется. Например:

if $sender_address matches \N\.com$\N

Любая часть строки, расположенная между двумя вхождениями \N, копируется без изменений расширителем строки. Однако, если строка указана в кавычках (обязательно, только если она содержит пробелы или круглые скобки), вы должны удвоить все обратные косые черты и написать[4]:

if $sender_address matches "\\N\\.com$\\N"

Если регулярное выражение содержит подвыражения в скобках для захвата частей совпадающей строки, в действиях, следующих за успешным совпадением, можно использовать подстановки числовых переменных, например $1. Если совпадения нет, значения числовых переменных остаются неизменными. Предыдущие значения не восстанавливаются после endif, другими словами, всегда доступен только один набор значений. Если условие содержит несколько подусловий, соединенных and или or, то в последующих действиях будут доступны строки, извлеченные из последнего успешного совпадения. Расширение строки условия происходит непосредственно перед его проверкой, что означает, что числовые переменные из одного подусловия доступны для использования в последующих подусловиях.

  1. Без использования \N вам пришлось бы писать \\\\ для обратной косой черты и \\$ для знака доллара.

10.15.2 Числовые условия тестирования

Для проведения численных тестов доступны следующие условия:

<number1> is above <number2>
<number1> is not above <number2>
<number1> is below <number2>
<number1> is not below <number2>

Например:

if $message@_size is not above 10k

Аргументы <number> должны расширяться до строк цифр, за которыми может следовать одна из букв K или M (в верхнем или нижнем регистре), что приводит к умножению на 1024 и на 1024x1024 соответственно.

10.15.3 Проверка личной почты

Общим требованием к пользовательским фильтрам является различие между входящей личной почтой и почтой из списка рассылки. В частности, этот тест обычно требуется перед отправкой «сообщений об отпуске» (vacation messages), чтобы избежать отправки их в списки рассылки. Состояние:

personal

является сокращением для:

$header_to: contains $local_part@$domain and
$header_from: does not contain $local_part@$domain and
$header_from: does not contain server@ and
$header_from: does not contain daemon@ and
$header_from: does not contain root@ and
$header_subject: does not contain "circular" and
$header_precedence: does not contain "bulk" and
$header_precedence: does not contain "list" and
Sheader_precedence: does not contain "junk"

Это условие проверяет появление текущего пользователя в заголовке To:, проверяет, что отправитель не является текущим пользователем или одним из ряда распространенных демонов, и проверяет содержимое заголовков Subject: и Precedence:. Это полезно только в файлах пользовательских фильтров, потому что во время работы системного фильтра не существует уникального получателя (из которого можно установить $local_part и $domain).

Если для локальных частей используются префиксы или суффиксы (что зависит от конфигурации Exim), первые два теста также выполняются с:

$local_part_prefix$local_part$local_part_suffix

вместо просто $local_part. Если система настроена на перезапись локальных частей почтовых адресов (например, на перезапись dag46 как Dirk.Gently), в этих тестах также используется переписанная форма адреса.

В этом примере показано использование personal в файле фильтра, рассылающем сообщения об отпуске:

if personal then
  mail
   to $reply_address
   subject "Re: $h_subject:"
   file $home/vacation/message
   once $home/vacation/once
   once_repeat 10d
endif

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

alias <address>

любое количество раз, например:

if personal
  alias smith@else.where.example
  alias jones@other.place.example
then ...

Это приводит к тому, что сообщения, содержащие псевдонимы адресов, рассматриваются как личные. Псевдонимы используются при проверке заголовков To: и From:.

10.15.4 Тестирование значимых действий

Привели ли какие-либо ранее выполненные команды фильтра к значимому действию, можно проверить с помощью предоставленного условия, например:

if not delivered then save mail/anomalous endif

10.15.5 Проверка сообщений об ошибках

Условие error_message истинно, если входящее сообщение является рикошетом, то есть если его адрес отправителя в конверте пуст. Размещение команды:

if error_message then finish endif

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

10.15.6 Проверка статуса доставки

Есть два условия, которые в основном предназначены для использования в файлах системных фильтров, но также доступны и в пользовательских файлах фильтров. Условие first_delivery истинно, если это первая попытка доставки сообщения, и ложно в противном случае.

Условие manually_thawed верно только в том случае, если сообщение было заморожено по какой-либо причине и впоследствии было разблокировано системным администратором. Маловероятно, что оно будет использоваться в файлах фильтров пользователей. Явная принудительная доставка считается ручным размораживанием, а размораживание в результате использования опции auto_thaw — нет.

10.15.7 Проверка списка адресов

Существует возможность циклического просмотра списка адресов и применения условия к каждому из них. Проверка выполняется как условие, являющееся частью команды if, и имеет вид:

foranyaddress <string> (<condition>)

где <string> интерпретируется как список адресов RFC 2822, как в типичной строке заголовка или как значение $recipients в системном фильтре, а <condition> — любое допустимое условие фильтра или комбинация условий. Круглые скобки, окружающие условие, являются обязательными, чтобы отделить его от возможных дополнительных подусловий прилагаемой команды if. В этом условии переменная раскрытия $thisaddress устанавливается в некомментарную часть каждого из адресов в строке по очереди. Например, если строка была следующей (здесь она разделена на две строки):

B.Simpson <bart@springfield.example>,
  lisa@springfield.example (his sister)

тогда $thisaddress будет по очереди принимать значения bart@springfield.example и lisa@springfield.exampl]e.

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

В этом примере проверяется наличие восьмизначной локальной части в любом адресе в заголовке To::

if foranyaddress $h_to: ( $thisaddress matches ^\\d{8}@ ) then ...

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

Строки заголовков могут быть объединены, если проверка должна применяться более чем к одной из них. Например:

if foranyaddress $h_to:,$h_cc: ...

сканирует адреса в заголовках To: и Cc:.

10.16 Дополнительные возможности системных фильтров

Во время работы системного фильтра переменная $recipients содержит список всех получателей конверта сообщения, разделенных запятыми и пробелами. Есть также некоторые дополнительные команды фильтрации. Обычно они разрешены только в системных фильтрах, и попытки использовать их в пользовательском фильтре или при тестировании с использованием -bf завершаются ошибкой. Тем не менее, параметры allow_fail и allow_freeze роутера redirect могут быть установлены в значение true, чтобы разрешить использование команд fail и freeze в несистемных фильтрах. Это средство предназначено для использования в системах, в которых запускаются централизованно управляемые файлы фильтров для каждого пользователя; обычно нецелесообразно включать эти команды, когда пользователи могут изменять свои собственные файлы фильтров.

10.16.1 Команда fail

fail text <"text">

Например:

fail text "Administrative rejection"

Эта команда предотвращает любые доставки сообщения, за исключением тех, которые могли быть ранее настроены в фильтре. Это похоже на seen finish, но в этом случае генерируется сообщение о возврате. Текстовая строка (которую можно опустить, если она не требуется) включается в сообщение о возврате. Никакие другие команды в файле фильтра не выполняются, поэтому, если, например, вы хотите использовать logwrite для ведения журнала принудительных сбоев, вы должны поместить эту команду перед fail.

Текст, указанный в команде fail, также используется как часть сообщения об ошибке, которое записывается в журнал. Если сообщение довольно длинное, это может заполнить много места в журнале, когда такие сбои являются обычными. Чтобы уменьшить размер лог-сообщения, Exim интерпретирует текст особым образом, если он начинается с двух символов << и содержит позже >>. Текст между этими двумя строками записывается в журнал, а остальная часть текста используется в сообщении о возврате. Например:

fail "<<filter 1>>Your message is not being delivered because \
     it contains attachments that we do not like."

Соблюдайте особую осторожность при работе с командой fail, когда принимаете решение об отказе на основе содержимого сообщения, потому что обычное сообщение об ошибке доставки включает в себя содержимое исходного сообщения и, следовательно, снова вызовет команду fail (вызывая почтовый цикл), если только не принимаются меры для предотвращения этого. Проверка условия error_message — один из способов предотвратить это. Вы можете использовать, например:

if $message_body contains "this is spam" and not error message
then
  fail text "spam is not wanted here"
endif

10.16.2 Команда freeze

freeze text <"text">

Например:

freeze text "Administrative rejection"

Эта команда предотвращает любые доставки, кроме ранее настроенных в фильтре, но сообщение не возвращается. Вместо этого, после того как были предприняты попытки доставки, настроенные фильтром, сообщение остается в очереди и замораживается. Никакие другие команды в файле фильтра не выполняются, поэтому, если, например, вы хотите использовать logwrite для ведения журнала действий по замораживанию, вы должны поместить эту команду перед freeze.

Команда freeze игнорируется, если сообщение было разморожено вручную и впоследствии не заморожено вручную. Это означает, что автоматическое замораживание в системном фильтре может использоваться как способ проверки подозрительных сообщений. Если при проверке обнаружено, что замороженное сообщение все-таки валидно, его ручная разморозка (используя опцию -Mt или через монитор Exim) позволяет его доставить. Попытка принудительной доставки (например, с использованием -M или -qff) также считается размораживанием вручную, но не достижением времени auto_thaw.

10.16.3 Команда headers add

headers add <"text">

Например:

headers add "X-Filtered: checked on $primary_hostname"

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

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

10.16.4 Команда headers remove

headers remove <"text">

Например:

headers remove "Return-Receipt-To"

Строка расширяется и затем обрабатывается как список имен заголовков, разделенных двоеточиями. Любые строки заголовка с такими именами удаляются из сообщения. Эта команда применяется только к оригинальным строкам заголовков, которые хранятся вместе с сообщением; другие, такие как Envelope-to: и Return-path:, которые добавляются во время доставки, не могут быть удалены этим способом.

Строки заголовков, удаленные системным фильтром, становятся невидимыми во время последующего процесса доставки, и на них нельзя ссылаться в строках расширения. Это отличается от заголовков, предназначенных для удаления роутерами, которые остаются видимыми и удаляются только при транспортировке сообщения.

Глава 11
Общие данные и процессы Exim

В обзоре работы Exim в главе 3 упоминается использование нескольких процессов. В этой главе мы опишем различные типы процессов, которые используются для обработки сообщений. Это справочная информация о том, как работает Exim, которая может быть полезна, когда вы хотите точно понять, что он делает. Четыре типа процесса следующие:

Процесс демона

Процесс-демон прослушивает входящие SMTP-соединения и запускает процесс приема для каждого из них. Демон также может периодически запускать процессы обработчика очередей. Обычно существует только один процесс демона.

Принимающие процессы

Процесс приема не-SMTP принимает одно входящее сообщение и сохраняет его на спул-диске Exim'а. Процесс приема SMTP может принимать несколько сообщений от одного и того же источника.

Процессы запуска очереди

Процесс запуска очереди просматривает список ожидающих сообщений и запускает процесс доставки для каждого из них по очереди.

Процессы доставки

Процесс доставки выполняет одну попытку доставки одного сообщения.

Большинство процессов Exim недолговечны. Они выполняют одну задачу, например получение или доставку сообщения, а затем завершаются. Единственным исключением является процесс-демон, который, как следует из его названия, работает непрерывно.

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

Рисунок 11-1: Общие данные

Файлы сообщений и файлы подсказок хранятся в подкаталогах внутри каталога спулинга Exim, имя которого настраивается, хотя чаще всего он называется /var/spool/exim. Вы можете узнать имя каталога спула, выполнив команду:

exim -bP spool directory

В некоторых конфигурациях файлы журналов также находятся здесь, в другом подкаталоге, но Exim можно настроить так, чтобы они размещались в другом месте. В конфигурациях, где информация журнала записывается только в syslog, файлы журнала отсутствуют.

11.1 Файлы сообщений

Каждое сообщение хранится в двух отдельных файлах, имена которых состоят из уникального идентификатора сообщения, за которым следуют -D и -H соответственно. Например, сообщение 12b2ie-0000GI-00 содержится в двух файлах:

12b2ie-0000GI-00-D
12b2ie-0000GI-00-H

Первая строка каждого файла — это собственное имя файла. Эта самоидентификация является страховкой от сбоя диска, который уничтожит каталог, но не сами файлы. Файл -D содержит тело сообщения и может быть очень большим. Он никогда не обновляется после получения сообщения.

Файл -H содержит строки конверта и заголовка сообщения вместе с другой информацией о сообщении, такой как хост, с которого оно пришло, и те адреса, на которые оно уже было доставлено. Точный формат описан в справочном руководстве. Этот файл обычно довольно мал и обновляется в течение всего времени существования сообщения, которое не может быть доставлено всем получателям с первой попытки, чтобы записать, какие адреса были обработаны. Существования файла -H достаточно, чтобы указать на наличие сообщения в очереди. Отдельный список сообщений не ведется.

Каталог input в каталоге спулинга Exim используется для хранения файлов сообщений, которые при нормальных обстоятельствах постоянно создаются и удаляются, когда почта проходит через систему. Если обрабатывается большое количество почты, между различными процессами Exim будет конкуренция за доступ к каталогу input. Кроме того, если общее количество сообщений в очереди в любой момент времени велико, обновление каталога может занять много времени, что может привести к снижению производительности. Размер каталога, вызывающего снижение производительности, различается в разных операционных системах. Solaris, например, может обрабатывать каталоги большего размера, чем GNU/Linux (используя файловую систему по умолчанию), прежде чем начнет деградировать. Чтобы улучшить ситуацию, когда очередь большая, можно установить опцию:

split_spool_directory = true

Если это сделано, используется дополнительный уровень каталога. Во каталоге input создаются 62 подкаталога с именами, состоящими из одной буквы или цифры, и файлы сообщений распределяются между ними. Шестой символ идентификатора сообщения используется для определения подкаталога, в котором хранится сообщение. Например, сообщение 12b2ie-0000GI-00 хранится в файле input/e. Шестой символ выбран потому, что он является младшей base-62 цифрой времени поступления сообщения и меняется каждую секунду. Подобное разделение каталога input уменьшает количество файлов в любом каталоге, а также уменьшает количество конфликтов между процессами, пытающимися создать или удалить файлы в каталоге. Одним из недостатков является то, что для сканирования очереди требуется больше работы, либо для ее перечисления, либо для выполнения запуска очереди, но эти операции выполняются относительно нечасто.

Во время доставки Exim создает два других файла для каждого сообщения. Третий файл в каталоге input (или его подкаталоге) с суффиксом -J (для «journal») используется для записи каждого адреса получателя сразу после его доставки. Если сообщение необходимо сохранить для последующей попытки доставки в конце выполнения доставки, содержимое файла -J объединяется с файлом -H, а файл -J удаляется. Есть две причины для использования файлов журнала:

Последний файл для каждого сообщения — это журнал сообщений (message log), который содержит записи журнала, фиксирующие ход доставки сообщения. Например, для каждой успешной или неудачной доставки в этот файл записывается строка. Та же самая информация также записывается в основной журнал Exim, но хранение копий в файлах журнала сообщений позволяет администратору легко проверять прогресс или отсутствие отдельных сообщений. Журналы сообщений хранятся в подкаталоге с именем msglog в каталоге спула Exim, и их имена являются соответствующими идентификаторами сообщений. Файл журнала сообщения удаляется после завершения обработки сообщения[2]. Если установлена опция split_spool_directory, каталог msglog подразделяется так же, как и каталог input.

Вы можете отключить использование журналов для каждого сообщения, установив для параметра message_logs значение false. Это уменьшает количество файлов, которые Exim использует для обработки сообщения, что может быть полезно в сильно загруженной системе.

  1. Даже если бы Exim и все остальное программное обеспечение в системе были свободны от ошибок, аппаратные сбои и потери питания могут вызвать этот эффект.

  2. Существует параметр, называемый save_message_logs, который заставляет их вместо этого перемещаться в буферный каталог с именем msglog.OLD для использования в статистике или отладке. В этом случае ответственность за их удаление лежит на администраторе.

11.2 Блокировка файлов сообщений

Когда процесс Exim работает с сообщением, он блокирует файл -D, чтобы предотвратить попытки любого другого процесса Exim доставить то же сообщение[3]. Если для сообщения запущен другой процесс доставки, например, обработчик очереди, он замечает блокировку и выходит, ничего не делая, записывая сообщение:

Spool file is locked (another process is handling this message)

Это нормальное явление и не указывает на ошибку, однако, если это происходит для одного и того же сообщения в течение длительного периода времени, это может означать, что в процессе доставки возникла проблема. Вы можете отключить эти записи журнала, сняв флажок журнала skip_delivery (21.2.2).

  1. Файл -H нельзя использовать для блокировки сообщения, так как он может обновляться в процессе доставки, а обновление осуществляется путем записи временного файла и переименования. Это изменяет базовую идентичность (inode) файла, на которой основана блокировка.

11.3 Файлы подсказок

Exim собирает данные о ранее возникших проблемах с доставкой, чтобы адаптировать свое поведение к изменяющимся обстоятельствам. Например, он запоминает хосты, к которым ему не удалось подключиться, чтобы не повторять их слишком часто. Термин «подсказки» (hints) используется для описания этих данных, потому что они не критичны для работы Exim'а. Если, например, информация об отказавшем хосте потеряна, Exim попытается доставить ее при следующей возможности, вместо того, чтобы ждать ранее рассчитанного времени повторной попытки. Однако все это означает, что система выполняет больше работы, чем в противном случае. Схема попыток доставки изменяется, но сообщения не повреждаются и не теряются.

Из-за некритической природы данных подсказок Exim поддерживает их с помощью простых системных вызовов ввода/вывода; нет необходимости в тяжеловесном аппарате, основанном на транзакциях, который был бы необходим, если бы данные должны были управляться максимально безопасным способом. Это означает, что накладные расходы на поддержку подсказок минимальны.

Данные подсказок хранятся в нескольких файлах в подкаталоге каталога очереди с именем db. Эти файлы не читаются и не записываются последовательно, как обычные файлы, потому что это было бы очень медленно. Вместо этого содержащиеся в них данные хранятся в индексированных файлах DBM. Exim использует три разных типа баз данных подсказок, а именно:

Точные имена файлов в каталоге db зависят от используемой библиотеки DBM. Некоторые библиотеки используют два имени файла с расширениями .dir и .pag, тогда как другие используют .db или вообще не используют расширение. Таким образом, база данных retry, например, может храниться в retry.dir и retry.pag, или retry.db, или просто retry. Вы также увидите файлы, имена которых заканчиваются на .lock, в каталоге db. Они используются, чтобы гарантировать, что только один процесс одновременно записывает в базу данных, когда Exim обновляет файлы подсказок[5].

Каждый процесс доставки обращается к подсказкам retry, и после любой доставки SMTP проверяются подсказки wait-. Если система работает нормально, большинство этих обращений осуществляется только для чтения с использованием общих блокировок, которые не задерживают процессы. Только в случае временного сбоя или успешной доставки после предыдущего сбоя процессу необходимо получить монопольный доступ для обновления подсказок. Есть надежда, что это относительно редкое явление.

Файлы подсказок содержат устаревшую информацию, которую необходимо время от времени удалять. Например, если несколько хостов настроены на прием почты для определенного домена, но все они в какой-то момент недоступны, создаются данные повторных попыток для каждого хоста. Однако когда ожидающее сообщение впоследствии доставляется одному из них, информация для остальных остается. Есть утилита exim_tidydb, которая убирает «сухостой» (dead wood) (давно не обновлявшиеся данные) в файлах подсказок; его следует запускать через равные промежутки времени (например, ежедневно или еженедельно). Существуют также некоторые другие утилиты для проверки и изменения содержимого файлов подсказок (21.10).

  1. См. параметры queue_smtp_domains и -odgqs.

  2. Обычная блокировка самих файлов не может быть использована, потому что Exim обращается к ним косвенно через библиотеку DBM, а не все библиотеки DBM предоставляют интегрированные средства блокировки. Поэтому для простоты используется внешняя блокировка.

11.4 Файлы журнала

Если Exim не настроен на использование только syslog, он записывает данные журнала в три файла в своем каталоге журнала, расположение которых настраивается во время сборки или во время выполнения. Чаще всего это подкаталог log внутри каталога спулинга Exim'а (который используется по умолчанию) или такое расположение, как /var/log/exim в системах, которые хранят все свои журналы в одном месте. Содержимое журналов Exim описано позже (21.1).

Все процессы Exim записывают в одни и те же файлы журналов, но ни один процесс Exim никогда не читает какие-либо данные журналов. Взаимосвязь между процессами достигается путем открытия файлов журнала для добавления и обеспечения того, чтобы каждая строка журнала записывалась в одной операции записи. Затем операционная система гарантирует, что за один раз происходит только одно обновление, и процессам не нужно самостоятельно выполнять какую-либо блокировку.

Файлы журналов обычно циклически (обычно ежедневно) переименовываются. Для этой цели предусмотрена утилита Exim под названием exicyclog (21,4), но если ваша операционная система имеет собственные средства циклирования журналов, их можно использовать вместо нее. После того, как файл журнала переименован, новый файл создается, как только любой процесс Exim имеет что-то для регистрации. Существующие процессы Exim, которые уже открыли старый файл, могут оставить его открытым, но не будут записывать в него снова; как только эти процессы завершены, файл становится бездействующим. Обычной практикой является сжатие предыдущего файла журнала через 24 часа после его переименования; сценарий exicyclog делает это автоматически.

11.5 ID пользователей и групп для процессов Exim

Exim обычно устанавливается как программа «setuid root» с разрешениями, установленными следующим образом:

-rwsr-xr-x  1 root  mail  561952 Nov 30 09:53 exim

Разрешение s означает, что всякий раз, когда Exim запускается, он получает привилегию суперпользователя, без которой (например) он не может писать в почтовый ящик каждого пользователя. Тем не менее, из общих принципов безопасности желательно, чтобы любой процесс Exim прекращал работу от имени root, как только ему больше не нужны привилегии. Для этого ему нужно использовать какой-то другой uid. Следовательно, как часть процесса сборки Exim (описанного в главе 22), должны быть определены uid и gid; они упоминаются в этой книге как «Exim uid» и «Exim gid»[6].

  1. Ниже приводится общее обсуждение безопасности (19.1).

11.6 Отношения процесса

Хотя нет никакого центрального процесса, который имел бы полный контроль над тем, что делает Exim, процессы Exim взаимодействуют друг с другом различными способами. Соединения показаны на рис. 11-2. Верхняя часть рисунка связана с приемом сообщения, а нижняя — с его доставкой. Сплошные линии обозначают потоки данных, а пунктирные линии используются для того, чтобы показать, где один процесс создает другой, не передавая никаких данных сообщения. Например, процесс-демон создает новый процесс для каждого входящего SMTP-соединения, а локальные процессы приема могут быть созданы любым другим процессом, таким как MUA пользователя или сценарий, отправляющий сообщение.

Рисунок 11-2: Отношения процесса

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

Процесс доставки выполняет одну попытку доставки для одного сообщения. Каждая локальная и удаленная доставка сообщения выполняется в отдельном подпроцессе основного процесса доставки. В случае удаленных доставок одновременно может быть запущено несколько, в зависимости от значения remote_max_parallel.

11.7 Процесс демона

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

exim -bd -q15m

Опция -bd настраивает демона, который прослушивает входящие SMTP, а -q15m указывает, что он должен запускать новый процесс запуска очереди каждые 15 минут. Эти параметры совместимы с Sendmail, поэтому команда:

/usr/sbin/sendmail -bd -q15m

которая появляется в сценариях загрузки в большинстве операционных систем, будет правильно запускать демон Exim, при условии, что /usr/sbin/sendmail был преобразован в символическую ссылку на двоичный файл Exim.

Когда демон запускается, если не включена отладка, он отключается от любого управляющего терминала. После этого он записывает свой идентификатор процесса в файл, чтобы его можно было легко найти. Расположение этого файла настраивается либо во время сборки, либо путем установки pid_file_path, но если местоположение не указано, файл pid записывается в каталог спула Exim с использованием имени exim-daemon.pid. Это позволяет легко определить, когда вы хотите убить демона или послать ему сигнал HUP после изменения файла конфигурации Exim.

Если вы используете хост, который использует разные IP-адреса (на виртуальных интерфейсах) для поддержки нескольких виртуальных веб-серверов, вы можете не захотеть иметь входящие SMTP-соединения на всех виртуальных интерфейсах. Если вы установите для local_interfaces список IP-адресов, демон прослушивает только эти интерфейсы. В противном случае используются все интерфейсы.

По умолчанию для всех интерфейсов используется один и тот же номер порта. По умолчанию используется стандартный порт SMTP (25). Значение по умолчанию можно изменить с помощью параметра daemon_smtp_port или с помощью -oX в командной строке, запускающей демон. Другие порты могут быть полезны для специальных приложений или для тестирования.

Если вы хотите, чтобы демон прослушивал более одного порта, вы можете сделать это, соответствующим образом установив local_interfaces или используя расширенную форму параметра командной строки -oX, который принимает те же настройки, что и local_interfaces. Для каждого перечисленного интерфейса может быть указан номер порта; это отменяет значение по умолчанию. Можно использовать два разных формата, как показано в этом примере:

local_interfaces = <; 192.168.3.4.1234; \
                      [192.168.112.124]:5678

Первый (добавление точки и номера порта к IP-адресу) позволяет избежать необходимости менять разделитель списка[7]. Второй — это тот же формат, в котором комбинация IP-адреса и порта отображается в файлах журнала и строках заголовка Received:.

Вы можете настроить несколько портов на одном интерфейсе, повторив IP-адрес, например:

local_interfaces = 192.168.2.5.25 : 192.168.2.5.26

Переменные $interface_address и $interface_port содержат адрес интерфейса и порт для каждого сообщения, полученного через TCP/IP.

Следующие проверки выполняются, когда демон получает входящее SMTP-соединение:

Если ни одна из проверок не прошла успешно, создается новый процесс для продолжения обработки входящего соединения; теперь демон готов принять другое SMTP-соединение. Хотя он выполняет очень небольшую обработку перед разветвлением, в это время могут поступать другие входящие соединения. Операционная система поддерживает очередь ожидающих соединений, длина которой указывается в smtp_connect_backlog. После ожидания этого количества соединений последующие попытки соединения должны быть отклонены на уровне TCP/IP. Однако в некоторых операционных системах при таких обстоятельствах время ожидания попыток подключения истекло. Значение по умолчанию 20 является консервативным. Для больших систем, возможно, стоит увеличить это значение, возможно существенно (50 — разумное число).

Помимо входящих TCP/IP-соединений, есть еще два события, которые запускают процесс демона. Первый — это его таймер, в случае, если он настроен на периодический запуск процессов обработчика очереди. Всякий раз, когда таймер истекает, создается новый процесс запуска очереди, если только queue_run_max не больше нуля, а количество все еще работающих процессов запуска очереди больше или равно его значению.

Другим полезным событием является поступление сигнала SIGHUP. Вы должны посылать демону сигнал SIGHUP всякий раз, когда файл конфигурации Exim'а обновляется. Демон реагирует, закрывая все сокеты, которые он прослушивает, и повторно выполняя себя, тем самым перечитывая файл конфигурации. Рекомендуется проверить конец основного журнала, чтобы убедиться, что демон успешно перезапустился. Следствием того, как обрабатывается SIGHUP, является то, что память о количестве входящих SMTP-соединений и запущенных процессов обработчика очереди теряется после SIGHUP.

Демон должен отслеживать завершение процессов приема SMTP и процессов обработчика очередей, чтобы вести подсчет активных процессов и воздерживаться от запуска новых при достижении пределов. Однако завершение этих процессов не приводит к пробуждению демона. Вместо этого демон проверяет завершенные процессы всякий раз, когда он просыпается по любой другой причине, что часто происходит в загруженных системах. В системах, где мало что происходит, иногда можно увидеть «зомби» (несуществующие) процессы, которые являются дочерними элементами демона. Это совершенно нормально; они убираются при следующем пробуждении демона.

  1. Это также удобно при использовании -oX, поскольку позволяет избежать использования метасимволов оболочки.

  2. Подробную информацию о средней нагрузке системы см. в команде Unix uptime.

11.7.1 Обзор параметров демона

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

daemon_smtp_port (string, default = unset)

Этот параметр указывает порт SMTP по умолчанию, который прослушивает демон. Его можно переопределить, указав номер порта с IP-адресом в опции local_interfaces или используя -oX в командной строке. Если этот параметр не установлен, используется стандартный SMTP-порт 25.

local_interfaces (string, default = unset)

Строка должна быть списком IP-адресов, разделенных двоеточиями. Каждый адрес может дополнительно включать порт в любом из следующих форматов:

<ip address>.<port number>
[<ip address>]:<port number>

Если значение local_interfaces не установлено, демон выдает общий listen(), который принимает входящие SMTP-соединения на любом интерфейсе, используя порт по умолчанию. В противном случае он прослушивает только указанные здесь интерфейсы (и порты). Ошибка возникает, если не удается привязать прослушивающий сокет к любому из перечисленных интерфейсов. Содержимое local_interfaces также используется как список адресов локальных хостов при маршрутизации почты и проверке почтовых циклов (6.5).

queue_run_max (integer, default = 5)

Эта опция управляет максимальным количеством процессов обработчика очередей, которые демон Exim будет запускать одновременно. Это не означает, что он запускает их все сразу, а скорее то, что если максимальное количество все еще работает, когда приходит время запускать еще один, он воздерживается от его запуска. Это может произойти с очень большими очередями и/или очень медленной доставкой. Помните, что Exim не является централизованной системой. Это ограничение применяется только к одному процессу демона. Если обработчик очереди запущен другим способом (например, вручную администратором), он не учитывается в этом лимите. Кроме того, если демон перезапускается или отправляется сигнал SIGHUP, он теряет память о ранее запущенных обработчиках очередей.

smtp_accept_max (integer, default = 20)

Это определяет максимальное количество одновременных входящих SMTP-соединений, которые Exim примет. Это относится только к прослушивающему демону; нет контроля (в Exim), когда inetd обрабатывает входящий SMTP. Если установлено нулевое значение, ограничение не применяется, но делать это не рекомендуется.

smtp_accept_max_per_host (string, default = unset)

Эта опция ограничивает количество одновременных IP-подключений с одного хоста (строго, с одного IP-адреса) к демону Exim. Строка расширяется, чтобы разрешить применение разных ограничений к разным хостам по ссылке на $sender_host_address. После достижения предела дополнительные попытки подключения с того же хоста отклоняются с кодом ошибки 421. По умолчанию ограничение не установлено. Если используется этот параметр, smtp_accept_max должен быть ненулевым.

smtp_accept_queue (integer, default = 0)

Если количество одновременных входящих SMTP-соединений, обрабатываемых демоном прослушивания, превышает это значение, полученные сообщения просто помещаются в очередь; никакие процессы доставки не запускаются автоматически. Нулевое значение подразумевает отсутствие ограничений, и очевидно, что любое ненулевое значение полезно, только если оно меньше значения smtp_accept_max (если оно не равно нулю). См. также queue_only, queue_only_load, queue_smtp_domains и различные параметры командной строки -od.

smtp_accept_reserve (integer, default = 0)

Если значение smtp_accept_max больше нуля, этот параметр указывает количество SMTP-соединений, зарезервированных для соединений с хостов, указанных в smtp_reserve_hosts.

smtp_connect_backlog (integer, default = 20)

Указывает максимальное количество ожидающих SMTP-соединений. Exim передает это значение в систему TCP/IP, когда устанавливает своего слушателя. После того, как это количество подключений ожидает внимания демона, последующие попытки подключения будут отклонены на уровне TCP/IP.

smtp_load_reserve (fixed point, default = unset)

Всякий раз, когда средняя загрузка системы выше этого значения, входящие SMTP-соединения принимаются только от тех хостов, которые соответствуют записи в smtp_reserve_hosts.

smtp_reserve_hosts (host list, default = unset)

Эта опция определяет хосты, для которых зарезервированы SMTP-соединения; см. smtp_accept_reserve и smtp_load_reserve.

11.8 Процессы приема

Процессы приема для обработки входящих SMTP-сообщений от удаленных хостов запускаются демоном или inetd[9]. Один процесс приема SMTP может принимать любое количество сообщений. Однако, если полученное число превышает значение smtp_accept_queue_per_connection (по умолчанию 10), для остальных процессов автоматической доставки не запускается. Они просто помещаются в очередь для следующего обработчика очереди.

Соединение TCP/IP от локального процесса обрабатывается так же, как и соединение с удаленного хоста, даже если оно использует адрес замыкания на себя 127.0.0.1 (или ::1 в системе IPv6).

Другим способом, которым локальный процесс может послать сообщение, является запуск самого процесса приема Exim (то есть запуском /usr/sbin/sendmail или /usr/lib/sendmail в новом процессе). Сообщение передается процессу Exim на его стандартный ввод. Это можно сделать несколькими способами (14.3).

Получение сообщения состоит из копирования его содержимого в пару файлов -D и -H в буферной области Exim'а. Как только эти файлы были успешно записаны, прием завершен, и Exim возвращает отправителю ответ об успехе.

Существование файла -H означает наличие сообщения в очереди. Отдельного списка сообщений нет; файлы — это очередь. Как только файл -H появляется (путем переименования временного имени), другой процесс Exim может начать работу по доставке сообщения. Процесс, который получает сообщение, создает процесс доставки до его завершения, за исключением следующих обстоятельств:

Есть еще два варианта, которые могут задерживать доставку некоторых сообщений до запуска следующей очереди, но не обязательно задерживать все доставки. Это параметры queue_domains и queue_smtp_domains (или -odqs) (12.12).

  1. В большинстве инсталляций Exim используется демон, и в этой книге это обычно предполагается. Однако, если администратор по какой-либо причине хочет маршрутизировать все входящие соединения TCP/IP через inetd, Exim может справиться с таким способом работы. Тем не менее, это, вероятно, будет менее эффективным в загруженной системе.

11.9 Процессы обработчика очереди

Работа процесса запуска очереди состоит в том, чтобы запустить процессы доставки для всех сообщений, которые находятся в очереди Exim'а, ожидая завершения каждого процесса перед запуском следующего. Другими словами, один процесс запуска очереди проходит через очередь, пытаясь доставить только одно сообщение за раз. Он (по умолчанию) не делает различий между сообщениями, в которых произошла неудачная попытка доставки, и теми, которые были помещены в очередь без запуска процесса немедленной доставки. Однако он пропускает замороженные сообщения.

Обратите внимание, что обработчик очереди сам по себе не выполняет никакой работы по доставке. Это остается за процессами доставки, которые он создает. Все, что делает обработчик очереди, — это организовывает процессы доставки для всех ожидающих сообщений контролируемым образом.

Процессы запуска очереди должны запускаться через регулярные промежутки времени, и наиболее распространенный способ сделать это — использовать такую опцию, как -q15m (каждые 15 минут) в команде, которая запускает демон Exim. Однако внешние механизмы, такие как cron, могут использоваться для запуска обработчиков очередей с помощью команды:

exim -q

Несколько процессов обработчика очередей могут быть активны одновременно. Демон не запускает новый, если максимальное количество (указанное в queue_run_max) все еще активно. Обработчики очередей, запущенные не демоном, не учитываются.

В очень загруженных средах может быть желательно контролировать общее количество процессов доставки Exim, которые выполняются одновременно. Если вы установите queue_only, немедленная доставка при получении подавляется, поэтому единственными процессами доставки являются те, которые запускаются обработчиками очередей. Если вы настроите демон на частый запуск обработчиков очередей (скажем, каждую минуту, запуская его с параметром -q1m), но ограничите максимальное количество, которое может быть одновременно активным, например, установив:

queue_run_max = 10

одновременно никогда не будет выполняться более 10 процессов доставки, хотя для выполнения фактической доставки будут созданы дополнительные подпроцессы[10].

  1. Если remote_max_parallel больше единицы, также может быть несколько подпроцессов для удаленной доставки (4.9).

11.9.1 Специальные виды работы с очередью

Для особых целей доступны дополнительные параметры запуска очереди; они обычно не используются в регулярных периодических запусках очереди, а либо указываются для одноразовых запусков очереди системным администратором, либо генерируются в ответ на какое-то конкретное событие, такое как подключение коммутируемого хоста.

При обычном выполнении очереди процессы доставки проверяют данные повторных попыток на наличие адресов и хостов и воздерживаются от попыток доставки для тех адресов, время повторных попыток которых еще не наступило. Это можно обойти, запустив обработчик очереди с параметром -qf, который инициирует попытку доставки для всех адресов. Еще более мощной опцией является -qff, которая, помимо переопределения времени повтора, не позволяет пропускать замороженные сообщения.

Если к любой из опций -q добавляется i (для «начальной доставки»), сообщения, которые ранее были опробованы, пропускаются. Это может быть полезно, если вы помещаете сообщения в очередь без немедленной доставки (например, с помощью queue_only или -odq) и хотите, чтобы обработчик очереди обрабатывал только новые сообщения. Если в конце любой из опций -q добавить l (строчную букву L), будут осуществляться только локальные доставки. Эти варианты опции -q приведены в таблице 11-1.

Таблица 11-1: Параметры обработчика очереди (-q)
Опция Значение
-q Запуск одной очереди, учитывание времени повтора
-qf Запуск одной очереди, переопределение времени повтора
-qff Запуск одной очереди, переопределение времени повтора, включение замороженных сообщений
-gi, -qfi, -qffi Только первые попытки доставки
-ql, -qfl, -qffl Только местные доставки
-qil, -qfil, -qffil Оба вышеперечисленных

Можно запустить только часть очереди или выбрать только сообщения, отправители или получатели которых соответствуют определенным шаблонам (20,5).

Обычные исполнители очередей обрабатывают ожидающие сообщения в произвольном порядке, который, вероятно, каждый раз будет другим. Это полезно, когда есть одно конкретное сообщение, которое провоцирует сбои доставки для некоторого адреса. Например, очень большие сообщения иногда вызывают тайм-ауты или другие проблемы при передаче на удаленные узлы, в то время как сообщения меньшего размера на те же узлы могут пройти. После временной ошибки, такой как тайм-аут, Exim не готов снова попробовать тот же хост в течение некоторого времени, поэтому любые другие сообщения для того же адреса, скорее всего, будут пропущены в той же очереди. Обрабатывая сообщения «рандомно», есть шанс, что в какой-то будущей очереди сообщения без проблем будут обработаны первыми и, таким образом, будут доставлены вместо задержки из-за сообщения об ошибке.

Вы можете указать, что Exim не должен выполнять эту рандомизацию, а вместо этого должен обрабатывать сообщения в порядке их идентификаторов, что фактически является порядком их поступления, установив queue_run_in_order. Однако для этого редко бывает веская причина, и это может снизить производительность в системах с большой очередью, в которой задан раздельный буферный каталог. Причина в том, что все подкаталоги входного каталога должны быть просканированы до начала любых доставок, чтобы получить полный список сообщений и отсортировать их по ID. Когда порядок не ограничен, подкаталоги можно обрабатывать по одному.

11.10 Процессы доставки

Процессы доставки являются наиболее сложными процессами Exim. Каждый процесс доставки обрабатывает одну попытку доставки только для одного сообщения, хотя у сообщения может быть много получателей и, следовательно, много действий по доставке, и все они выполняются в подпроцессах. Процессы доставки могут быть запущены в результате прибытия сообщения, процессом запуска очереди или администратором, использующим параметр -M. Например:

exim -M 11w03z-00042J-00

запускает процесс доставки для данного сообщения, игнорируя любые повторные попытки.

Когда процесс доставки запускается, он обычно сначала проверяет, не заморожено ли сообщение, над которым он работает. Если да, то в лог пишет:

Message is frozen

и завершается, ничего не делая. Однако процессу доставки можно приказать обрабатывать замороженные сообщения независимо (это происходит, если он запущен в результате, например, -M или -qff), а также есть опция auto_thaw, которая автоматически «отмораживает» (размораживает) сообщения после того, как они были заморожены в течение определенного периода времени.

Прежде чем перейти к обычным действиям доставки, процесс доставки запускает системный фильтр, если он настроен. Возможности фильтрации Exim описаны в главе 10. Системный фильтр может добавлять или удалять строки заголовков, изменять список получателей или вызывать зависание сообщения или отказ всех его адресов получателей.

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

Если сообщение должно быть доставлено более чем на один удаленный хост, Exim может быть сконфигурирован для запуска нескольких доставок SMTP одновременно, установив значение remote_max_parallel больше единицы, как в этом примере:

remote_max_parallel = 5

Когда это сделано и необходимо выполнить несколько удаленных доставок, процесс доставки одновременно разветвляется на несколько подпроцессов, вплоть до заданного максимума. Значение remote_max_parallel управляет максимальным количеством параллельных доставок, созданных только одним процессом доставки Exim. Поскольку у Exim нет центрального менеджера очередей, нет способа контролировать общее количество одновременных доставок, выполняемых на локальном хосте, если сконфигурирована немедленная доставка входящих сообщений.

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

Окончательные действия процесса доставки зависят от того, были ли полностью обработаны все получатели сообщения (либо доставлены, либо возвращены). Если адресов больше нет, все буферные файлы для сообщения удаляются, а в основной журнал записывается «Completed». В противном случае есть некоторые адреса, которые подверглись временным ошибкам или были пропущены по какой-либо причине. Предупреждающее сообщение о задержке доставки отправляется отправителю сообщения, если это необходимо (19.8.3), и файлы буфера обновляются для записи тех адресов, которые были доставлены или возвращены в текущей попытке доставки.

11.10.1 Варианты доставки

Процесс доставки обычно работает со всеми адресами получателей в сообщении. На хосте, который не подключен к Интернету постоянно, это неуместно; вы хотите доставить на локальные адреса, но сохраните удаленные, пока хост не будет в сети. Параметры queue_domains и queue_smtp_domains позволяют указать это поведение (12.12).

Иногда может быть полезно указать порядок, в котором происходят удаленные доставки. Это делается путем установки remote_sort_domains в список доменов; затем удаленные доставки на адреса в этих доменах выполняются в указанном порядке. После этого происходят другие удаленные доставки в непредсказуемом порядке. Например, вы можете указать, что доставка на хосты в вашей локальной сети должна выполняться в первую очередь, с помощью такой настройки, как:

remote_sort_domains = *.mydomain.example

Наконец, есть опция, называемая hold_domains, которая указывает список доменов, которые Exim не должен доставлять, за исключением случаев, когда доставка принудительна администратором. Адрес в этих доменах откладывается каждый раз, когда он встречается в процессе доставки. Эта функция предназначена в качестве временной оперативной меры для задержки доставки почты, пока решается какая-то проблема или тестируется какая-то новая конфигурация.

11.11 Обзор типов процессов обработки сообщений

Всякий раз, когда запускается бинарный файл Exim, тип процесса управляется параметрами командной строки, с которыми он вызывается. Варианты для четырех видов процесса обработки сообщений сведены в таблицу 11-2.

Таблица 11-2: Типы процессов обработки сообщений
Параметр Значение
-bd Процесс демона, прослушивающий SMTP, разветвляет процессы приема SMTP
-bs из inetd Процесс приема SMTP
-bs (не inetd) Локальный процесс приема SMTP
-bS Локальный пакетный процесс приема SMTP
-M Процесс принудительной доставки определенного сообщения
-g Одиночный процесс обработчика очереди, запускает процессы доставки
-q <время> Процесс демона, периодически запускает обработчик очереди
-t или ничего Локальный процесс приема

Локальный процесс приема может быть создан любым другим процессом, но создание других типов процессов требует, чтобы вызывающая сторона имела административные привилегии Exim (19.3).

11.12 Другие типы процессов

Помимо обработки сообщений, для администрирования Exim или для отладки конфигурации используются некоторые другие процессы. Для их настройки используются специальные параметры конфигурации (подробнее см. главу 20). Например, опция -bp заставляет Exim перечислять сообщения в своей очереди.

Глава 12
Ошибки доставки и повторная попытка

Эта глава посвящена временным ошибкам доставки и тому, как Exim с ними справляется. В идеальном мире каждое сообщение либо доставлялось бы с первой попытки, либо отбрасывалось бы, и временных ошибок не возникало бы. В реальном мире этого не происходит; хосты время от времени отключаются или не отвечают, а сетевые соединения терпят неудачу. MTA должен быть готов удерживать сообщения в течение некоторого времени, время от времени пытаясь их доставить. Необходимы некоторые правила для определения того, как часто должны повторяться попытки и когда следует отказаться от них, поскольку повторные попытки продолжаются слишком долго.

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

12.1 Повторная попытка после ошибок

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

Некоторые MTA используют повторные попытки на основе сообщений (message-based); они независимо применяют расписание повторов к каждому сообщению. Это может привести к тому, что хосты будут опробованы несколько раз подряд. Exim не такой; для сбоев, не связанных с конкретным сообщением, используется повторная попытка на основе хоста. Если доставка на хост временно завершается сбоем, все сообщения, направляемые на этот хост, задерживаются до следующего времени повторной попытки.

На практике Exim обычно основывает операции повтора на ошибочном IP-адресе, а не на имени хоста. Если хост имеет более одного IP-адреса, каждый из них обрабатывается отдельно в отношении повторных попыток. В последующем обсуждении мы используем слово «хост», говоря об ошибках удаленной доставки, чтобы его было легче читать. Однако следует понимать, что это относится к одному IP-адресу, так что хост с несколькими сетевыми интерфейсами фактически рассматривается как несколько независимых хостов.

Информация о временных сбоях доставки хранится в базе данных подсказок, называемой retry, в подкаталоге db каталога спула Exim. Вы можете прочитать его содержимое, если хотите, с помощью утилиты exinext (21.6.2). Информация включает сведения об ошибке, время первого сбоя, время самого последнего сбоя и время, до которого нецелесообразно повторять попытку.

Exim использует набор настраиваемых правил повтора (retry rules) в отдельном разделе конфигурационного файла, чтобы решить, когда в следующий раз попытаться выполнить неудачную доставку. Эти правила позволяют указать фиксированные или увеличивающиеся интервалы повторных попыток или их комбинацию. Детали правил приведены далее в этой главе, после описания различных видов ошибок.

12.2 Ошибки удаленной доставки

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

12.2.1 Ошибки хоста

Ошибка хоста не связана ни с конкретным сообщением, ни с конкретным получателем сообщения. Ошибки хоста следующие:

Когда в начале соединения или в ответ на команду EHLO или HELO выдается постоянный код ошибки SMTP (5<xx>), все адреса, которые перенаправляются на хост, завершаются ошибкой и возвращаются отправителю в сообщении об отказе.

Другие виды ошибок хоста рассматриваются как временные и вызывают отсрочку всех адресов, маршрутизируемых на хост. Данные о повторных попытках создаются для хоста, и они не повторяются ни для какого сообщения, пока не наступит время повторной попытки. Если текущий набор адресов не доставлен на какой-либо резервный хост текущим процессом доставки, сообщение добавляется в список тех, кто ожидает отказавший хост[1]. Это подсказка, которую Exim использует, если делает последующую успешную доставку на хост. Он проверяет, есть ли какие-либо другие сообщения, ожидающие того же хоста, и, если да, отправляет их, используя то же SMTP-соединение.

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

12.2.2 Ошибки сообщений

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

Для временной ошибки сообщения все адреса, которые направляются на хост, откладываются. Данные повтора не создаются для хоста, вместо этого создается запись повтора для комбинации имени хоста и идентификатора сообщения. Сообщение не добавляется в список ожидающих этого хоста. Это гарантирует, что сообщение о сбое не будет отправлено на этот хост снова, пока не наступит время повторной попытки. Однако другие сообщения, направляемые на хост, не затрагиваются, поэтому, если какое-либо свойство сообщения вызывает ошибку, это не останавливает доставку другой почты.

Если удаленный хост указывает поддержку параметра SIZE в своем ответе на EHLO, Exim добавляет SIZE=<nnn> к команде MAIL. Таким образом, слишком большое сообщение вызывает постоянную ошибку сообщения, потому что оно приходит как ответ на MAIL. Однако, когда SIZE не используется, некоторые хосты реагируют на неприемлемо большие сообщения, просто разрывая соединение. Это приводит к временной ошибке сообщения, если она обнаруживается после отправки всего сообщения. Хосты с лучшим поведением возвращают постоянную ошибку после окончания сообщения; это позволяет отбрасывать сообщение без повторных попыток.

12.2.3 Ошибки получателя

Ошибка получателя связана с конкретным получателем сообщения. Ошибки получателя следующие:

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

Сообщение не добавляется в список ожидающих этого хоста. Использование хоста для других адресов получателей не затрагивается, и, за исключением случаев тайм-аута, другие получатели обрабатываются независимо и могут быть успешно доставлены в текущем сеансе SMTP. После тайм-аута, конечно, продолжить сеанс невозможно, поэтому все обращения откладываются. Однако те, которые не завершились ошибкой, не имеют последующих задержек повторных попыток. Поэтому, если один получатель вызывает проблемы, у других есть шанс пройти, когда последующая попытка доставки будет предпринята до времени повторной попытки неудачливого получателя.

12.2.4 Проблемы классификации ошибок

Было замечено, что некоторые хосты дают временные ответы об ошибках на каждую команду MAIL в определенное время («недостаточно места» ((insufficient space)). Они рассматриваются как ошибки сообщения. Было бы неплохо, если бы такие обстоятельства можно было бы распознавать вместо ошибок хоста, и повторять данные для самого хоста. но это невозможно в текущем дизайне Exim. Что на самом деле происходит, так это то, что создаются данные повтора для каждой комбинации (хост, сообщение).

Причина, по которой тайм-ауты после MAIL и RCPT обрабатываются особым образом, заключается в том, что иногда они могут возникать в результате того, что процедуры проверки удаленного хоста занимают очень много времени. Exim делает это предположение и обрабатывает их так, как если бы был получен временный ответ об ошибке. Тайм-аут после последней точки обрабатывается особым образом, поскольку известно, что некоторые неработающие реализации не могут распознать конец сообщения, если последний символ последней строки является двоичным нулем. Таким образом, полезно ли рассматривать этот случай как ошибку сообщения?

Тайм-ауты в другое время рассматриваются как ошибки хоста, предполагающие проблему с хостом или подключением к нему. Если тайм-аут после MAIL, RCPT или последней точки действительно является проблемой подключения, предполагается, что при следующей попытке тайм-аут, вероятно, возникнет в какой-то другой точке диалога, в результате чего это будет рассматриваться как ошибка хоста.

Имеются экспериментальные данные о том, что некоторые MTA разрывают соединение после завершающей точки, если им по какой-либо причине не нравится содержимое сообщения. Это противоречит RFC, который указывает, что должен быть дан ответ 5<xx>. Вот почему Exim рассматривает этот случай как ошибку сообщения, а не как ошибку хоста, чтобы не задерживать другие сообщения на тот же хост.

12.2.5 Доставка на несколько хостов

Во всех случаях временной ошибки доставки, если для текущего набора адресов доступны другие хосты (или IP-адреса) (например, из нескольких записей MX), они проверяются в текущем запуске на наличие любых недоставленных адресов, при условии, конечно, их собственных данных повторной попытки. Это означает, что вновь созданные данные о повторной попытке получателя не влияют на текущий процесс доставки; вместо этого он вступает в силу при следующем запуске процесса доставки сообщения.

12.3 Ошибки локальной доставки

Удаленные доставки — не единственные случаи, когда может возникнуть временная ошибка; такие ошибки могут возникать и при местных доставках. Два наиболее распространенных случая заключаются в следующем:

Механизм вычисления времени повтора такой же, как и для ошибок удаленной доставки, но задержки повтора применяются только к доставке в очереди. Когда доставка не является частью выполнения очереди (как правило, немедленная доставка при получении сообщения), роутеры всегда работают, и всегда предпринимаются попытки локальной доставки, даже если для них установлено время повтора. Это улучшает поведение, если одно конкретное сообщение вызывает проблемы (например, вызывает переполнение квоты или провоцирует ошибку в файле фильтра). Если при такой доставке возникает временный сбой, данные повторной попытки обновляются как обычно, а последующие попытки доставки из очереди выполняются только по истечении времени повторной попытки для локального адреса.

12.4 Ошибки маршрутизации

Также возможны временные ошибки во время маршрутизации. Чаще всего они вызваны следующими причинами:

Обработка повторных попыток применяется к маршрутизации адреса, а также к транспортировке сообщения, но только для процессов доставки, запущенных в прогонах очереди (как объяснялось в предыдущем разделе). Правила повтора не различают маршрутизацию и транспортировку, поэтому невозможно, например, указать различное поведение для ошибок маршрутизации домена snark.example и ошибок доставки на хост snark.example. Однако, несмотря на то, что они используют одно и то же правило повтора, фактическое время повтора для маршрутизации и транспортировки данного домена поддерживается независимо.

12.5 Правила повтора

Правила для контроля того, как часто Exim повторяет попытки временно сбойного адреса, содержатся в отдельной части конфигурационного файла, представленной строкой «begin retry» (начать повторную попытку). Каждое правило повтора занимает одну строку и состоит из трех частей: шаблона, имени ошибки и списка параметров повтора. На рис. 12-1 показано единственное правило, представленное в файле конфигурации по умолчанию. Многие установки работают только с этим правилом по умолчанию.

Рисунок 12-1: Правило повтора по умолчанию

Когда Exim необходимо вычислить, когда в следующий раз попытаться доставить определенный адрес, он выполняет поиск правил повторных попыток по порядку и использует первое попавшееся, которое соответствует определенным критериям, в зависимости от конкретной обнаруженной ошибки. Если подходящее правило не найдено, временная ошибка преобразуется в постоянную, а адрес возвращается после первой попытки доставки. Поэтому окончательное правило обычно должно содержать звездочки в первых двух полях, как показано на рис. 12-1, чтобы оно применялось ко всем случаям, не охватываемым предыдущими правилами.

12.5.1 Шаблоны правил повтора

Шаблон, который запускает правило повтора, может быть любым элементом, который может появиться в списке адресов. Возможности включают регулярные выражения и несколько форм поиска (18.7). Однако доменное имя (возможно, с подстановочными знаками) является наиболее распространенным типом шаблона повторных попыток. Шаблон обрабатывается как список адресов из одного элемента, что означает, что он расширяется перед проверкой адреса.

Правила повтора для удаленной доставки

При поиске правила повтора после неудачной попытки маршрутизации для удаленного домена (например, после тайм-аута DNS) каждая строка в конфигурации повтора проверяется только для домена в адресе. Однако при поиске правила повтора после неудачной попытки удаленной доставки (например, тайм-аут соединения) каждая строка в конфигурации повтора сначала проверяется на соответствие имени удаленного хоста, а затем имени домена в адресе. Например, если записи MX для a.b.c.d:

a.b.c.d.  MX  5  x.y.z.
          MX  6  p.q.r.
          MX  7  m.n.o.

и правила повтора:

p.g.r    *    F,24h,30m;
a.b.c.d  *    F,4d,45m;

затем при неудачной доставке адреса xyz@a.b.c.d хосту p.g.r используется первое правило для определения времени повтора, но для всех остальных хостов домена a.b.c.d используется второе правило. Второе правило также используется, если маршрутизация к a.b.c.d терпит временный сбой.

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

Правила повтора для местных доставок

В случае доменов, которые обрабатываются локально, временные сбои обычно связаны как с локальной частью, так и с доменом. Например, сбой доставки, вызванный переполненным почтовым ящиком, характерен для одного пользователя. Поэтому при работе с этим типом ошибок нам нужно настроить время повтора для одного конкретного адреса, а не для всего домена. Exim обрабатывает это требование с помощью опции retry_use_local_part, которая существует как общая опция как для роутеров, так и для транспортов. Значение по умолчанию — true для локальных транспортов и роутеров, для которых установлена check_local_user, и false во всех остальных случаях. Таким образом, значение по умолчанию верно для «нормальной» доставки в локальные почтовые ящики или команд конвейера.

Когда Exim имеет дело с временной ошибкой доставки для адреса, который был обработан драйвером с retry_use_local_part, установленным в true, полный адрес (user@domain) сопоставляется с любыми правилами повтора, которые начинаются с регулярных выражений или шаблонов, содержащих локальные части. Однако если в шаблоне нет локальной части, которая не является регулярным выражением, локальная часть адреса не используется при сопоставлении. Таким образом, такая запись, как:

lookingglass.example        *  F,24h,30m;

соответствует любому адресу, домен которого lookingglass.example, независимо от того, установлен ли параметр retry_use_local_part, тогда как:

alice@lookingglass.example  *  F,24h,30m;

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

12.5.2 Имена ошибок правила повтора

Второе поле в правиле повтора — это имя конкретной ошибки или звездочка, которая соответствует любой ошибке. Ошибки, которые можно указать, перечислены в таблице 12-1.

Таблица 12-1: Поле ошибки в правилах повторных попыток
Ошибка Значение
auth_failed Аутентификация не удалась
quota Превышена квота при локальной доставке
quota_<time> Превышена квота при локальной доставке, и почтовый ящик не был прочитан в течение <time>
refused_MX Соединение отклонено; узел получен из MX-записи
refused_A Соединение отклонено; хост не получен из записи MX
refused Любое соединение отклонено
timeout_connect Время ожидания соединения истекло
timeout_DNS Время поиска DNS истекло
timeout Любой тайм-аут

Это поле позволяет применять разные алгоритмы повтора к разным видам ошибок; некоторые примеры показаны в следующем разделе. Ошибка сбоя аутентификации возникает, когда сервер указан в hosts_require_auth в транспорте smtp, но клиент не может пройти аутентификацию. Ошибки квот применяются как к системным квотам, так и к собственному механизму квот Exim'а в транспорте appendfile.

12.5.3 Наборы параметров правила повтора

Оставшаяся часть правила повтора представляет собой последовательность наборов параметров повтора, разделенных точкой с запятой и необязательным пробелом. Например:

F,3h,10m; G,16h,40m,1.5; F,4d,6h

Каждый набор состоит из:

<letter>, <cutoff times, <arguments>;

Например, в:

F,8h,15m;

буква F, время отсечки 8 часов, аргумент всего один. Буква идентифицирует алгоритм вычисления нового времени повтора; время отсечки — это время, после которого этот алгоритм больше не применяется, а аргументы изменяют действие алгоритма. Есть два доступных алгоритма:

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

  1. G был выбран потому, что он находится рядом с F, а также потому, что интервалы образуют геометрическую прогрессию.

12.6 Расчет времени повтора

Когда Exim вычисляет время повтора из правила повтора, наборы параметров сканируются слева направо, пока не будет достигнут тот, чье время отсечки еще не прошло. Время отсечки измеряется с момента обнаружения первого сбоя для хоста или домена (в сочетании с локальной частью, если необходимо), а не с момента получения сообщения.

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

*  *  F,2h,15m; G,16h,1h,1.5; F,4d,6h;

В течение первых 2 часов после обнаружения сбоя время следующей попытки рассчитывается как 15 минут после самого последнего сбоя. После этого перед следующей повторной попыткой существует интервал в 1 час, который каждый раз увеличивается в 1,5 раза. пока не пройдет 16 часов с момента первого отказа. После этого повторные попытки происходят каждые 6 часов, пока не пройдет 4 дня.

На данный момент у Exim'а закончились алгоритмы повтора для адреса. В этом состоянии при любой доставке происходит временный сбой, он преобразуется в постоянный сбой тайм-аута и адрес отклоняется. Что произойдет, если придет новое сообщение на тот же адрес, описано в разделе 12.10.

Поскольку геометрическое правило повторных попыток может «разбежаться» и создать чрезвычайно длинные интервалы повторных попыток, существует параметр конфигурации, называемый retry_interval_max, который ограничивает максимальный интервал между повторными попытками. Его значение по умолчанию — 24h, это гарантирует, что все временно сбойные адреса проверяются не реже одного раза в день.

12.7 Использование времени повтора

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

Если новые сообщения для ошибочного адреса не приходят, минимальное время между повторными попытками — это интервал между процессами обработчика очереди. Нет особого смысла устанавливать время повтора 5 минут, если ваши обработчики очередей выполняются только один раз в час, если только нет значительного количества входящих сообщений (что может иметь место, например, в системе, которая отправляет все на смарт-хост).

12.8 Примеры правила повтора

Вот несколько примеров правил повтора, подходящих для использования, когда wonderland.example является доменом, который доставляется локально:

alice@wonderland.example quota     F,7d,3h
wonderland.example       quota_5d
wonderland.example       *         F,1h,15m; G,2d,1h,2;
lookingglass.example     *         F,24h,30m;
*                        refused_A F,12h,20m;
*                        *         F,2h,15m; G,16h,1h,1.5; F,5d,6h;

Первое правило устанавливает специальную обработку почты на адрес alice@wonderland.example при возникновении ошибки превышения квоты. Повторные попытки продолжаются каждые 3 часа в течение 7 дней. Второе правило обрабатывает ошибки превышения квоты для других локальных частей в wonderland.example в случае, когда почтовый ящик не читался в течение 5 дней. Отсутствие локальной части в шаблоне имеет тот же эффект, что и использование *@. Поскольку алгоритмы повторных попыток не предоставляются, сообщения, которые не удается выполнить из-за квоты, немедленно возвращаются, если почтовый ящик не читался в течение как минимум 5 дней. Если почтовый ящик читался в течение последних 5 дней, это правило не применяется, и вместо него используется следующее правило.

Третье правило обрабатывает все остальные ошибки для wonderland.example; повторные попытки происходят каждые 15 минут в течение часа, затем с геометрически увеличивающимися интервалами до тех пор, пока не пройдет 2 дня с момента первой неудачной доставки.

Четвертое правило контролирует все повторные попытки для домена lookglass.example, а оставшиеся два правила обрабатывают все остальные домены со специальными действиями для отказа в подключении от хостов, которые не были получены из записи MX. Ошибка «connection refused» (отказ в подключении) означает, что хост запущен и работает, но не прослушивает SMTP-порт. Это состояние существует в течение короткого времени при перезапуске хоста, но если оно продолжается в течение некоторого времени, возрастает вероятность того, что хост является рабочей станцией, чье имя ошибочно вкралось в адрес электронной почты, потому что она никогда не будет принимать SMTP. соединения. Поэтому имеет смысл возвращать такие адреса быстрее, чем обычно.

Последнее правило в конфигурации повтора всегда должно иметь звездочки в первых двух полях, чтобы обеспечить общий охват для любых адресов и ошибок, которые не имеют своей собственной специальной обработки, если, конечно, вы не хотите, чтобы такие адреса никогда не повторялись. В этом примере выполняется попытка каждые 15 минут в течение 2 часов, затем с интервалом, начинающимся с 1 часа и увеличивающимся в 1,5 раза до 16 часов, затем каждые 6 часов до 5 дней.

12.9 Тайм-аут повторных данных

Одна проблема с использованием Exim схемы повторных попыток на основе хоста, а не на основе сообщений, возникает, когда хост используется нечасто. Типичным примером является вторичный хост MX для некоторого домена. Предположим, что есть период сбоя сети, когда и первичный, и вторичный хост для домена недоступны. Данные о повторных попытках вычисляются для обоих из них. Когда сеть восстанавливается, почта доставляется на первичный сервер, оставляя информацию о повторных попытках для вторичного по-прежнему установленной. Могут пройти недели или месяцы, прежде чем будет предпринята повторная попытка подключения к вторичному серверу. Если затем произойдет сбой, Exim рискует сделать вывод, что все это время он не работал.

Чтобы обойти эту проблему, Exim ставит временные метки на данные, которые он записывает в свою базу данных повторных попыток. Когда он просматривает данные во время доставки, он игнорирует все, что старше значения, установленного в параметре retry_data_expire (по умолчанию 7 дней). Если, например, хост не пробовался в течение 7 дней, Exim попытается сделать это немедленно, когда придет сообщение для него, и если это не удастся, он рассчитает время повторной попытки, как если бы это было неудачно в первый раз.

Если хост действительно окончательно мертв, такое поведение время от времени вызывает всплеск повторных попыток, но только в том случае, если сообщения направляются на него редко. Если есть сообщение хотя бы раз в 7 дней, срок действия данных повтора никогда не истечет.

12.10 Длительные сбои

Специальная обработка происходит, когда адрес не работает так долго, что достигнуто время отсечки для последнего алгоритма. Это не зависит от того, как долго какое-либо конкретное сообщение не работает; важна продолжительность непрерывного сбоя для адреса. Для маршрутизации или локальных доставок последующий сбой приводит к тому, что Exim отключает адрес по таймауту, и он возвращается. С удаленной доставкой немного сложнее, потому что удаленный домен может маршрутизировать более чем к одному хосту, каждый из которых может иметь более одного IP-адреса. Срок действия адреса истекает только тогда, когда истекает время отсечки для всех IP-адресов. Например, если домен lookglass.example маршрутизируется записями MX как к tweedledum.example, так и к tweedledee.example, а правила повтора следующие:

tweedledum.example  *  F,1d,30m;
tweedledee.example  *  F,5d,2h;

тогда адрес alice@lookglass.example не истечет, пока tweedledum.example не будет отключен более одного дня, а tweedledee.example не будет отключен более пяти дней.

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

Одна из возможностей — попытаться доставить каждое сообщение, хотя это может привести к множеству неудачных попыток доставки. Локальные доставки не используют много ресурсов, так что Exim всегда пробует локальную доставку, даже если время ожидания адреса истекло ранее. Если доставка не удалась, адрес отбрасывается.

Удаленные доставки обрабатываются по-другому, чтобы избежать слишком большого количества потенциально дорогостоящих попыток доставки. Для каждого IP-адреса, прошедшего свое предельное время, Exim продолжает вычислять время повторных попыток, основываясь на окончательном алгоритме повторных попыток. До тех пор, пока не будет достигнуто время повторной попытки после отсечки для одного из IP-адресов, ошибочный адрес отбрасывается без фактической попытки доставки на удаленный хост. Это означает, что новое сообщение может прийти и быть возвращено без какой-либо попытки доставки. По сути, Exim говорит: «Этот хост был мертв в течение пяти дней, и я недавно попробовал его, поэтому пока не стоит пытаться снова». Если новое сообщение поступает после времени повтора хотя бы для одного из IP-адресов, делается одна новая попытка доставки на те IP-адреса, для которых истекло время повтора, и, если это все еще не удается, адрес возвращается и вычисляется новое время повторных попыток.

Окончательный интервал в правиле повтора часто бывает довольно длинным (в правиле по умолчанию он равен 6 часам). Если вы чувствуете, что это слишком долго для ожидания между повторными попытками отказавшего хоста, вы можете добавить дополнительный набор параметров специально для сокращения этого времени. Рассмотрим, например, правило по умолчанию с одним дополнительным набором параметров:

*  *  F,2h,15m; G,16h,1h,1.5; F,4d,6h; F,4d1h,1h;

Это правило действует в течение 4 дней и 1 часа вместо 4 дней. Само по себе это, конечно, не имеет большого значения, но когда это правило истекает, окончательный интервал повтора составляет 1 час вместо 6. Это означает, что с этого момента Exim пытается доставить на хост, если прошел по крайней мере 1 час с момента последнего сбоя. Если прошло менее часа, адрес будет возвращен без попытки доставки.

Аналогичное поведение можно указать другим способом, установив:

delay_after_cutoff = false

в транспорте smtp. Когда delay_after_cutoff равно false, если все IP-адреса, к которым маршрутизируют домены, прошли свое окончательное время отсечки, Exim пытается доставить на те IP-адреса, которые не были опробованы с момента прибытия сообщения. Если их нет или все они не работают, адрес отбрасывается. Другими словами, он не делает задержек при поступлении нового сообщения, а немедленно пробует IP-адреса с истекшим сроком действия, если только они не были опробованы с момента прибытия сообщения (предположительно, при доставке какого-либо другого сообщения). Если существует непрерывный поток сообщений для неисправных доменов, снятие задержки delay_after_cutoff означает, что будет гораздо больше попыток доставки на неисправные IP-адреса, чем в случае по умолчанию, когда delay_after_cutoff имеет значение true. Однако, если группа сообщений поступает более или менее одновременно, некоторые из них могут быть отклонены без попытки доставки.

12.11 Тайм-аут конечного адреса

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

Например, если соединение пункта назначения с Интернетом имеет низкое качество и имеет частые сбои, короткие сообщения могут быть доставлены, а более длинные сообщения почти всегда терпят неудачу. Всякий раз, когда сообщение успешно доставлено, «часы повтора» (retry clock) для хоста перезапускаются, поэтому время ожидания никогда не истекает. В результате ошибочные сообщения могут оставаться в очереди навсегда. Чтобы предотвратить это, Exim использует другое правило, называемое предельным временем ожидания адреса (ultimate address timeout), которое состоит из двух частей:

Вспомним более ранний пример, когда домен lookglass.example маршрутизировался по MX-записям на два хоста, время повтора которых устанавливалось по следующим правилам:

tweedledum.example  *  F,1d,30m;
tweedledee.example  *  F,5d,2h;

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

12.12 Периодически подключаемые хосты

Exim был разработан для работы в среде, где все хосты постоянно подключены к Интернету. Однако хосты, которые используют коммутируемое соединение для подключения к Интернету только периодически, стали довольно распространенными, потому что этот способ работы дешевле. Существуют также хосты с соединениями ISDN, администраторы которых хотят контролировать использование ISDN. Например, они не хотят, чтобы соединение устанавливалось каждый раз, когда локальный пользователь отправляет сообщение удаленному адресату.

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

12.12.1 Входящая почта для периодически подключаемого хоста

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

Если вы используете Exim на сервере, настоятельно рекомендуется использовать первый метод, особенно если, вероятно, будет больше, чем тривиальное количество сообщений, ожидающих подключения клиента. Простая стратегия очередей Exim основана на предположении, что большинство сообщений могут быть доставлены быстро, и поэтому размеры очередей обычно будут небольшими. Сканирование больших очередей может замедлить работу. Кроме того, если вы используете второй подход, вы смешиваете два типа сообщений в очереди: те, у которых есть проблемы с доставкой, и те, которые ожидают подключения клиентского хоста. Это усложняет администрирование.

Если вы используете очередь Exim как место для хранения сообщений для коммутируемых клиентов (и, несмотря на более ранние замечания, это вполне разумно, если, скажем, есть только один клиент, который получает несколько сообщений в день), есть некоторые параметры конфигурации, которые могут улучшить производительность. Вы должны установить длительный период повтора для прерывистых хостов. Например:

cheshire.wonderland.example  *  F,5d,24h

Это предотвратит множество неудачных попыток доставки, но Exim помнит, какие сообщения он поставил в очередь для этого хоста. Как только клиент подключается к сети, доставка одного сообщения может быть форсирована (либо с помощью параметров -M или -R, либо с помощью SMTP-команды ETRN). Это приводит к доставке всех сообщений, находящихся в очереди, часто по одному SMTP-соединению. Пока хост остается подключенным, любые новые сообщения доставляются немедленно.

Если подключающиеся хосты не имеют фиксированных IP-адресов (то есть, если хосту присваивается другой IP-адрес каждый раз, когда он подключается), механизмы повтора Exim'а на удерживающем хосте становятся запутанными, потому что IP-адрес обычно используется как часть ключевой строки для хранения информации о повторных попытках. Этого можно избежать, установив retry_include_ip_address в значение false для транспорта smtp. Когда это сделано, повторная попытка будет основана только на имени хоста. Это имеет недостатки для постоянно подключенных хостов, которые имеют более одного IP-адреса, поэтому лучше организовать отдельный транспорт для этих периодически подключаемых хостов. Например, в конфигурации может быть два транспорта smtp, подобных этому:

normal_smtp:
  driver = smtp

special_smtp:
  driver = smtp
  no_retry_include_ip_address

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

dnslookup:
  driver = dnslookup
  transport = ${if match{$domain}\
              {\N\.variable\.example$\N} \
              {special_smtp}{normal_smtp}}

Это делает обычную маршрутизацию DNS, но выбирает транспорт special_smtp для доменов, имена которых заканчиваются на .variable.example, и транспорт normal_smtp для всех остальных.

12.12.2 Exim на периодически подключающемся хосте

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

queue_domains = ! +local_domains

чтобы все удаленные домены были поставлены в очередь. Если в локальной сети есть несколько хостов, которые обмениваются почтой между собой, можно задать queue_domains, чтобы исключить их домены из очереди:

queue_domains = ! *.local.hosts : ! +local_domains

При подключении к Интернету, если запуск очереди начинается с выполнения:

exim -qf

каждое сообщение, скорее всего, будет отправлено в отдельном сеансе SMTP, поскольку ранее маршрутизация не выполнялась. (Лучше всего использовать -qf вместо просто -q на случай, если ранее были неудачные попытки доставки, потому что -qf переопределяет время повторных попыток.) Сообщения от периодически подключаемых хостов часто отправляются на один смарт-хост, и это более эффективно, если все они могут быть отправлены в одном SMTP-соединении. Это можно организовать, запустив вместо этого очередь с помощью:

exim -qqf

В этом случае очередь сканируется дважды. При первом проходе выполняется маршрутизация, но доставки не происходит. Это как если бы каждая удаленная доставка терпела временный сбой, и Exim обновляет свой файл подсказок, который содержит список того, какие сообщения ожидают какие хосты. Второй проход — это обычный запуск очереди; поскольку все сообщения были маршрутизированы ранее, сообщения, предназначенные для одного и того же хоста, скорее всего, будут отправлены в виде нескольких доставок в одном SMTP-соединении.

Еще один способ заранее организовать удаленную маршрутизацию — использовать queue_smtp_domains вместо queue_domains. Вы можете сделать это только в том случае, если есть возможность маршрутизировать удаленные адреса, когда клиент не подключен к Интернету. Например, если вы хотите отправить все на один смарт-хост, чей IP-адрес вам известен, вы можете использовать такой роутер, как:

remotes:
  driver = manualroute
  route_list = * 192.168.4.5

где IP-адрес смарт-хоста указан явно. Если вы не поместите смарт-хост в свой файл /etc/hosts, указание его имени вместо IP-адреса вызовет поиск DNS, который не будет работать, когда клиент находится в автономном режиме. Отличие от queue_domains в том, что Exim выполняет маршрутизацию, когда приходит сообщение, поэтому запуск очереди может быть выполнен с -qf вместо -qff, что быстрее (хотя и незначительно, если есть только несколько сообщений).

Демон на периодически подключаемом узле

Если вы запускаете демон Exim на периодически подключаемом хосте, его не следует настраивать для запуска каких-либо процессов обработчика очередей. То есть его надо запускать только с опцией -bd. Вы можете сделать это, если у вас есть пользовательские агенты, которые используют SMTP для передачи сообщений на MTA через интерфейс loopback, или если у вас есть входящая почта с других хостов в локальной сети.

Входящая почта на периодически подключаемый хост

Если входящая почта из Интернета принимается с использованием SMTP, сервер, скорее всего, отправит много сообщений по одному соединению. По умолчанию Exim приостанавливает автоматическую доставку сообщений после того, как определенное количество сообщений было получено за одно соединение. Это предотвращает запуск слишком большого количества локальных процессов доставки с одного удаленного хоста. Это не так актуально для хоста, который получает почту только из одного источника, поэтому значение smtp_accept_queue_per_connection должно быть увеличено или даже установлено на ноль (то есть отключено), чтобы все входящие сообщения по одному соединению доставлялись немедленно.

Глава 13
Шифрование, аутентификация и другая обработка SMTP

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

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

13.1 Зашифрованные SMTP-соединения

RFC 3207 определяет, как можно настроить SMTP-соединения, чтобы данные, передаваемые между двумя хостами, шифровались при передаче. Как только соединение установлено, клиент выдает команду STARTTLS. Если сервер принимает это, два хоста согласовывают механизм шифрования, который будет использоваться для всех последующих передач данных[1]. Затем сеанс SMTP возвращается к исходному состоянию, и клиент снова начинает работу, отправляя новую команду EHLO по зашифрованному соединению.

Шифрование SMTP-соединений использует протокол, известный как TLS ((Transport Layer Security), который определен в RFC 2246. Это стандартизированный протокол, производный от уровня защищенных сокетов Netscape (SSL). TLS реализован в Exim с использованием библиотеки OpenSSL или GnuTLS[2]. В самом дистрибутиве Exim нет криптографического кода.

Чтобы использовать шифрование, вы должны установить OpenSSL или GnuTLS, а затем собрать версию Exim, включающую поддержку TLS. Вам также необходимо понимать основные концепции шифрования на управленческом уровне и, в частности, то, как используются открытые ключи, закрытые ключи и сертификаты, включая концепции подписания сертификатов и центров сертификации. Если вы ничего не понимаете в сертификатах и ключах, пожалуйста, попробуйте найти источник этой исходной информации, который не является специфичным для Exim или даже для обработки почты. Некоторые полезные вводные материалы можно найти в разделе часто задаваемых вопросов о добавлении SSL к веб-серверу Apache[3]. Другие части этой документации также полезны и содержат ссылки на дополнительные файлы. Книга Эрика Рескорла SSL and TLS (Addison-Wesley, 2001) содержит полезный вводный материал, а также подробные обсуждения протоколов шифрования.

Вы можете создать закрытый ключ и самоподписанный сертификат, используя команду req, поставляемую с OpenSSL, например:


openssl req -x509 -newkey rsa:1024 -keyout filel -out file2 \
                  -days 9999 -nodes

filel и file2 могут быть одним и тем же файлом; ключ и сертификат разделены и поэтому могут быть идентифицированы независимо. Параметр -days указывает период действия сертификата; здесь мы указываем долгое время. Параметр -nodes важен: если вы его не зададите, ключ будет зашифрован парольной фразой, которую вам предложат ввести, и любое использование ключа вызовет дополнительные запросы на ввод парольной фразы. Это бесполезно, если вы собираетесь использовать этот сертификат и ключ в MTA, где запрос невозможен.

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

  1. Некоторые устаревшие клиенты не поддерживают STARTTLS, а вместо этого подключаются к другому порту и ожидают немедленного согласования механизма шифрования. Поддерживать таких клиентов можно с помощью параметра командной строки -tls-on-connect, описанного в справочном руководстве.

  2. См. http://www.openssl.org/ или http://www.gnu.org/software/gnutls/gnutls.html соответственно.

  3. См. http://www.modssl.org/docs/2.7/ssl_faq.html#ToC24.

13.1.1 Настройка Exim для использования TLS в качестве сервера

Если Exim был собран с поддержкой TLS, он объявляет о доступности команды STARTTLS для клиентских хостов, которые соответствуют tls_advertise_hosts, но не для каких-либо других. Значение по умолчанию для этой опции не установлено, что означает, что STARTTLS вообще не рекламируется. Это значение по умолчанию выбрано потому, что оно целесообразно для хостов, которые хотят использовать TLS только в качестве клиента, а также потому, что TLS не может работать как сервер без дополнительной информации.

Для поддержки TLS на сервере необходимо настроить tls_advertise_hosts для соответствия некоторым хостам, а также указать файлы, содержащие один или несколько сертификатов и закрытый ключ. Например:

tls_advertise_hosts = *
tls_certificate = /etc/secure/exim/certs
tls_privatekey = /etc/secure/exim/privkey

Первый файл содержит сертификат X509 сервера и любые промежуточные сертификаты, необходимые для его проверки. Второй файл содержит закрытый ключ сервера. Эти файлы должны быть доступны для чтения пользователем Exim. Сертификаты и ключ можно хранить в одном файле; если tls_privatekey не установлен, предполагается, что это так.

Только с этими настройками Exim будет работать как шифрующий сервер с такими клиентами, как Netscape. Для этого не требуется, чтобы клиент имел сертификат (но о том, как настоять на этом, см. в следующем разделе). Есть еще один вариант, который может понадобиться в других ситуациях. Если для tls_dhparam задано имя файла, библиотека SSL инициализируется для использования шифров Диффи-Хеллмана с параметрами, содержащимися в файле. Это увеличивает набор шифров, поддерживаемых сервером[4].

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

  1. См. команду openssl dhparam для способа генерации этих данных.

13.1.2 Запрос клиентских сертификатов

Если вы хотите, чтобы сервер Exim запрашивал сертификат клиента при согласовании сеанса TLS, вы должны установить либо tls_verify_hosts, либо tls_try_verify_hosts для соответствия соответствующим клиентам. По умолчанию эти списки хостов не заданы; вы, конечно, можете установить любой из них в *, чтобы он применялся ко всем соединениям TLS. Сертификаты клиента проверяются путем их сравнения со списком ожидаемых сертификатов, указанным в tls_verify_certificates, который содержит имя файла или каталога.

Один файл может содержать несколько сертификатов, соединенных встык. Если используется каталог, каждый сертификат должен находиться в отдельном файле с именем (или символической ссылкой) вида <<hash>>.0, где <<hash>> — хеш-значение, построенное из сертификата. Вы можете вычислить соответствующий хэш, выполнив команду:

openssl x509 -hash -noout -in /cert/file

где /cert/file содержит только один сертификат.

Разница между tls_verify_hosts и tls_try_verify_hosts заключается в том, что происходит, когда клиент не предоставляет сертификат или если сертификат не соответствует ни одному из сертификатов в коллекции с именем tls_verify_certificates.

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

13.1.3 Переменные, устанавливаемые для соединения TLS

Переменной $tls_cipher присваивается имя шифра, согласованного для входящего соединения TLS. Он включается в строку заголовка Received: входящего сообщения (по умолчанию; вы, конечно, можете это изменить). Он также включен в строку журнала, которая записывает прибытие сообщения, обозначенного X=. Если вы этого не хотите, вы можете отключить селектор журнала tls_cipher (21.2.2).

Если SMTP-соединение не зашифровано, значением $tls_cipher является пустая строка. Это обеспечивает способ проверки шифрования в расширениях строк.

Когда Exim получает сертификат от клиента (независимо от того, проверен он или нет), значение отличительного имени становится доступным в переменной $tls_peerdn. Поскольку это часто длинная текстовая строка, по умолчанию она не включается в строку журнала или строку заголовка Received:. Вы можете организовать его ведение журнала (с ключом DN=), установив селектор журнала tls_peerdn, и вы можете использовать received_header_text для изменения содержимого строки заголовка Received:.

13.1.4 Настройка Exim для использования TLS в качестве клиента

Селекторы журнала для данных TLS применяются к исходящим SMTP-доставкам, а также к входящим сообщениям, при этом log_peerdn приводит к записи в журнал отличительного имени сертификата сервера. Остальная конфигурация клиента для TLS находится в транспорте smtp.

Нет необходимости устанавливать какие-либо параметры, чтобы TLS работал в транспорте smtp. Если TLS объявляется сервером, транспорт smtp автоматически попытается запустить сеанс TLS. Однако этого можно избежать, установив hosts_avoid_t1s (опция транспорта) в список хостов серверов, для которых не следует использовать TLS.

Если вы хотите настоять на том, чтобы для доставки на определенные серверы использовалось шифрование, вы можете настроить hosts_require_tls, чтобы они соответствовали им. Exim не будет доставлять сообщения в открытом виде ни на какие хосты, соответствующие этой опции. Если возникает какая-либо ошибка при настройке TLS для хоста, который соответствует hosts_require_tls, попытка доставки на этот хост не предпринимается. Если есть альтернативные хосты, они пробуются; в противном случае доставка откладывается.

Если хост сервера не соответствует hosts_require_tls, Exim может попытаться доставить незашифрованное сообщение. Это всегда происходит, если ответом на STARTTLS является код 5<xx>. Для временного кода ошибки или сбоя согласования сеанса TLS после кода успешного ответа все, что происходит, контролируется параметром tls_tempfail_tryclear транспорта smtp. Если оно ложно, доставка на этот хост откладывается, и пробуются другие хосты (если они доступны). Если это true, Exim попытается доставить в чистом виде.

Вы можете предоставить клиенту сертификат и закрытый ключ, установив tls_certificate и (необязательно) tls_privatekey в качестве параметров транспорта smtp. Сертификат передается серверу только в том случае, если он его запрашивает. (Если сервером является Exim, он запросит сертификат, если tls_verify_hosts или tls_try_verify_hosts соответствует клиенту.)

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

Все параметры клиента раскрываются перед использованием, а $host и $host_address содержат имя и IP-адрес сервера, к которому подключен клиент. Принудительный сбой расширения приводит к тому, что Exim ведет себя так, как будто соответствующая опция не установлена.

13.2 SMTP-аутентификация

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

Exim использует ACL, чтобы контролировать, какие входящие сообщения он принимает, как для ретрансляции, так и для локальной доставки. Полная информация описана в следующей главе, но здесь мы приведем пример, чтобы показать необходимость аутентификации SMTP.

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

accept hosts = 192.168.5.224/27

в ACL, который запускается для каждой команды SMTP RCPT. Это принимает любой адрес получателя, если хост-отправитель соответствует данной сети. (Если это не так, применяются последующие операторы ACL, чтобы решить, принимать адрес или нет.)

Однако проверка путем отправки адреса хоста не работает в следующих случаях:

Аутентификация SMTP (RFC 2554) была изобретена как один из способов решения этих проблем[5]. Она работает следующим образом:

После того, как клиент прошел аутентификацию, сервер может разрешить ему делать то, что не разрешено делать клиентам, не прошедшим аутентификацию. Что это такое, полностью зависит от управления сервером. Exim использует ACL для реализации такого контроля.

  1. Другая возможность — использовать шифрование с проверенными сертификатами.

13.2.1 Механизмы аутентификации

Было опубликовано несколько различных механизмов аутентификации. Exim поддерживает три из них: PLAIN, LOGIN и CRAM-MD5[6]. Эти механизмы используются различными популярными пользовательскими агентами, которые отправляют почту на сервер с помощью SMTP. Однако, поскольку не все заинтересованы в SMTP-аутентификации, код для этих механизмов не включен в бинарный файл Exim, если конфигурация времени сборки явно не запрашивает его.

Прежде чем описывать, как Exim сконфигурирован для поддержки аутентификации SMTP, нам нужно объяснить, как работают три общих механизма аутентификации.

  1. Exim также поддерживает метод Secure Password Authentication (SPA, также известный как NTLM или NTCR), но он не рассматривается в этой книге. Подробности в справочнике. Этот механизм используется некоторыми серверами Microsoft.

13.2.2 Аутентификация PLAIN

Аутентификация PLAIN описана в RFC 2595. Она требует отправки трех связанных строк данных, разделенных двоичными нулевыми символами. Они отправляются либо с самой командой AUTH, либо в качестве ответа на пустую строку запроса. Вторая и третья строки данных представляют собой пару user/password, которую сервер может проверить. Первая строка не имеет отношения к аутентификации SMTP и обычно пуста[7]. Вот пример обмена аутентификацией, где объединенная строка данных отправляется как часть команды AUTH, а строки, отправленные клиентом и сервером, обозначаются как C и S соответственно:

C: AUTH PLAIN AHBoMTAAc2VjcmV0
S: 235 Authentication successful

Строка base-64 AHBoMTAAc2VjcmV0 представляет собой кодировку:

<nul>ph10<nul>secret

где <nul> представляет двоичный нулевой байт. Первая строка данных пуста, имя пользователя — ph10, а пароль — secret.

Аутентификация PLAIN эффективна тем, что требует только одной команды и ответа. Пароль должен храниться в открытом виде на клиентском хосте, но может храниться в зашифрованном виде на сервере точно так же, как и для паролей для входа. Однако, если не используется зашифрованное SMTP-соединение, данные передаются по сети в незашифрованном виде и могут быть перехвачены. По этой причине проверка подлинности PLAIN часто доступна только в том случае, если соединение SMTP зашифровано с использованием TLS, как описано ранее в этой главе.

  1. Этот механизм предназначен для использования в протоколах, отличных от SMTP, где для последующих операций используется конкретный идентификатор пользователя (например, для запуска сеанса входа в систему); первая строка может указывать другого пользователя, отличного от того, чей пароль был проверен.

13.2.3 Аутентификация LOGIN

Аутентификация LOGIN не описана ни в одном RFC, но она используется пользовательским агентом Pine. Как и PLAIN-аутентификация, она основана на комбинации пользователя и пароля, но каждый из них запрашивается отдельно, поэтому обмен аутентификацией может выглядеть следующим образом:

C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: cGgxMA==
S: 334 UGFzc3dvcmQ6
C: c2VjcmVO
S: 235 Authentication successful

В незакодированном виде это:

C: AUTH LOGIN
S: 334 Username:
C: ph10
S: 334 Password:
C: secret
S: 235 Authentication successful

Аутентификация LOGIN менее эффективна, чем PLAIN, потому что требуется три взаимодействия. Как и при обычной аутентификации, имя пользователя и пароль передаются в открытом виде. Некоторые люди утверждают, что это «безопаснее», потому что имя пользователя и пароль не передаются в одном пакете, хотя это не кажется очень сильным аргументом.

13.2.4 Аутентификация CRAM-MD5

Аутентификация CRAM-MD5 (RFC 2195) позволяет избежать передачи незашифрованных паролей по сети. Сервер отправляет одну строку запроса, а клиент отправляет обратно имя пользователя, за которым следует пробел и дайджест MD5[8] строки вызова, объединенной с паролем. Сервер вычисляет дайджест MD5 той же строки и сравнивает его с тем, что он получил. Например:

C: AUTH CRAM-MD5
S: 334 PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaSSuZxQ+
C: dGltIGRKOTJiNGJiMzZRhZmFhNzBmMjkwNWVKZDMxOTZhHNTU3
S: 235 Authentication successful

В незакодированном виде это:

C: AUTH CRAM-MD5
S: 334 <1896.697170952@postoffice.reston.example>
C: tim dd92b4bb34afaa70£2905edd3196a557
S: 235 Authentication successful

Строка dd92b4bb34afaa70£2905edd3196a557 представляет собой дайджест MD5:

<1896.697170952@postoffice.reston.example>secret

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

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

  1. Дайджест MD5 представляет собой 16-байтовый криптографический хэш, вычисленный из произвольной текстовой строки таким образом, чтобы свести к минимуму вероятность того, что две строки будут иметь один и тот же дайджест. См. RFC 1321.

13.2.5 Информирование об аутентификации

Если Exim сконфигурирован как сервер аутентификации, он обычно объявляет механизмы, которые он поддерживает, в ответ на команду EHLO. Однако бывают ситуации, когда этого не всегда хочется.

Рассмотрим конфигурацию, в которой некоторым хостам разрешено выполнять ретрансляцию без аутентификации (например, потому что они находятся в локальной сети), тогда как другим требуется аутентификация. Что происходит, когда подключается клиентский хост, которому не требуется аутентификация? Некоторое клиентское программное обеспечение, увидев поддержку аутентификации, настаивает на попытке аутентификации, и нет возможности настроить его иначе. Это может привести к тому, что он будет запрашивать у пользователя данные аутентификации без необходимости.

Чтобы избежать этой проблемы, вы можете установить параметр auth_advertise_hosts, чтобы определить хосты, на которые следует отправлять оповещение. Например, чтобы исключить хосты в локальной сети, вы можете использовать такой параметр:

auth_advertise_hosts = ! 192.168.33.0/24

Как и для всех списков хостов, содержимое auth_advertise_hosts расширяется перед каждым использованием, и список проверяется заново для каждой команды EHLO. Это позволяет использовать разные значения для зашифрованных и незашифрованных сеансов. Рассмотрим эту настройку:

auth_advertise_hosts = ${if eq{$tls_cipher}{}{}{*}}

Расширение проверяет содержимое $tls_cipher, сравнивая его с пустой строкой (вторая пара фигурных скобок после eq). Если $tls_cipher пуст, что подразумевает незашифрованный сеанс, значение расширения пусто, поэтому оно не соответствует хостам. Однако в сеансе TLS $tls_cipher не является пустым, поэтому значением расширения является *, что соответствует всем хостам. Таким образом, эффект этого параметра заключается в том, что аутентификация не объявляется, когда клиент отправляет первую команду EHLO, но если сеанс TLS согласован, аутентификация объявляется в ответ на вторую команду EHLO, которую отправляет клиент.

13.2.6 Выбор механизма аутентификации

Если вы настраиваете клиент Exim для использования удаленного сервера и не знаете, какие механизмы аутентификации он поддерживает, вы можете использовать Telnet, чтобы узнать:

$ telnet some.server.example 25
220 some.server.example ESMTP Exim 4.05 Mon, 13 May 2002 10:24:18 +0100
EHLO client.domain.example
250-some.server.example Hello client.domain.example [192.168.8.20]
250-SIZE 20971520
250-PIPELINING
250-AUTH PLAIN CRAM-MD5
250 HELP
quit

Если вы настраиваете сервер Exim, у вас часто нет особого выбора, какой механизм аутентификации использовать; на практике вы застряли с тем, что поддерживает программное обеспечение ваших клиентов. Однако стоит задуматься о проблемах.

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

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

Использование альтернативного пароля, установленного с аутентификацией CRAM-MD5, означает, что вам не нужно хранить на сервере обычные пароли (только пароли SMTP); это, вероятно, самый безопасный из поддерживаемых в настоящее время механизмов, когда шифрование не используется. При использовании зашифрованных соединений лучше использовать PLAIN или LOGIN, поскольку они не требуют хранения паролей на сервере в открытом виде.

13.2.7 Аутентификаторы Exim

В разделе файла конфигурации среды выполнения, который начинается с «begin authenticators», вы указываете параметры для аутентификации SMTP. Он содержит определения для нескольких аутентификаторов, каждый из которых определяет механизм аутентификации.

Аутентификаторы определяются с использованием того же синтаксиса, что и определения роутеров и транспортов. Когда Exim получает почту SMTP, он действует как сервер; когда он отправляет сообщения по SMTP, он действует как клиент. Варианты конфигурации предусмотрены для использования в обоих этих случаях, и одна версия Exim может действовать и как клиент, и как сервер в разное время. Каждый аутентификатор может иметь как серверные, так и клиентские функции.

Чтобы было ясно, какие параметры к какой функции относятся, префиксы server_ и client_ используются в именах параметров, относящихся либо к серверной, либо к клиентской функции соответственно. Функции сервера и клиента отключены, если ни одна из их опций не указана. Если аутентификатор должен использоваться как для серверных, так и для клиентских функций, требуется одно определение с использованием обоих наборов параметров. Например:

cram:
  driver = cram_md5
  public_name = CRAM-MD5
  server_secret = ${if eq{$1}{ph10}{secret}fail}
  client_name = ph10
  client_secret = secret2

Опция server_ используется, когда Exim действует как сервер, а опции client_ используются, когда он действует как клиент. Объяснение этого примера следует позже, вместе с описанием аутентификатора CRAM-MD5.

13.2.8 Аутентификация на сервере Exim

Когда сообщение получено от аутентифицированного хоста, значение $received_protocol устанавливается равным asmtp вместо esmtp или smtp, а $sender_host_authenticated содержит имя аутентификатора, который успешно аутентифицировал клиента. Пусто, если не было успешной аутентификации.

Когда клиентский хост аутентифицировал себя, Exim обращает внимание на параметр AUTH во входящих командах SMTP MAIL, например:

MAIL FROM: <theboss@acme.com.example> AUTH joker@edu.example

Предполагается, что адрес, указанный в параметре AUTH, идентифицирует аутентифицированного исходного отправителя сообщения, но эта функция, похоже, не получила широкого распространения. Специальное значение <> означает «нет доступных аутентифицированных отправителей». Если клиентский хост не аутентифицирован, Exim принимает синтаксис параметра AUTH, но игнорирует данные.

Если принято, значение доступно во время доставки в переменной $authenticated_sender и передается другим хостам, на которых Exim аутентифицируется как клиент. Не путайте это значение со строкой $authenticated_id, полученной в процессе аутентификации (см. далее в этой главе) и обычно не являющейся полным адресом электронной почты.

13.2.9 Тестирование аутентификации сервера

Опция Exim -bh может быть полезна для тестирования конфигураций аутентификации сервера (20.9.3). Данные для команды AUTH должны быть отправлены в кодировке base 64. Если на вашем хосте установлена команда mimencode, быстрый способ получения таких данных (например):

echo -e -n '\Oname\Opassword' | mimencode

Команда echo -e -n работает в большинстве оболочек (то есть она интерпретирует обратную косую черту в тексте и выводит результат без завершающего символа новой строки). Однако в некоторых оболочках (например, в оболочке Solaris Bourne) параметры не распознаются. В этих случаях обратная косая черта часто интерпретируется автоматически, но вы должны добавить \c, чтобы избежать завершающего символа новой строки:

echo '\Oname\0password\c' | mimencode

При отсутствии команды mimencode можно использовать следующий Perl-скрипт:

use MIME::Base64;
printf ("%s", encode_base64(eval "\"SARGV[O]\""));

Это интерпретирует его аргумент как строку Perl, а затем кодирует его. Интерпретация строки Perl позволяет включать в данные двоичные нули, необходимые для некоторых видов аутентификации. Например, командная строка для запуска этого скрипта с использованием имени encode может быть такой:

encode '\Ouser\Opassword'

13.2.10 Аутентификация клиентом Exim

Транспорт smtp имеет две опции: hosts_require_auth и hosts_try_auth. Когда транспорт smtp соединяется с сервером, который объявляет о поддержке аутентификации, а также соответствует одной из этих опций, Exim (как клиент) пытается аутентифицироваться следующим образом:

Ошибка аутентификации, возникающая при отсрочке доставки, может быть обнаружена в правиле повторных попыток (12.5.2). Это означает, что вы можете контролировать количество повторных попыток или (указав отсутствие повторных попыток) превратить временную ошибку в постоянную.

Когда Exim аутентифицирует себя на удаленном сервере, он добавляет параметр AUTH к командам MAIL, которые он отправляет, если у него есть аутентифицированный отправитель для сообщения. Если локальный процесс вызывает Exim для отправки сообщения, адрес отправителя, созданный на основе логина и qualify_domain, считается аутентифицированным.

13.2.11 Параметры, общие для всех аутентификаторов

Настроенные аутентификаторы имеют имена, как роутеры и транспорты. Следующие параметры являются общими для всех аутентификаторов:

driver (string, default = unset)

Этот параметр всегда должен быть установлен. Он указывает, какой из доступных аутентификаторов ((plaintext или cram_md5) следует использовать.

public_name (string, default = unset)

Этот параметр указывает имя механизма проверки подлинности, который реализует драйвер и по которому он известен внешнему миру. Эти имена должны содержать только буквы верхнего регистра, цифры, знаки подчеркивания и дефисы (RFC 2222), но Exim на самом деле сопоставляет их без учета регистра. Если public_name не задано, по умолчанию используется имя экземпляра драйвера.

Публичные имена аутентификаторов, сконфигурированных как серверы, объявляются Exim'ом, когда он получает команду EHLO, в том порядке, в котором они определены. При получении команды AUTH список аутентификаторов просматривается в порядке определения для того, чье общедоступное имя соответствует механизму, указанному в команде AUTH.

server_set_id (string, default = unset)

Когда сервер Exim успешно аутентифицирует клиента, эта строка расширяется с использованием данных аутентификации и сохраняется для любых входящих сообщений в переменной $authenticated_id. Она также включается в строки журнала для входящих сообщений. Например, конфигурация средства проверки подлинности пользователя/пароля может сохранить имя пользователя, которое использовалось для проверки подлинности, и ссылаться на него впоследствии во время доставки сообщения.

13.2.12 Использование аутентификатора plaintext на сервере

При работе в качестве сервера аутентификатор plaintext собирает одну или несколько строк данных от клиента и помещает их в $1, $2 и т. д. Количество требуемых строк данных контролируется параметром server_prompts, который содержит список строк подсказок, разделенных двоеточиями. Однако подсказки не обязательно отправляются как вызовы, поскольку сначала используются любые строки, отправленные с помощью команды AUTH.

Данные, предоставленные в командной строке AUTH, обрабатываются как список строк, разделенных NUL. Если строк в server_prompts больше, чем количество строк, предоставленных командой AUTH, оставшиеся подсказки используются для получения дополнительных данных. Каждый ответ от клиента может быть списком строк, разделенных NUL. Этот общий подход позволяет настроить plaintext для поддержки аутентификации PLAIN или LOGIN.

После получения достаточного количества строк данных содержимое server_condition расширяется. Сбой расширения (принудительный или иной) приводит к возврату временного кода ошибки. Если результатом успешного раскрытия является пустая строка, 0, no или false, аутентификация завершается ошибкой. Если результатом расширения является 1, yes или true, аутентификация проходит успешно, а содержимое опции server_set_id расширяется и сохраняется в $authenticated_id. Для любого другого результата возвращается временный код ошибки с развернутой строкой в качестве текста ошибки.

Для механизма аутентификации PLAIN требуются три строки данных. Они отправляются либо с командой AUTH, либо в отдельном ответе после пустого вызова[9]. Вторая и третья строки данных обрабатываются как пара пользователь/пароль. Используя в качестве примера одного фиксированного пользователя и пароль, аутентификатор PLAIN можно настроить следующим образом:

fixed_plain:
  driver = plaintext
  public_name = PLAIN
  server_prompts = :
  server_condition = ${if and {{eq{$2}{ph10}}{eq{$3}{secret}}}{yes}{no}}
  server_set_id = $2

Настройка server_prompts указывает только одну (пустую) строку подсказки, потому что пустые строки в конце списков всегда игнорируются в Exim. Этот аутентификатор будет объявлен в ответе на EHLO как:

250-AUTH PLAIN

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

AUTH PLAIN AHBoMTAAc2VjcmvV0

Строка аргумента закодирована в base 64, как того требует RFC. Этот пример при декодировании является <nul>ph10<nul>secret, где <nul> представляет нулевой байт. Он разбит на три строки, первая из которых пуста. Поскольку имеется только одна строка подсказки, дальнейшие подсказки не отправляются. Если клиент не отправляет данные с командой, сервер запрашивает ее пустым вызовом, как в этом примере (где клиентские команды и ответы сервера обозначены буквами C и S):

C: AUTH PLAIN
S: 334
C: AHBOMTAAc2Vjcmv0

После получения данных содержимое server_condition расширяется. В этом примере проверяется, что вторая и третья строки данных являются ph10 и secret соответственно.

Для механизма аутентификации LOGIN никакие данные не отправляются с командой AUTH. Вместо этого имя пользователя и пароль предоставляются отдельно в ответ на запросы. Аутентификатор открытого текста может быть настроен для поддержки этого следующим образом:

fixed_login:
  driver = plaintext
  public_name = LOGIN
  server_prompts = Username:: : Password::
  server_condition = \
    ${if and {{eq{$1}{ph10}}{eq{$2}{secret}}}{yes}{no}}
  server_set_id = $1

Этот аутентификатор фактически принимает данные как часть команды AUTH, но если клиент не предоставляет их (как в случае с клиентами LOGIN), для получения двух элементов данных используются строки приглашения.

  1. Существует распространенное заблуждение, что данные должны быть отправлены с помощью команды AUTH. Это возникает из-за того, что RFC, определяющий PLAIN-аутентификацию (RFC 2595), написан с использованием терминологии SASL (Simple Authentication and Security Layer) и говорит об «одном сообщении». В контексте SMTP это неверно интерпретируется как «одна SMTP-команда».

13.2.13 Проверка паролей в аутентификаторах plaintext

Только что показанные примеры plaintext нереалистичны, потому что они допускают только одну комбинацию пользователя/пароля. На практике обычно требуется иметь возможность аутентифицировать несколько пользователей, каждый со своим паролем. Чтобы помочь в этом, предусмотрено несколько функций строк расширения. Их можно комбинировать со средствами поиска, чтобы обеспечить множество различных способов аутентификации пользователей.

Например, если зашифрованные пароли доступны в /etc/passwd, вы можете использовать следующую настройку в аутентификаторе plaintext:

server_condition = ${if crypteq{$3}\
  {${extract{1}{:}{$lookup{$2}lsearch{/etc/passwd}{$value}}}}\
  }{yes}{no}}

Строка расширения использует поиск для получения пользовательских данных из файла, извлекает первое поле данных (зашифрованный пароль) и сравнивает его с паролем проверки подлинности с использованием условия расширения crypteq. Это условие шифрует пароль субъекта и сравнивает результат с тем, что было получено из файла.

Однако многие операционные системы больше не хранят зашифрованные пароли непосредственно в /etc/passwd, как следует из этого примера. Вместо этого пароли хранятся в «теневых» (shadow) файлах, недоступных для чтения обычными пользователями. Процессы, обрабатывающие входящие SMTP-соединения, выполняются под пользователем Exim, поэтому любые файлы, на которые ссылается расширение server_condition, должны быть доступны этому пользователю. Если файл теневого пароля не может быть прочитан пользователем Exim, эта техника не может быть использована.

Один из способов обойти это — использовать другой набор паролей для аутентификации и хранить их в файле, который Exim может прочитать. В любом случае это более безопасно, чем совместное использование паролей для входа, потому что раскрытие одного не ставит под угрозу другой. Exim также поддерживает Pluggable Authentication Modules (PAM), аутентификацию с использованием LDAP, демона аутентификации Cyrus pwcheck и RADIUS (17.7.5).

13.2.14 Использование аутентификатора plaintext в клиенте

Аутентификатор plaintext имеет только одну клиентскую опцию, называемую client_send. Ее значение представляет собой список строк аутентификационных данных, разделенных двоеточиями. Каждая строка независимо расширяется перед отправкой на сервер. Первая строка отправляется с командой AUTH; последующие строки отправляются в ответ на запросы сервера.

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

Это пример конфигурации клиента, в которой реализован механизм аутентификации PLAIN с фиксированным именем и паролем:

fixed_plain:
  driver = plaintext
  public_name = PLAIN
  client_send = ^ph10^secret

Отсутствие двоеточий в client_send означает, что весь текст отправляется с помощью команды AUTH, а символы циркумфлекса преобразуются в нулевые байты. Аналогичный пример, использующий механизм LOGIN:

fixed_login:
  driver = plaintext
  public_name = LOGIN
  client_send = : ph10 : secret

Начальное двоеточие гарантирует, что никакие данные не будут отправлены с самой командой AUTH. Остальные строки отправляются в ответ на подсказки.

13.2.15 Использование аутентификатора cram_md5 на сервере

Аутентификатор cram_mdS5 имеет одну серверную опцию, которая называется server_secret. Когда сервер получает ответ клиента на вызов CRAM-MD5, «username» помещается в переменную расширения $1, а server_secret расширяется, чтобы получить пароль для этого пользователя. Затем сервер вычисляет дайджест CRAM-MD5, который должен был отправить клиент, и проверяет, получил ли он правильную строку. Если раскрытие server_secret принудительно завершится ошибкой, аутентификация завершится неудачно. Если расширение не удается по какой-либо другой причине, клиенту возвращается временный код ошибки.

Например, следующий аутентификатор проверяет, что имя пользователя, данное клиентом, — это ph10, и если это так, использует secret в качестве пароля. Для любого другого имени пользователя аутентификация завершается ошибкой:

fixed_cram:
  driver = cram_md5
  public_name = CRAM-MD5
  server_secret = ${if eq{$1}{ph10}{secret}fail}
  server_set_id = $1

Если аутентификация прошла успешно, настройка server_set_id сохраняет имя пользователя в $authenticated_id. Более сложная версия может искать секретную строку в файле или базе данных, используя имя пользователя в качестве ключа.

13.2.16 Использование аутентификатора cram_md5 в клиенте

При использовании в качестве клиента аутентификатор cram_md5 имеет две опции: client_name и client_secret, обе из которых должны быть установлены. Они раскрываются и используются как имя пользователя и секретные строки соответственно при вычислении ответа на запрос сервера.

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

Простой пример конфигурации аутентификатора клиента cram_md5 с использованием фиксированных строк выглядит следующим образом:

fixed_cram:
  driver = cram_md5
  public_name = CRAM-MD5
  client_name = ph10
  client_secret = secret

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

13.3 SMTP через TCP/IP

SMTP через TCP/IP — единственный способ передачи сообщений между хостами, который поддерживает Exim. Исходящий SMTP через TCP/IP описан ранее (9.1). Следующие несколько разделов охватывают некоторые детали обработки, которые происходят для входящего SMTP через TCP/IP. После этого мы обсудим некоторые другие варианты использования SMTP, в которых удаленный хост не задействован.

13.3.1 Входящие SMTP-сообщения через TCP/IP

Входящие SMTP-сообщения через TCP/IP могут быть приняты одним из двух способов: запуском прослушивающего демона или использованием inetd. В последнем случае запись в /etc/inetd.conf должна быть такой:

smtp stream tcp nowait exim /usr/exim/bin/exim in.exim -bs

Exim различает этот случай и случай, когда локальный пользовательский агент использует опцию -bs, проверяя, является ли стандартный ввод сокетом.

По умолчанию Exim делает минимальное протоколирование входящих SMTP-соединений. Например, он не записывает запись в журнал, когда удаленный хост подключается или отключается (через демон или inetd), если только отключение не является неожиданным. Доступен ряд селекторов журналов для увеличения объема регистрируемой информации (21.2.2).

Объем доступного дискового пространства проверяется всякий раз, когда в команде MAIL принимается SIZE, независимо от того, настроено ли message_size_limit или check_spool_space, если для smtp_check_spool_space не задано значение false. Выдается временная ошибка, если не хватает места. Проверка выполняется на сумму, указанную в check_spool_space, плюс значение, заданное с помощью SIZE, то есть проверяется, что добавление входящего сообщения не уменьшит пространство ниже порогового значения.

Когда сообщение успешно получено, Exim включает локальный ID сообщения в свой ответ на последнюю точку, которая завершает данные, например:

250 OK id=13M6GM-0005kt-00

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

Exim может быть настроен на перезапись адресов по мере их получения в SMTP-командах до того, как будет выполнена какая-либо проверка синтаксиса (15.6.3). Его также можно настроить для проверки адресов во входящих SMTP-командах (14.9).

13.3.2 Команды VRFY и EXPN

RFC 2821 определяет две SMTP-команды, которые изначально предназначались для помощи в отладке проблем с доставкой: VRFY проверяет адрес электронной почты, а EXPN выводит расширение псевдонима или списка рассылки. В прежние времена, когда Интернет был более дружелюбным местом, где сообщения часто доставлялись непосредственно на узлы назначения, почтовые администраторы регулярно использовали эти команды. В настоящее время, когда гораздо больше почты доставляется опосредованно через почтовые концентраторы и шлюзы, их потенциальная полезность снизилась, и, кроме того, многие администраторы рассматривают их как угрозу безопасности.

Exim по умолчанию не поддерживает ни одну из этих команд. Он отвечает на команду VRFY следующим образом:

252 VRFY not available

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

Если вы хотите сделать VRFY или EXPN доступными на вашем сервере, вы должны настроить соответствующие списки управления доступом, которые принимают эти команды при соответствующих условиях. Например, вы можете разрешить их с хостов в вашей локальной сети, но не иначе, или только для подключений к loopback интерфейсу.

Когда VRFY принимается, запускается точно такой же код, как при вызове Exim с опцией -bv. И наоборот, EXPN рассматривается как проверка адреса (аналогично параметру -bt), а не проверка (параметр -bv). Выполняется одноуровневое расширение адреса. Если в качестве аргумента EXPN указана неквалифицированная локальная часть, она уточняется с qualify_domain.

13.3.3 Команда ETRN

RFC 821 (первоначальный RFC для SMTP) описывает команду TURN, которая меняет роли клиента и сервера. Идея заключалась в том, что клиент мог подключиться, отправить свою исходящую почту, а затем использовать TURN, чтобы стать сервером для получения входящих сообщений. Однако без аутентификации это имеет серьезные проблемы с безопасностью и не рекомендуется в RFC 2821.

RFC 1985 описывает расширение SMTP под названием ETRN, предназначенное для преодоления проблем безопасности исходной команды TURN, при этом позволяя клиенту подключаться и запрашивать доставку ожидающей почты. Это нашло некоторую поддержку в сообществах, где клиенты подключаются к серверам с помощью коммутируемого доступа.

Команда ETRN предназначена для «выпуска» сообщений, ожидающих доставки на определенные хосты. Они не отправляются по тому же соединению, которое выдало команду ETRN, а маршрутизируются обычным способом через новые соединения TCP/IP, что позволяет избежать проблем с безопасностью TURN. Exim содержит поддержку ETRN, но это не вписывается естественным образом в то, как разработан Exim. Поскольку Exim не организует свою очередь сообщений по узлам, найти «все сообщения, ожидающие этого узла» непросто. Если вы используете сервер, который является системой хранения для коммутируемых систем, и количество почты, которое необходимо хранить, превышает тривиальное, вам следует подумать о доставке ожидающей почты в локальные файлы, используя разные каталоги для каждого хоста, скажем, как обсуждалось ранее (12.12). ETRN все еще можно использовать для запуска программы доставки, которая считывает сообщения из этих файлов.

Команду ETRN можно использовать в нескольких форматах, в которых ее аргумент определяется как хост или доменное имя. Единственная форма, полностью поддерживаемая Exim, это та, в которой текст начинается с префикса #, и в этом случае интерпретация остального текста не определена и специфична для SMTP-сервера. Действительная команда ETRN вызывает запуск Exim с опцией -R, с остатком текста ETRN в качестве аргумента. Например:

ETRN #brigadoon

запускает команду:

exim -R brigadoon

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

Exim использует ACL для управления использованием ETRN. По умолчанию эта команда отклоняется, а попытка регистрируется в основном журнале и журнале отклонения. Если вы хотите использовать ETRN, вам нужно настроить ACL, который распознает хосты, которым разрешено его использовать. Когда ETRN принимается, это регистрируется в главном журнале.

Если установлен smtp_etrn_serialize (по умолчанию), он предотвращает одновременное выполнение более чем одного запуска очереди для одной и той же строки аргумента в результате команды ETRN. Это не позволяет некорректно ведущему себя клиенту запускать более одного обработчика очередей одновременно. Exim реализует сериализацию, записывая запись подсказок всякий раз, когда процесс запускается ETRN, и удаляя ее, когда процесс завершается.

Очевидно, что записи с подсказками могут оставаться без дела в случае сбоя системы или программы. Чтобы защититься от этого, Exim игнорирует любые записи старше шести часов, но обычно вы должны организовать удаление любых файлов в каталоге spool/db, имена которых начинаются с misc после перезагрузки системы.

Для большего контроля над тем, что делает ETRN, можно использовать опцию smtp_etrn_command. Она определяет команду, которая запускается всякий раз, когда принимается ETRN, независимо от формы ее аргумента. Например:

smtp_etrn_command = /etc/etrn_command $domain $sender_host_address

Строка разбивается на аргументы, которые раскрываются независимо друг от друга. Переменная $domain устанавливается в качестве аргумента команды ETRN, но проверка синтаксиса содержимого этого аргумента не выполняется.

13.4 Локальный SMTP

Некоторые пользовательские агенты используют SMTP для передачи сообщений своему локальному MTA с использованием стандартного ввода и вывода, в отличие от передачи конверта в командной строке и записи сообщения в стандартный ввод. Это поддерживается параметром командной строки -bs. Эта форма SMTP обрабатывается так же, как входящие сообщения через TCP/IP, за исключением того, что вся обработка, специфичная для хоста, игнорируется, а любой отправитель конверта, указанный в команде MAIL, игнорируется, если только вызывающий абонент не является доверенным.

И наоборот, некоторые программные приложения для управления хранилищами сообщений принимают входящие сообщения от MTA, используя разновидность SMTP, известную как LMTP (RFC 2033). Exim поддерживает это либо через транспорт lmtp для связи с локальным процессом по каналу (9.6), либо с помощью опции protocol транспорта smtp для использования LMTP через TCP/IP (9.1).

13.5 Пакетный SMTP

Пакетный SMTP — это формат для хранения сообщений или их передачи другим процессам, в котором конверты добавляются к сообщению в виде SMTP-команд (9.3.2). Он в основном используется как промежуточный формат между Exim и другими формами транспорта, такими как UUCP или частный агент доставки для коммутируемых клиентов.

Сообщения в пакетном формате SMTP могут быть переданы Exim с помощью опции командной строки -bS. Это заставляет Exim принимать одно или несколько сообщений, читая SMTP на стандартном вводе, но не генерировать SMTP-ответы. Если вызывающему абоненту доверяют, отправителям в командах MAIL доверяют; в противном случае отправитель всегда вызывает Exim. Неквалифицированные отправители и получатели не отклоняются (кажется, в этом мало смысла), а вместо этого автоматически квалифицируются. HELO и EHLO действуют как RSET; VRFY, EXPN, ETRN, HELP и DEBUG действуют как NOOP; QUIT завершает работу.

Если при чтении сообщения обнаружена какая-либо ошибка, включая пропущенную точку в конце, Exim немедленно сдается. Он записывает детали ошибки в стандартный вывод в стилизованном виде, который вызывающая программа должна иметь возможность использовать автоматически, например:

554 Unexpected end of file
Transaction started in line 10
Error detected in line 14

Он записывает более подробную версию для человеческого восприятия в стандартный файл ошибок, например:

An error was detected while processing a file of BSMTP input.
The error message was:

  501 '>' missing at end of address

The SMTP transaction started in line 10.
The error was detected in line 12.
The SMTP command at fault was:

  rept to:<malformed@in.com.plete

1 previous message was successfully processed.
The rest of the batch was abandoned.

Код возврата от Exim равен нулю, только если ошибок не было. Он будет 1, если некоторые сообщения были приняты до того, как была обнаружена ошибка, и 2, если сообщения не были приняты.

Глава 14
Прием сообщений и управление политиками

Давным-давно, когда Интернет был молод и невинен, MTA принимали любое посланное им сообщение и делали все возможное, чтобы доставить его, включая отправку на другой хост. Этот процесс известен как ретрансляция (relaying). Этот совместный подход в последнее время так часто подвергался злоупотреблениям, что в настоящее время считается плохой вещью для MTA быть неизбирательным в сообщениях, которые он готов принять.

Хост, который принимает произвольные сообщения для ретрансляции, называется открытым релеем (open relay); такие хосты раньше были обычным явлением, но по мере роста уровня злоупотреблений почти все они были устранены. В современном Интернете хосты, которые ретранслируют почту, должны убедиться, что они делают это только в определенных случаях, которые они ожидают обработать, например, ретранслировать только на определенные домены или с определенных хостов.

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

Эта глава посвящена тому, как Exim принимает входящие сообщения, включая проверки, которые могут применяться в процессе приема. Они задаются с помощью Access Control Lists (ACL). При установке «из коробки» Exim настроен так, чтобы не допускать никакой ретрансляции и накладывать некоторые стандартные проверки на входящие сообщения с других хостов. Системный администратор может настроить эти проверки, изменив списки управления доступом по умолчанию.

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

14.1 Источники сообщений

Сообщения, полученные через TCP/IP, обрабатываются иначе, чем сообщения, полученные от локальных процессов напрямую, и мы кратко укажем на некоторые из этих отличий. Локальный процесс, конечно, может установить соединение TCP/IP с локальным хостом либо с помощью интерфейса loopback, либо с использованием внешнего IP-адреса хоста. Сообщения, полученные по таким соединениям, обрабатываются так же, как и сообщения, полученные от удаленных узлов. В частности, обратите внимание, что loopback адрес не считается специальным. Если вы хотите, например, разрешить ретрансляцию сообщений, полученных через loopback интерфейс, вы должны настроить это явно[1].

SMTP — это единственный способ передачи сообщения через соединение TCP/IP, но когда сообщение передается из локального процесса без использования TCP/IP, поддерживается несколько форматов, включая использование SMTP через соединение канала. Если SMTP используется таким образом, сообщение по-прежнему считается исходящим от локального пользовательского процесса; только ввод через TCP/IP считается «удаленным». Однако все входные данные SMTP (будь то локальные или удаленные) подлежат проверкам, определенным ACL. Ввод без SMTP не проверяется.

В отличие от некоторых других MTA, Exim никогда не изменяет тела сообщений. В частности, он не пытается преобразовать одну форму кодирования в другую. Это «8-битный чистый», что означает, что он обрабатывает только те символы, которые требуется интерпретировать, такие как CR (возврат каретки) и LF (перевод строки). Все остальные значения символов рассматриваются как данные.

  1. Это разрешено конфигурацией по умолчанию.

14.2 Управление размером сообщения

Хорошей идеей будет установить ограничение на размер сообщения, которое будет обрабатывать Exim. Опция ограничения размера сообщения управляет этим ограничением. Например:

message_size_limit = 12M

устанавливает ограничение в 12 МБ (ограничение по умолчанию — 50 МБ). Сообщения, превышающие лимит, не принимаются[2].

Когда Exim создает сообщение о возврате, он добавляет оригинальное сообщение в конец текста сообщения об ошибке. Чтобы избежать отправки слишком больших сообщений о возврате, существует ограничение на объем копируемого исходного сообщения. Это устанавливается return_size_limit, которое по умолчанию равно 100 КБ. Тело исходного сообщения усекается при достижении предела[3] и комментарий, указывающий на это, добавляется вверху. Значение return_size_1limit всегда должно быть несколько меньше значения message_size_limit.

  1. Существует параметр транспорта, также называемый message_size_limit,, который ограничивает размер сообщения, которое может обрабатывать конкретный транспорт. Чтобы иметь какой-либо эффект, он, конечно, должен быть меньше глобального предела.

  2. Ограничение не точное до последнего байта из-за использования буферизации для передачи сообщения порциями.

14.3 Сообщения от локальных процессов

Процесс приема Exim может быть запущен любым локально запущенным процессом. Чаще всего это происходит, когда пользователь дает указание пользовательскому агенту отправить сообщение, но другие процессы также могут отправлять сообщения, если они того пожелают. Например, если команда, автоматически запускаемая cron, производит выходные данные, они отправляются пользователю по почте. По историческим причинам процессы, отправляющие сообщения таким образом, вызывают локальный MTA, используя один из путей /usr/sbin/sendmail или /usr/lib/sendmail. Какой бы из этих путей не использовался в вашей операционной системе, он должен быть настроен как символическая ссылка на Exim, которая совместима с интерфейсом Sendmail для приема сообщений таким образом.

Exim можно запускать прямо из оболочки, передавая параметры и аргументы в командной строке, а сообщения — в стандартном вводе. Обычно это полезно только для тестирования, потому что вы должны указать всё сообщение, включая все строки заголовка. Для отправки «настоящих» сообщений из оболочки лучше использовать пользовательский агент, такой как почтовая команда Unix.

14.3.1 Адреса в строках заголовка

В локально отправленном сообщении, если в любой из строк заголовка, содержащих адреса, найден неквалифицированный адрес, он квалифицируется с использованием домена, определенного qualify_domain (для отправителей) или qualify_recipient (для получателей) на момент получения сообщения. Например, на хосте с именем host.plc.example вы могли настроить:

qualify_domain = plc.example
qualify_recipient = ahost.plc.example

Если входящее сообщение содержит:

From: theboss
To: thedogsbody

Exim преобразует эти строки в:

From: theboss@plc.example
To: thedogsbody@ahost.plc.example

14.3.2 Указание адресов получателей

Есть несколько различных способов передачи адресов конвертов получателей в Exim из локального процесса. Во всех случаях разрешены неквалифицированные адреса; они квалифицированы с использованием домена, определенного опцией qualify_recipient. По умолчанию это значение qualify_domain, которое, в свою очередь, по умолчанию соответствует имени локального хоста.

Для обоих типов SMTP в одном соединении может быть передано более одного сообщения, тогда как в других случаях за один раз может передаваться только одно сообщение. Дополнительные сведения об обработке SMTP приведены в главе 13. Источники адресов получателей для локально отправленных сообщений и параметры командной строки, влияющие на них, приведены в таблице 14-1.

Таблица 14-1: Источники адресов получателей для локально отправленных сообщений
Опция Значение
нет Аргументы команды являются получателями
-t Получатели из заголовков (Bcc: удалено)
-bs Получатели локальных SMTP-команд RCPT
-bS Получатели пакетных SMTP-команд RCPT
  1. Эта опция также используется при запуске Exim под inetd; Exim может заметить разницу, потому что в случае с inetd стандартным вводом является сокет с соответствующим удаленным IP-адресом. При запуске из inetd Exim рассматривает сообщение как пришедшее с удаленного хоста.

14.3.3 Локальные адреса отправителей

Источник адреса отправителя конверта для сообщений, отправляемых локально (то есть не через TCP/IP), зависит от того, является ли вызывающий пользователь доверенным (19.3). Доверенному пользователю разрешено указать адрес отправителя с помощью команды MAIL, если сообщение получено с использованием SMTP, или одним из следующих способов:

Если вызывающий Exim не является доверенным, адрес отправителя, указанный любым из этих способов, распознается, но игнорируется. Вместо этого Exim создает отправителя конверта для сообщения, используя идентификатор входа в систему процесса, который его вызвал, и домен, указанный в параметре qualify_domain.

14.4 Неквалифицированные адреса с удаленных хостов

В RFC указано, что все адреса в сообщении, полученном от другого хоста, как в конверте, так и в строках заголовка, должны быть полностью квалифицированными (то есть содержать как локальную часть, так и домен). Есть только одно исключение: неквалифицированный адрес postmaster должен быть принят. Другие неполные адреса, например, в следующих командах SMTP:

MAIL FROM: <caesar>
RCPT TO:<brutus>

вызывают ошибочные ответы из-за отсутствия домена. Однако когда SMTP используется в качестве протокола отправки с локальных рабочих станций, иногда возникает необходимость ослабить это ограничение. Вы можете разрешить определенным хостам отправлять сообщения, содержащие неквалифицированные адреса, установив один или оба параметра sender_unqualified_hosts или recipient_unqualified_hosts в списки таких хостов. Например, шлюз в локальной сети 192.168.45.0 может разрешить своим локальным клиентам отправлять неквалифицированные адреса, установив:

sender_unqualified_hosts = 192.168.45.0/24
recipient_unqualified_hosts = 192.168.45.0/24

Неквалифицированные адреса не сохраняются в сообщениях, которые получает Exim. Каждый такой адрес квалифицируется, как только он принимается, с использованием домена, указанного в параметре qualify_domain для адресов отправителя и домена в параметре qualify_recipient для получателей. В качестве значения по умолчанию для qualify_domain используется имя локального хоста, а в качестве значения по умолчанию qualify_recipient используется значение qualify_domain.

На хосте delta.plc.example, если ни одна из этих опций не установлена, неполный адрес apollo становится apollo@delta.plc.example, но если установлен:

qualify_domain = plc.example

во всех случаях он становится apollo@plc.example. Однако со следующим:

qualify_domain = plc.example
qualify_recipient = delta.plc.example

более короткий домен используется только для адресов отправителей. К моменту доставки сообщения его конверт содержит только полные адреса.

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

14.5 Проверка удаленного хоста

В этом разделе мы рассмотрим ряд различных проверок, которые могут быть применены к удаленному хосту, когда он подключается к Exim для отправки сообщения. Это независимые проверки, которые не являются частью обработки ACL.

14.5.1 Явная блокировка хоста

Опция host_reject_connection заставляет Exim отклонять SMTP-соединения от определенных хостов, как только они получены. Например:

host_reject_connection = 192.168.34.5 : *.enemy.example

Эта опция предназначена для использования в исключительных случаях. Обычно SMTP-соединения принимаются, а проверки выполняются позже (с использованием ACL) во время передачи сообщения. Это обеспечивает большую гибкость. Например, вы можете принимать сообщения для постмастера от определенных хостов, но блокировать все остальное.

Когда соединение отклоняется host_reject_connection, сообщение остается на удаленном хосте, который может попытаться доставить его на альтернативные хосты MX или может попытаться повторно доставить его позже[5]. Это еще одна причина отказа на более позднем этапе. Если удаленный хост серьезно не поврежден, постоянное отклонение каждого получателя приводит к возврату сообщения без повторной попытки.

  1. Однако, если на удаленном хосте работает Exim, он будет возвращать сообщение при получении начального ответа 550.

14.5.2 Проверка имени хоста

При получении соединения TCP/IP единственной идентификацией удаленного хоста, которая есть у Exim, является IP-адрес. Хост должен указать свое имя в качестве данных в команде EHLO или HELO, но нет гарантии, что это имя, под которым он зарегистрирован. Рабочие станции, использующие SMTP для отправки почты, обычно отправляют неправильное имя.

Имя, предоставленное EHLO или HELO, помещается в переменную $sender_helo_name. Единственный способ, которым Exim может найти правильно зарегистрированное имя для хоста, это искать имя, связанное с IP-адресом. Его можно найти в таблице локальных хостов в /etc/hosts, но чаще требуется поиск в DNS. Это порождает две проблемы: во-первых, поиск в DNS может быть дорогим и занимать много времени, а во-вторых, значительное количество интернет-хостов регистрируется в DNS только «в одну сторону». То есть вы можете искать их имена, чтобы найти IP-адрес, но не наоборот.

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

host_lookup = *

Это заставляет Exim искать имя хоста для каждого входящего соединения. Если вы используете загруженный сервер, возможно, стоит удалить этот параметр или изменить его, чтобы он был менее общим. Например, вы можете использовать следующую настройку:

host_lookup = 192.168.3.0/24

чтобы ограничить поиск хостами в вашей локальной сети. Хотя host_lookup может содержать любой элемент, разрешенный в списке хостов, обычно он содержит только IP-адреса. Нет большого смысла включать имена хостов, потому что это подразумевает поиск имени хоста для проверки, можно ли найти имя хоста!

Если имя хоста найдено путем поиска IP-адреса, оно помещается в переменную $sender_host_name. Если поиск не выполняется или завершается неудачей, эта переменная пуста. Неспособность найти имя хоста сама по себе не приводит к немедленному отклонению соединения, но может привести к более позднему отклонению, если имя хоста необходимо для одной из проверок, описанных в следующих разделах.

Если хост не совпадает с host_lookup, Exim не ищет его имя в начале соединения. Однако, если одно из условий в ACL требует знания имени хоста, в этот момент выполняется поиск. Самый распространенный пример — наличие списка хостов, содержащего подстановочные знаки (18.6.4).

В лог-файлах Exim имена хостов, которые не были проверены, но которые являются просто данными команд EHLO или HELO, показаны в скобках. Было замечено, что некоторые неисправные клиентские хосты указывают имя сервера или один из его локальных доменов вместо своего собственного имени в командах EHLO или HELO. Предположим, что хост serverplc.example обрабатывает домен plc.example. Клиентский хост с именем broken.example, который сломан таким образом, может отправить эту команду:

ehlo server.plc.example

когда он должен отправить:

ehlo broken.example

Использование предоставленного имени в строках лога (даже в круглых скобках) вероятно вызовет путаницу, так что есть возможность заставить Exim искать правильное имя в этой ситуации. Если аргумент, указанный EHLO или HELO, совпадает со списком доменов helo_lookup_domains, поиск выполняется принудительно. Значение по умолчанию для этой опции следующее:

helo_lookup_domains = @ : @[]

Элемент @ соответствует имени хоста сервера, а элемент @[] соответствует любому из его IP-адресов в скобках (18.5).

14.5.3 Проверка EHLO или HELO

В RFC конкретно указано, что почта не должна быть отклонена на основании содержимого данных команд EHLO или HELO. Тем не менее, есть установки, которые хотят быть строгими в этой области, и Exim имеет опции helo_verify_hosts и helo_try_verify_hosts для их поддержки. Значения этих опций представляют собой списки хостов, для которых требуется более строгая проверка.

Разница между этими двумя вариантами заключается в том, что происходит, когда проверка не проходит. Для хостов, соответствующих helo_verify_hosts, сбой вызывает постоянную ошибку (код ошибки 550); запись записывается в журналы mainlog и rejectlog. Для хостов, соответствующих helo_try_verify_hosts, обработка продолжается после неудачной проверки (без записи в лог). Это состояние можно обнаружить в последующем ACL (14.8.15), что позволяет использовать его для принятия или отклонения адресов получателей в сочетании с другими условиями.

Если отправляющий хост соответствует helo_verify_hosts или helo_try_verify_hosts, аргумент, предоставленный EHLO или HELO, проверяется. Если он представлен в форме буквального IP-адреса в квадратных скобках, он должен совпадать с фактическим IP-адресом хоста-отправителя. Если это доменное имя, имя хоста-отправителя ищется по его IP-адресу и сравнивается с ним. Если поиск или сравнение не удается, выполняется поиск IP-адресов, связанных с именем EHLO или HELO, и их сравнение с IP-адресом хоста-отправителя. Если ни один из них не совпадает, проверка завершается неудачно.

Даже когда хосты helo_verify_hosts и helo_try_verify_hosts не используются, Exim отклоняет команды EHLO и HELO, которые содержат синтаксические ошибки. Одной из распространенных ошибок является появление символов подчеркивания в именах доменов[6]. Поскольку эта ошибка очень распространена, существует возможность указать дополнительные разрешенные символы. Этот параметр позволяет подчеркивание:

helo_allow_chars = _

Иногда возникает необходимость разрешить некоторым хостам отправлять реальный мусор в командах EHLO или HELO (включая вообще отсутствие данных); такие хосты можно разместить, установив helo_accept_junk_hosts. Например:

helo_accept_junk_hosts = 192.168.5.224/27

полностью удаляет проверку синтаксиса для хостов в этой сети.

  1. Существует распространенное заблуждение, что запрещение символов подчеркивания является ограничением DNS. Это не так; доменное имя в DNS может содержать самые разные символы (см. RFC 2181). Однако в RFC 2821, спецификации SMTP, есть ограничение, которое разрешает использовать только буквы, цифры, точки и дефисы в именах доменов, используемых в SMTP-транзакциях.

14.6 Ограничение скорости поступления сообщений

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

Если хост соответствует smtp_ratelimit_hosts, значения smtp_ratelimit_mail и smtp_ratelimit_rcpt используются для управления скоростью приема команд MAIL и RCPT соответственно. Эти параметры применяются независимо к отдельным SMTP-соединениям; нет обмена данными между разными соединениями с одного и того же хоста.

Каждый из этих параметров, если он установлен, должен содержать четыре значения, разделенных запятыми:

Порог
Это число указывает, сколько команд MAIL в одном соединении или сколько команд RCPT в одном сообщении может быть принято до того, как сработает ограничение скорости.
Начальная временная задержка
Это количество времени, на которое следует отложить первую команду после достижения порога. В отличие от других случаев в Exim, здесь разрешены числа с десятичными дробными частями.
Множитель
Это число, которое может содержать дробную часть, является коэффициентом, на который увеличивается задержка для каждой последующей команды.
Максимальное значение задержки
Обычно это должно быть менее пяти минут, потому что по истечении этого времени клиент может прервать выполнение команды SMTP.

Например, эти настройки успешно использовались на сайте, впервые предложившем эту функцию, для контроля почты от своих клиентов:


smtp_ratelimit_mail = 2,0.5s,1.05,4m
smtp_ratelimit_rcpt = 4,0.25s,1.015,4m

Первый параметр указывает задержки, которые применяются к командам MAIL после того, как два были получены по одному соединению. Начальная задержка составляет 0,5 секунды, увеличиваясь каждый раз в 1,05 раза до максимального значения в четыре минуты. Второй параметр применяет задержки к командам RCPT, когда в одном сообщении встречается более четырех.

14.7 Управление релеем

Конфигурация Exim по умолчанию не разрешает ретрансляцию сообщений с других хостов. Это предполагает простейшую среду, в которой вся почта, принимаемая от других хостов, адресована одному локальному домену, имя которого совпадает с именем хоста. Хост находится «в конце очереди» доставки почты. Это не означает, что вся входящая почта извне должна доставляться локально; могут быть псевдонимы или пользовательские файлы .forward, которые вызывают доставку сообщений на другие хосты. Такое перенаправление не классифицируется как ретрансляция.

Ретрансляция происходит, когда сообщение, полученное от другого хоста, передается третьему хосту без какой-либо ссылки на локальный домен в адресе получателя. Если сообщение отправляется с alpha.example на beta.example с адресом получателя homer@beta.example, ретрансляция не используется, даже если у пользователя homer на beta есть файл .forward, который отправляет сообщение на другой хост, поскольку исходный адрес получателя находится в локальном домене.

Однако, если получатель odysseus@gamma.example, то у нас есть случай ретрансляции с помощью beta:

alpha -> beta -> gamma

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

14.7.1 Входящая и исходящая ретрансляция

С точки зрения Exim, существует два вида ретрансляции сообщений, как показано на рисунке 14-1.

Рисунок 14-1: Ретрансляция сообщений

Хост, который действует как шлюз или как резервный MX, ретранслирует сообщения от произвольных хостов к определенному набору доменов. Это называется входящей ретрансляцией (incoming relaying). С другой стороны, хост, выступающий в качестве смарт-хоста для ряда клиентов, ретранслирует сообщения от этих клиентов в Интернет в целом. Это называется исходящей ретрансляцией (outgoing relaying). Что нежелательно, так это передача почты с произвольных удаленных хостов через вашу систему на произвольные домены.

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

14.8 Access control lists

Access control lists (списки контроля доступа) обеспечивают гибкость в том, как Exim проверяет входящие сообщения. Большинство из них применяются к непакетному вводу SMTP, будь то с удаленных хостов или с локального хоста. Существует также один ACL, который можно использовать для проверки пакетных SMTP-сообщений и сообщений, отличных от SMTP.

ACL определяются в отдельном разделе файла конфигурации среды выполнения, который начинается с begin acl. Каждое определение ACL начинается с имени, заканчивающегося двоеточием. Вот полный раздел ACL, содержащий только один очень маленький ACL:

begin acl

smail_acl:
  accept  hosts = one.host.example

Вы можете иметь сколько угодно списков ACL в разделе ACL, и порядок их появления не имеет значения. Списки являются самозавершающимися.

Большинство списков ACL используются для управления поведением Exim'а, когда он получает определенные SMTP-команды. Чаще всего используется для управления тем, какие получатели принимаются во входящих сообщениях (то есть, какие команды RCPT принимаются).

<р4>14.8.1 Конфигурация ACL по умолчанию

Прежде чем мы подробно опишем содержимое ACL, мы кратко рассмотрим ACL из файла конфигурации по умолчанию, который выглядит следующим образом: acl_check_rept:

acl_check_rept:
  accept  hosts         = :
  deny    local_parts   =   ^.*[@%!/|] : ^\\.
  accept  local_parts   = postmaster
          domains       = +local_domains
  require verify        = sender
  accept  domains       = +local_domains
          endpass
          message       = unknown user
          verify        = recipient
  accept  domains       = +relay_to_domains
          endpass
          message       = unrouteable address
          verify        = recipient
  accept  hosts         = +relay_from_hosts
  accept  authenticated = *
  deny    message       = relay not permitted

В самом файле также есть обильные комментарии. Этот ACL используется для каждой команды RCPT во входящем сообщении SMTP. Для каждого получателя операторы ACL рассматриваются по порядку, пока адрес получателя не будет либо принят, либо отклонен. Затем команда RCPT принимается или отклоняется в зависимости от результата обработки ACL.

acl_check_rept:

Эта строка, состоящая из имени, оканчивающегося двоеточием, отмечает начало ACL и дает ему имя.

accept  hosts = :

Этот оператор ACL принимает получателя, если хост-отправитель соответствует заданному списку хостов. Но что означает этот странный список? На самом деле он не содержит имен хостов или IP-адресов. Наличие двоеточия помещает в список пустой элемент; Exim сопоставляет это, только если входящее сообщение пришло не с удаленного хоста (поскольку в этом случае имя хоста и IP-адрес пусты). Двоеточие важно. Без него сам список пуст и никогда не сможет ничего сопоставить.

Этот оператор безоговорочно принимает всех получателей в сообщениях, отправленных SMTP из локальных процессов с использованием стандартного ввода и вывода (то есть без использования TCP/IP). Ряд MUA работает таким образом.

deny   local_parts  = ^.*[@%!/|] : ^\\.

В первом элементе этого утверждения используется регулярное выражение для отклонения адресов получателей, локальные части которых содержат любой из символов @, %, !, / или |. Хотя эти символы полностью допустимы в локальных частях (в случае @, только если они правильно указаны), они обычно не встречаются в почтовых адресах Интернета.

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

Второй элемент в инструкции deny использует другое регулярное выражение для отклонения адресов получателей, локальные части которых начинаются с точки. Это мера предосторожности против попыток злоупотребления конфигурациями, в которых локальная часть используется в качестве имени файла (например, когда это имя списка рассылки).

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

accept  local_parts   = postmaster
        domains       = +local_domains

Этот оператор, который имеет два условия, принимает входящий адрес, если локальная часть является postmaster, а домен является одним из перечисленных в списке доменов local_domains. Наличие этот оператор означает, что почта локальному постмастеру никогда не блокируется ни одним из последующих тестов. Это может быть полезно при устранении проблем в случаях, когда последующие тесты ошибочно запрещают доступ.

require verify        = sender

Этот оператор требует, чтобы адрес отправителя был проверен, прежде чем можно будет использовать любой последующий оператор ACL. Если проверка не пройдена, входящий адрес получателя отклоняется. Проверка заключается в попытке маршрутизировать адрес, чтобы увидеть, может ли на него быть доставлено сообщение. В случае удаленных адресов базовая проверка проверяет только домен, но при необходимости можно использовать выноски (callouts) для дополнительной проверки. Детали проверки адреса обсуждаются позже (14.9).

accept  domains       = +local_domains
        endpass
        message       = unknown user
        verify        = recipient

Этот оператор принимает входящий адрес получателя, если его домен является одним из локальных доменов, но только если адрес можно проверить. При проверке локальных адресов обычно проверяется и локальная часть, и домен.

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

accept  domains       = +relay_to_domains
        endpass
        message       = unrouteable address
        verify        = recipient

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

domainlist relay_to_domains = 

То есть это пустой список, не соответствующий доменам.

accept  hosts         = +relay_from_hosts

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

hostlist relay_from_hosts = 127.0.0.1

Это позволяет процессам на локальном хосте отправлять исходящие сообщения через интерфейс обратной связи. Exim не обрабатывает петлевой адрес каким-либо особым образом, поэтому необходимо явно включить этот вид ретрансляции, потому что некоторые пользовательские агенты, особенно MH и NMH, отправляют почту, подключаясь к петлевому адресу на локальном хосте.

Проверка получателя в этом операторе ACL опущена, потому что во многих случаях отправки сообщений клиенты являются глупыми MUA, которые плохо справляются с ответами об ошибках SMTP. Если вы на самом деле выполняете ретрансляцию из MTA, вам, вероятно, следует добавить проверку получателя в этот оператор ACL.

accept  authenticated = *

Сюда поступает управление, когда произвольный хост пытается выполнить ретрансляцию в произвольный домен. Оператор принимает адрес только в том случае, если клиентский хост аутентифицировал себя (13,2). Конфигурация по умолчанию не определяет никаких аутентификаторов, что означает, что ни один клиент не может пройти аутентификацию. Вам нужно добавить определения аутентификатора в конфигурацию, если вы хотите использовать этот оператор ACL.

deny    message       = relay not permitted

Последний оператор запрещает доступ, выдавая конкретное сообщение об ошибке. Достижение конца ACL также приводит к отказу в доступе, но с общим сообщением «administrative prohibition» (административный запрет).

14.8.2 Указание, когда используются ACL

Определение ACL в разделе ACL конфигурации времени выполнения само по себе не приводит к использованию ACL. Вы также должны указать Exim, когда его использовать. Глобальные параметры используются для указания того, какой ACL использовать в различных обстоятельствах. Они показаны в таблице 14-2.

Таблица 14-2: Варианты выбора ACL
Опция Использование
acl_smtp_auth ACL для запуска для команд AUTH
acl_smtp_connect ACL для запуска при запуске SMTP-соединения
acl_smtp_data ACL для запуска после завершения DATA
acl_smtp_etrn ACL для запуска для команд ETRN
acl_smtp_expn ACL для запуска для команд EXPN
acl_smtp_mail ACL для запуска для команд MAIL
acl_not_smtp ACL для запуска не-SMTP-сообщений
acl_smtp_rept ACL для запуска для команд RCPT
acl_smtp_starttls ACL для выполнения команд STARTTLS
acl_smtp_vrfy ACL для выполнения для команд VRFY

Например, с этой настройкой:

acl_smtp_rept = acl_check_rept

ACL по умолчанию, описанный ранее, используется всякий раз, когда Exim получает команду RCPT в диалоге SMTP. Большинство тестов политик для входящих сообщений могут быть выполнены при поступлении команд RCPT. Отклонение RCPT должно привести к тому, что отправляющий MTA откажется от этого адреса получателя.

Отклонение команды MAIL должно привести к отклонению всего сообщения, но некоторые MTA не реализуют это правильно и вместо этого продолжают пытаться отправить сообщение. Поэтому лучше вместо этого отклонять команды RCPT.

Однако вы не можете проверить содержимое сообщения (например, проверить адреса в строках заголовка) во время RCPT, поскольку само сообщение еще не передано. Такие тесты должны появиться в ACL, который запускается до того, как будет отправлен окончательный ответ на команду DATA. Это ACL, указанный в acl_smtp_data. В настоящее время уже невозможно отклонить отдельных получателей; ответ об ошибке отклоняет всё сообщение. К сожалению, известно, что некоторые MTA неправильно обрабатывают постоянную ошибку, полученную на этом этапе. Вместо этого они сохраняют сообщение в своей очереди и повторяют попытку позже, как это делают некоторые в случае отклоненной команды MAIL. Тем не менее, это их проблема, хотя она и тратит часть ваших ресурсов.

ACL, заданный параметром acl_not_smtp, запускается для входящих сообщений, которые не получены через интерактивный SMTP, то есть для не-SMTP-сообщений и пакетных SMTP-сообщений. ACL запускается после того, как сообщение прочитано, непосредственно перед тем, как оно будет окончательно принято. Отклонение либо приводит к тому, что Exim завершает работу с ненулевым кодом возврата, либо вызывает отправку сообщения о возврате отправителю, в зависимости от опции -oex, с которой был вызван Exim (20.4).

ACL, указанный в acl_smtp_connect, запускается в начале SMTP-соединения. Отказ в это время обычно заставляет хост-клиент повторить попытку позже.

Остальные параметры выбора ACL управляют использованием SMTP-команд AUTH, ETRN, EXPN, STARTTLS и VRFY.

14.8.3 Использование опций выбора ACL

Значения глобальных параметров выбора ACL расширяются перед использованием, поэтому вы можете использовать разные ACL в разных обстоятельствах. Чаще всего расширенная строка используется для имени ACL, определенного в конфигурации, как в примерах, показанных ранее. Однако это не единственная возможность. Развернув строку, Exim ищет ACL следующим образом:

14.8.4 Коды возврата ACL

Результатом выполнения ACL является либо «accept» (принятие), либо «deny» (отказ), либо, если какой-либо тест не может быть завершен (например, если база данных не работает), «defer» (откладывание). Эти результаты приводят к использованию кодов возврата 2xx, 5xx и 4xx соответственно в диалоге SMTP. Четвертый возврат, «error» (ошибка), возникает, когда возникает ошибка конфигурации, например, недопустимый синтаксис в ACL. Это также вызывает код возврата 4xx.

14.8.5 Параметры сброса ACL

Для большинства доступных ACL-списков, если соответствующая глобальная опция ac1_... не установлена, проверка не применяется, и разрешено выполнение соответствующего действия. Однако это не относится к командам ETRN, EXPN, RCPT и VRFY. Если для них не настроен ACL, они отклоняются. Это означает, что acl_smtp_rcpt должен быть определен до того, как Exim сможет получать какие-либо сообщения по SMTP-соединению.

14.8.6 Доступные данные для ACL сообщений

Когда запускается ACL для MAIL, RCPT или DATA, устанавливаются переменные, содержащие информацию о хосте и отправителе сообщения (например, $sender_host_address и $sender_address), и их можно использовать в операторах ACL. В случае RCPT (но не MAIL или DATA) $domain и $local_part устанавливаются из адреса получателя.

Переменной $message_size присваивается значение параметра SIZE в команде MAIL во время MAIL и RCPT или значение -1, если этот параметр не был задан. Его значение обновляется до истинного размера сообщения к моменту запуска ACL после DATA.

Переменная $rcpt_count увеличивается на единицу каждый раз при получении команды RCPT. Пока обрабатывается ACL для RCPT, значение $rcpt_count включает текущую команду. С другой стороны, переменная $recipients_count увеличивается на единицу каждый раз, когда принимается команда RCPT, поэтому во время обработки ACL для RCPT она содержит количество ранее принятых получателей. Таким образом, во время обработки первой команды RCPT значение $rcpt_count равно единице, тогда как значение $recipients_count равно нулю. Во время DATA $recipients_count содержит общее количество принятых получателей.

14.8.7 Доступные данные для ACL без сообщений

Когда запускается ACL для AUTH, ETRN, EXPN, STARTTLS или VRFY, оставшаяся часть командной строки SMTP помещается в $smtp_command_argument. Пример того, как это можно использовать для принятия решения о принятии команды, показан в разделе 14.8.20.

14.8.8 Формат ACL

Отдельный ACL состоит из ряда операторов. Каждое утверждение начинается с глагола, за которым может следовать ряд условий и других модификаторов. Если все условия соблюдены, глагол выполняется. Если условий нет, глагол всегда подчиняется. Что произойдет, если какое-либо из условий не будет выполнено, зависит от глагола (а в случае принятия — от специального модификатора endpass). Не все условия имеют смысл в каждой точке тестирования. Например, вы не можете проверить адрес отправителя в ACL, запущенном для команды VRFY.

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

14.8.9 Глаголы ACL

Глаголы ACL следующие:

accept

Если все условия соблюдены, ACL возвращает «accept». Если какое-либо из условий не выполняется, то, что происходит, зависит от того, появляется ли endpass среди условий. Если условие отказа предшествует endpass, управление передается следующему оператору ACL; если он следует за endpass, ACL возвращает «deny». Пример этого обсуждался в разделе 14.8.1.

defer

Если все условия соблюдены, ACL возвращает «defer». Если какое-либо из условий не выполняется, управление передается следующему оператору ACL. Результат «defer» вызывает временное отклонение SMTP-команды посредством кода ошибки 4xx.

deny

Если все условия соблюдены, ACL возвращает «deny». Если какое-либо из условий не выполняется, управление передается следующему оператору ACL. Например:

deny dnslists = blackholes.mail-abuse.org

отклоняет команды от хостов, которые находятся в черном списке DNS.

drop

Если все условия соблюдены, действие такое же, как и при deny, за исключением того, что после отправки постоянного кода ошибки соединение SMTP разрывается.

require

Если все условия соблюдены, управление передается следующему оператору ACL. Если какое-либо из условий не выполняется, ACL возвращает «deny». Например, при проверке команды RCPT:

require verify = sender

передает управление последующим операторам, только если отправитель сообщения может быть проверен. В противном случае он отклоняет команду.

warn

Если все условия соблюдены, содержимое модификаторов message и log_message (описанных вкратце) используется для добавления предупредительной строки заголовка к входящему сообщению или записи в основной журнал соответственно. Никаких действий не предпринимается, если возникает временная проблема с одним из условий, но инцидент регистрируется. Во всех случаях управление переходит к следующему оператору ACL. Например, следующее утверждение:

warn message = X-blacklisted-at: $dnslist_domain
     dnslists = blackholes.mail-abuse.org : \
                dialup.mail-abuse.org

добавляет строку заголовка X-blacklisted-at: к сообщениям, полученным от хостов, которые находятся в одном из заданных черных списков DNS. ACL для команд RCPT можно запускать много раз для одного сообщения, но любая заданная строка заголовка добавляется только один раз.

Строки заголовков, добавляемые ACL для команды RCPT, видны, когда последующий ACL запускается в конце команды DATA. Это обеспечивает способ передачи данных между двумя ACL.

В конце каждого ACL есть неявный безусловный deny.

14.8.10 Обработка условий и модификаторов

Не все условия применимы ко всем обстоятельствам. Например, проверка отправителей и получателей не имеет смысла в ACL, который запускается в результате поступления команды ETRN, а проверки заголовков сообщений могут выполняться только в ACL, заданном параметрами acl_smtp_data или acl_not_smtp.

Восклицательный знак перед условием отрицает его результат. Например:

deny   domains = *.dom.example
      !verify  = recipient

заставляет ACL возвращать «deny», если домен получателя заканчивается на dom.example и адрес получателя не может быть проверен.

Аргументы условий и модификаторов расширены. Принудительный сбой расширения приводит к игнорированию условия, т. е. ведет себя так, как если бы условие было истинным. Рассмотрим эти два утверждения:

accept senders = ${lookup{$host_name}lsearch\
                 {/some/file}{$value}fail}
accept senders = ${lookup{$host_name}lsearch\
                 {/some/file} {$value} {}}

Каждое из них пытается найти список приемлемых отправителей. Если поиск завершается успешно, выполняется поиск в возвращаемом списке, но если поиск терпит неудачу, поведение в этих двух случаях различно. fail в первом операторе приводит к тому, что условие игнорируется, и никаких дополнительных условий не остается. Таким образом, глагол accept имеет успех. Однако второй оператор генерирует пустой список, когда поиск не удается. Ни один отправитель не может соответствовать пустому списку, поэтому условие не выполняется, и, следовательно, accept также не выполняется.

14.8.11 Модификаторы ACL

Модификаторы ACL: control, delay, endpass, log_message и message. Они действуют следующим образом:

Модификатор control

Этот модификатор имеет аргумент, который является либо freeze, либо queue_only. Он может появляться только в списках ACL для команд, относящихся к входящему сообщению. Например:

accept  hosts   = +queue_hosts
        control = queue_only

Модификатор control приводит к тому, что сообщение замораживается или просто ставится в очередь (без немедленной доставки), соответственно, при условии, что сообщение принято. Как только один из этих элементов управления установлен, он остается установленным для сообщения и не может быть отменен другими ACL-списками.

Вы можете использовать control с глаголом warn, чтобы применить контроль, оставив решение принять или отклонить последующий глагол. Например:

warn    hosts   = +freeze hosts
        control = freeze
accept  ...

Если warn не имеет ни модификаторов message, ни модификаторов log_message (как в этом примере), он ничего не добавляет к сообщению и не записывает запись в журнал.

Модификатор delay

Этот модификатор, у которого есть аргумент, определяющий временной интервал, заставляет Exim ждать временной интервал, прежде чем продолжить. Время указано в обычной нотации Exim. Этот модификатор может появиться в любом ACL. Как и control, вы можете использовать задержку с глаголом warn, чтобы вызвать задержку, не принимая решения о принятии или отклонении.

Задержка происходит, как только модификатор обрабатывается; в отличие от message и log_message (см. ниже), которые не используются до тех пор, пока не будут оценены все условия. Например:

deny  condition = ${if > {$rcpt_count}{20}{yes}{no}}
      delay = 30s

В этом примере, если имеется более 20 команд RCPT, текущая команда отклоняется после 30-секундной паузы. Некоторым администраторам нравится включать задержки этого типа, чтобы замедлить отправку нежелательной почты.

Модификатор endpass

Этот модификатор, не имеющий аргумента, распознается только в операторах accept. Он отмечает границу между условиями, невыполнение которых приводит к передаче управления следующему оператору, и условиями, невыполнение которых приводит к тому, что ACL возвращает «deny».

Модификатор log_message

Для оператора warn этот модификатор указывает сообщение, которое записывается в основной журнал, если условия верны. «Warning:» добавляется в начало сообщения. Например, чтобы разрешить все команды AUTH, а также регистрировать их содержимое, вы можете использовать следующий ACL:

acl_check_auth:
  warn   log_message = received from $sender_fullhost: \
                       AUTH $smtp_command_data
  accept

Для операторов, отличных от warn, модификатор log_message устанавливает текст, который будет использоваться как часть сообщения журнала, когда доступ запрещен. Для операторов accept и require сообщение используется только в том случае, если оно предшествует условию, вызывающему отклонение. Например:

require log_message = wrong cipher
        encrypted   = DES -CBC3 -SHA

Этот оператор запрещает доступ, включая текст «wrong cipher» (неправильный шифр) в строке журнала, если сеанс SMTP не зашифрован с использованием набора шифров DES-CBC3-SHA.

log_message добавляется к любому базовому сообщению об ошибке, которое может существовать из-за условия. Например, при проверке адреса получателя перенаправление с использованием средства :fail: могло уже создать сообщение.

Хотя log_message задает текст перед условиями, к которым он применяется, расширение не происходит до тех пор, пока условие не будет выполнено. Это означает, что любые переменные, заданные условием, доступны для включения в сообщение. Например, ряд переменных, имена которых начинаются с $dnslist, устанавливаются после успешного поиска в черном списке DNS.

Модификатор message

Для оператора warn этот модификатор указывает строку заголовка, которая должна быть добавлена к входящему сообщению, если условия верны. Пример показан ранее с описанием глагола warn. Модификатор message игнорируется, если он используется с оператором warn в ACL, который не связан с входящим сообщением.

Для инструкций, отличных от warn, модификатор message устанавливает сообщение, которое используется как сообщение об ошибке, если текущая инструкция заставляет ACL отказывать в доступе. Сообщение возвращается как часть ответа SMTP. Для операторов accept и require используется самый последний модификатор message, который предшествует условию отказа. Рассмотрим эту версию более раннего примера, в котором строки message и verify поменялись местами:

accept  domains    = +local_domains
        endpass
        verify     = recipient
        message    = unknown user

Это не работает должным образом; предоставленное сообщение никогда не используется, потому что отклонение происходит до достижения модификатора message.

Операторы accept и require могут содержать более одного модификатора message, каждый из которых применяется к невыполнению другого условия. Оператор deny, с другой стороны, проверяет все свои условия перед отказом в доступе. Следовательно, всегда используется последний модификатор message, даже если он появляется после выполнения всех условий.

Текст модификатора message литеральный; любые кавычки воспринимаются как литералы, но поскольку текст расширяется, символы обратной косой черты всегда обрабатываются. Сообщение, содержащее новые строки, приводит к многострочному SMTP-ответу. Как и log_message, содержимое message не раскрывается до тех пор, пока не будет выполнено условие.

Если сообщение указано в инструкции ACL, которая запрещает доступ из-за сбоя проверки адреса, предоставленное сообщение переопределяет сообщение об ошибке из процесса проверки. В частности, оно переопределяет сообщение, установленное с помощью :fail: в роутере redirect. Однако исходное сообщение доступно в $acl_verify_message, так что вы можете использовать его как часть или вместо вашего сообщения, если хотите.

Взаимодействие message и log_message

Если log_message отсутствует при отказе в доступе, но присутствует message, текст message используется для регистрации. Однако, если он содержит символы новой строки, регистрируется только первая строка текста. При отсутствии как log_message, так и message используется встроенное сообщение по умолчанию. Ни log_message, ни message не используются, если они являются пустыми строками или если их расширения завершаются неудачей.

14.8.12 Условия ACL

Некоторые из наиболее распространенных условий ACL показаны в предыдущих примерах. Полный список показан в таблице 14-3. Использование этих условий описано в следующих разделах.

Таблица 14-3: Условия ACL
Условие Использование
acl Позволяет одному ACL вызывать другой
authenticated Тесты для аутентификации SMTP
condition Общее настраиваемое условие
dnslists Проверяет черные списки DNS
domains Проверяет домен адреса получателя
encrypted Тесты для зашифрованного сеанса SMTP
hosts Тесты для конкретных отправляющих хостов
local_parts Проверяет локальную часть адреса получателя
recipients Проверяет адрес получателя
sender_domains Проверяет домен адреса отправителя
senders Проверяет адрес отправителя
verify Проверяет адреса отправителя и получателя. Проверяет синтаксис и адреса отправителя в строках заголовков. Тестирует сертификат и проверяет HELO.

14.8.13 Проверка идентификации хоста клиента

Условие hosts проверяет, соответствует ли вызывающий хост заданному списку хостов. Этот тест чаще всего используется для разрешения ретрансляции с хостов в локальной сети с использованием фрагмента ACL, такого как эта строка из конфигурации по умолчанию (описанной ранее):

accept hosts = +relay_from_hosts

Если вы включаете поиск имен или имена хостов с подстановочными знаками в список хостов, который также содержит IP-адреса, вы обычно должны указывать IP-адреса первыми. Например, у вас может быть следующее:

accept hosts = 10.9.8.7 : dbm;/etc/friendly/hosts

Причина этого заключается в том, что Exim обрабатывает списки слева направо. Он может тестировать IP-адреса без каких-либо DNS-запросов, но когда он достигает элемента, требующего имени хоста, он терпит неудачу, если не может найти имя хоста для сравнения с шаблоном. Если расположить этот список в обратном порядке, следующим образом:

accept hosts = dbm;/etc/friendly/hosts : 10.9.8.7

оператор accept не работает для хоста, имя которого не может быть найдено, даже если его IP-адрес равен 10.9.8.7, потому что для поиска требуется имя хоста, которое идет первым.

Если вы действительно хотите сначала выполнить проверку имени и по-прежнему распознавать IP-адрес, даже если поиск имени не удался, вы можете переписать ACL следующим образом:

accept hosts = dbm; /etc/friendly/hosts
accept hosts = 10.9.8.7

Действие по умолчанию при невозможности найти имя хоста состоит в том, чтобы предположить, что хост отсутствует в списке, поэтому первый оператор accept завершается ошибкой. Затем второй оператор проверяет IP-адрес.

14.8.14 Использование черных списков DNS

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

Списки хранятся в DNS, чтобы сделать их легко доступными, а хосты индексируются по IP-адресу. Первоначальный список, положивший начало этой тенденции, называется Realtime Blackhole List. Это поддерживаемый вручную «черный список» хостов, которые, по мнению тех, кто ведет этот список, ведут себя ненадлежащим образом. Другой список, которым сейчас управляет тот же сайт, — это Dial-up User List (DUL)[7]. Этот список содержит IP-адреса, используемые интернет-провайдерами для своих клиентов с коммутируемым доступом. Многие администраторы настраивают свои MTA так, чтобы отказываться от прямых SMTP-соединений с таких хостов, аргументируя это тем, что пользователи коммутируемого доступа должны отправлять почту через серверы своего интернет-провайдера, где ее можно лучше контролировать. Некоторые другие списки используют автоматические средства для обнаружения открытых релеев; некоторые из их политик оказались спорными.

  1. См. http://mail-abuse.org/rbl/ и http://mail-abuse.org/dul/.

Проверка списка DNS в ACL

Условие dnslists ACL проверяет наличие записей в черных списках DNS. В своей простейшей форме условие проверяет, находится ли вызывающий узел в черном списке DNS, путем поиска инвертированного IP-адреса в одном или нескольких доменах DNS. Например, если IP-адрес вызывающего узла — 192.168.62.43, а оператор ACL выглядит следующим образом:

deny dnslists = blackholes.mail-abuse.org : \
                dialups.mail-abuse.org

просматриваются следующие домены:

43.62.168.192.blackholes.mail-abuse.org
43.62.168.192.dialups.mail-abuse.org

Если время поиска DNS истекает или по какой-либо другой причине не дает окончательного ответа, Exim ведет себя так, как будто хост не находится в соответствующем списке. Обычно это обязательное действие, когда dnslists используется с запретом (что является наиболее распространенным использованием), потому что оно предотвращает блокировку почты из-за сбоя DNS. Однако вы можете изменить это поведение, добавив в список один из следующих специальных элементов:

+include_unknown    behave as if the item is on the list
+exclude_unknown    behave as if the item is not on the list (default)
+defer_unknown      give a temporary error

Каждый из них применяется к любым последующим элементам в списке. Например:

deny dnslists = +defer_unknown : foo.bar.example

Проверка списка доменов прекращается, как только запись в DNS найдена. Если вы хотите выдать предупреждение для одного списка и заблокировать для другого, вы можете использовать два разных оператора, как в этом примере:

deny  dnslists = blackholes.mail-abuse.org
warn  dnslists = dialups.mail-abuse.org

Некоторые списки DNS содержат доменные имена, а не инвертированные IP-адреса[8]. Вы можете изменить искомое имя, добавив дополнительные данные в элемент dnslists, введенный косой чертой. Например:

deny  message  = Sender's domain is listed at $dnslist_domain
      dnslists = dsn.rfc-ignorant.org/$sender_address_domain

Этот конкретный пример полезен только в ACL, который выполняются после команд RCPT или DATA, когда доступен адрес отправителя. Если отправитель сообщения — user@tld.example, в этом примере ищется домен tld.example.dsn.rfc-ignorant.org. Вы можете смешивать записи с дополнительными данными и без них в одном и том же условии dnslists.

Конечно, вы можете комбинировать dnslists с любым другим условием ACL. Например, вы можете исключить из этой проверки подключения от хостов в вашей локальной сети. Этот фрагмент ACL:

deny  hosts    = !192.168.56.224/28
      dnslists = rbl-plus.mail-abuse.example

указывает, что проверка списка DNS происходит только для подключений от хостов, не входящих в сеть 192.168.56.224/28.

  1. См., например, ссылку на domain based zones по адресу http://www.rfc-ignorant.org/.

Данные получены из черного списка DNS

Если запись находится в черном списке DNS, $dnslist_domain содержит имя совпадающего домена, $dnslist_value содержит данные из записи DNS, а $dnslist_text содержит содержимое любой связанной записи TXT. Вы можете использовать эти переменные в модификаторах message или log_message. Хотя они появляются перед условием в ACL, они не раскрываются до тех пор, пока не произойдет сбой. Например:

deny  hosts    = !+local_networks
      message  = $sender_host_address is listed at $dnslist_domain
      dnslists = rbl-plus.mail-abuse.example

Поиск по черному списку DNS кэшируется Exim во время выполнения процесса приема, поэтому поиск на основе IP-адреса или адреса отправителя происходит не более одного раза для любого входящего соединения, даже если он появляется в ACL, который выполняется для каждого получателя.

Ограничение dnslists определенными значениями данных

Черные списки DNS создаются с использованием адресных записей в DNS. Исходный RBL просто использовал адрес 127.0.0.1 в правой части записей, но список RBL+ и некоторые другие списки используют ряд значений с разными значениями. Значения, используемые в списке RBL+, показаны в таблице 14-4.

Таблица 14-4: Использование адреса RBL+
Адрес Значение
127.1.0.1 RBL
127.1.0.2 DUL
127.1.0.3 DUL и RBL
127.1.0.4 RSS
127.1.0.5 RSS и RBL
127.1.0.6 RSS и DUL
127.1.0.7 RSS, DUL и RBL

Если вы добавите знак равенства и IP-адрес после имени домена dnslists, вы можете ограничить его действие записями DNS с соответствующей правой частью. Например, это утверждение:

deny dnslists = rblplus.mail-abuse.org=127.1.0.2

отклоняет только те хосты, которые дают 127.1.0.2. Можно указать более одного адреса, используя запятую в качестве разделителя. Это альтернативы; если хоть один из адресов совпадает, запись RBL работает. Если адресов нет, любая адресная запись считается совпадением.

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

deny dnslists = dsn.rfc-ignorant.org=127.0.0.3/$sender_address_domain

14.8.15 Проверка состояния SMTP-соединения

Существует несколько условий ACL для проверки состояния SMTP-соединения.

Проверка зашифрованного соединения

Условие encrypted проверяет наличие зашифрованного SMTP-соединения и, при необходимости, используемый набор шифров. Чтобы проверить шифрование без проверки каких-либо конкретных шифров, используйте этот параметр:

encrypted = *

Если SMTP-соединение зашифровано, звездочка соответствует любому набору шифров, поэтому условие истинно. Если SMTP-соединение не зашифровано, условие ложно. Вместо звездочки можно указать список названий наборов шифров. Например:

encrypted = IDEA-CBC-MD5:DES-CBC3-SHA

При этом условие выполняется только в том случае, если используется один из перечисленных наборов.

Проверка наличия подтвержденного сертификата

Опция tls_try_verify_hosts описана в разделе 13.1.2. Для совпадающих хостов это заставляет Exim запрашивать клиентский сертификат при настройке зашифрованного сеанса, но не завершается ошибкой, если сертификат не предоставлен, или если сертификат не тот, который ожидается. Следующее условие ACL:

verify = certificate

верно только в том случае, если сеанс зашифрован и сертификат был получен от клиента и проверен.

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

Проверка SMTP-аутентификации

Условие ACL authenticated используется для проверки подлинности входящего SMTP-соединения. Этот пример завершается успешно, если использовался какой-либо аутентификатор:

accept authenticated = *

Вы можете ограничить проверку определенными аутентификаторами, указав их имена в качестве аргумента условия. Например, вы можете разрешить ретрансляцию с локального хоста, использующего незашифрованный механизм аутентификации, но в противном случае требовать аутентификацию CRAM-MD5. Этот фрагмент ACL показывает, как можно написать такой тест:

accept hosts         = +local_lan_hosts
       authenticated = *
accept authenticated = cram_md5

Проверка команды EHLO

Опция helo_try_verify_hosts описана в разделе 14.5.3. Для совпадающих хостов это заставляет Exim проверять данные, предоставленные в команде EHLO или HELO, но продолжать, даже если проверка не удалась. Следующее условие ACL:

verify = helo

истинно, только если получена команда EHLO или HELO и ее содержимое проверено.

14.8.16 Проверка адресов отправителей

Существуют условия ACL, которые проверяют конкретные адреса отправителей, а также существует возможность проверки адресов отправителей. «Адрес отправителя» во всех этих случаях берется из конверта сообщения, а не из какой-либо строки заголовка.

Проверка конкретных адресов отправителей

Условие senders проверяет, соответствует ли отправитель входящего сообщения заданному списку адресов. Например, чтобы заблокировать сообщения от spammer@coldmail.example и от любой локальной части spamhaus.example, вы можете использовать этот оператор ACL:

deny senders = spammer@coldmail.example : \
               *@spamhaus.example

Чтобы проверить сообщение о возврате, у которого есть пустой отправитель, можно использовать следующее условие:

senders = :

Двоеточие необходимо для создания пустого элемента списка. Полностью пустой список никогда не соответствует ни одному отправителю.

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

domainlist relay_to_domains = doml.example : dom2.example
hostlist   relay_from_hosts = 127.0.0.1 : 192.168.23.0/24

Возможно, вы захотите проверить, что отправители исходящих ретранслируемых сообщений находятся в доменах входящей ретрансляции. Вы можете сделать это, изменив этот оператор ACL:

accept  hosts = +relay_from_hosts

в этот оператор ACL:

accept  hosts          = +relay_from_hosts
        endpass
        message        = Invalid sender domain
        sender domains = +relay_to_domains

Проверка адресов отправителей

Адрес отправителя в конверте сообщения — это адрес, на который отправляются возвратные сообщения. Если хост принимает сообщение с неправильным адресом отправителя, но впоследствии не может доставить сообщение, результатом является недоставленное сообщение о возврате, которое администратор почты должен отсортировать. На очень загруженных хостах такие проблемы часто просто игнорируются путем установки значения ignore_bounce_errors_after равным нулевому временному интервалу, но также разумно выполнить некоторую проверку адреса отправителя, прежде чем принимать сообщение.

Ниже перечислены наиболее распространенные причины неверных адресов отправителя (не по порядку):

Вы можете настроить Exim для проверки адресов отправителей, включив следующее условие в ACL:

verify = sender

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

Проверка также может быть использована для адресов получателей, поэтому мы оставим обсуждение ее деталей на более позднем этапе этой главы (14.9). Exim кэширует результат проверки отправителя, чтобы не делать это более одного раза для каждого сообщения.

14.8.17 Проверка адресов получателей

Предыдущее обсуждение ACL по умолчанию описывает использование условий domains и local_parts для проверки входящих адресов получателей в командах SMTP RCPT. Типичный пример из конфигурации по умолчанию выглядит следующим образом:

accept  local_parts = postmaster
        domains     = +local_domains

Когда любое из этих условий успешно выполняется при поиске, результат поиска помещается в $local_parts_data или $domain_data соответственно.

Существует также условие recipients, которое проверяет весь адрес получателя по списку адресов получателей. Предположим, у вас есть файл получателей, которые просили вас заблокировать сообщения от определенного отправителя. Вы можете использовать оператор ACL следующим образом:


deny  sender     = badguy@evil.example
      recipients = lsearch;/etc/blockbadguy

Вы также можете попросить Exim проверить каждого получателя, прежде чем принять его, включив следующее условие в соответствующий оператор ACL:

verify = recipient

ACL по умолчанию содержит примеры этого. Детали верификации обсуждаются далее в этой главе.

14.8.18 Автоматическое определение резервных доменов MX

Если хост действует как резервная копия MX для большого количества доменов, которые часто меняются, поддержание их списка для Exim для консультации, в дополнение к соответствующим записям MX в DNS, является дублированием усилий. Этого можно избежать, используя специальный элемент @mx_any в списке доменов, как в этом примере:

accept domains = @mx_any

Этот оператор ACL принимает любого получателя, чей домен имеет MX, указывающий на локальный хост. Элементы @mx_primary и @mx_secondary аналогичны, за исключением того, что первый совпадает только тогда, когда основной целью MX является локальный хост, а второй совпадает только тогда, когда никакой основной целью MX не является локальный хост, но есть вторичная цель. «Primary» означает запись MX с наименьшим значением предпочтения; конечно, может быть более одной такой записи MX.

14.8.19 Проверка строк заголовка сообщения

Распространенной уловкой рассылки спама является отправка синтаксически недопустимых строк заголовков, как в следующих примерах[9]:

To: @
To: <>

Кроме того, было замечено, что некоторые MUA отправляют недопустимые строки заголовков следующих типов[10]:

To: user@domain.example <user@domain.example>
To: <mailto:user@domain>

Exim может тестировать такие синтаксические ошибки в ACL, который запускается после получения сообщения, то есть в ACL, указанном в acl_smtp_data. Например, следующий оператор отклоняет сообщения с синтаксически недопустимыми строками заголовка:

require verify = header_syntax

При этом проверяется синтаксис всех строк заголовков, которые могут содержать списки адресов (Sender:, From:, Reply-To:, To:, Cc: и Bcc:). Это только проверка синтаксиса; проверка адресов не производится.

Другое условие, относящееся только к ACL, который запускается после получения сообщения, выглядит следующим образом:

verify = header_sender

Оно проверяет наличие поддающегося проверке адреса отправителя по крайней мере в одной из строк заголовка Sender:, Reply-To: или From:. Подробности проверки адреса приведены далее в этой главе. Вы можете комбинировать это условие с условием отправителя, чтобы ограничить его только сообщениями о возврате (если в конверте нет поддающегося проверке отправителя). Например:

deny   senders = :
       message = A valid sender header is required for bounces
      !verify  = header_sender
  1. Первый адрес неверен, потому что нет локальной части или домена. Пустой адрес, как и во втором примере, не допускается в строках заголовка (разрешен только в команде SMTP MAIL).

  2. В первом случае специальный символ (знак at) появляется без кавычек в «фразовой» части адреса RFC 2822. Во втором случае специальный символ (двоеточие) появляется в локальной части без кавычек.

14.8.20 Пользовательские проверки ACL

Условие condition позволяет создавать настраиваемые тесты в ACL, используя любые доступные элементы расширения строки и переменные. Значение условия расширяется, и если результатом является пустая строка, число ноль, no или false, условие ложно. Если результатом является ненулевое число, yes или true, условие истинно. Предполагается, что любое другое значение указывает на какую-то ошибку конфигурации, поэтому ACL дает «defer».

Например, предположим, что вы хотите разрешить использование любого механизма аутентификации в зашифрованном соединении, но только CRAM-MD5, если соединение не зашифровано. Лучший способ сделать это — отказаться от команды AUTH, когда условия не выполняются. Вы можете сделать это, создав ACL для проверки команды AUTH следующим образом:

acl_check_auth:
  accept encrypted = *
  accept condition = ${if eq {${uc:$smtp_command_argument}}\
                     {CRAM-MD5}{yes}{no}}
  deny   message = Require CRAM-MD5 or TLS encrypted connection
  *

Первый оператор в этом примере принимает команду AUTH, если соединение зашифровано. Второй оператор использует пользовательский тест, чтобы определить, является ли CRAM-MD5 аргументом команды AUTH. Результат расширения — yes, только если аргумент совпадает.

14.8.21 Вызов вложенных ACL-списков

Один ACL-список может вызывать другой ACL-список в виде «подпрограммы» с помощью условия acl. Это может упростить выполнение разных ACL для разных получателей, разных доменов или разных узлов-отправителей. Его также можно использовать для разделения длинных списков ACL на более управляемые части. Например, ACL по умолчанию можно переписать так, чтобы разделить ACL для локальных и удаленных адресов следующим образом:

acl_check_rcpt:
  accept  hosts = :
  deny    local_parts  = ^.*[@%!/|] : ^\\.
  accept  domains      = +local_domains
          endpass
          acl          = acl_local
  accept  acl          = acl_relay

«Подпрограммы» следующие:

acl_local:
  accept  local_parts   = postmaster
  require verify        = sender
  accept  verify        = recipient
  deny    message       = unknown user

acl_relay:
  require verify        = sender
  accept  domains       = +relay_to_domains
          endpass
          message       = unrouteable address
          verify        = recipient
  accept  hosts         = +relay_from_hosts
  accept  authenticated = *
  deny    message       = relay not permitted

Сообщение, установленное во вложенном ACL, переопределяется сообщением, указанным на более высоком уровне.

Возможные значения аргумента условия acl такие же, как и для опций выбора ACL (14.8.3). Для проверки условия запускается именованный или встроенный ACL. Если он возвращает «accept», условие истинно; если он возвращает «deny», условие ложно; если он возвращает «defer», вызывающий ACL возвращает «defer». ACL могут быть вложены до 20 в глубину; предел существует исключительно для того, чтобы поймать неконтролируемые циклы.

14.9 Проверка адреса

Верификация — это процесс проверки адреса отправителя или получателя путем проверки, насколько это возможно, может ли Exim доставить ему сообщение. Успешная проверка не может гарантировать, что адрес будет доставлен, но отказ от проверки гарантирует, что он не будет доставлен.

Несколько условий verify, описанных в предыдущем разделе, вызывают проверку адресов. За этими условиями может следовать ряд опций, изменяющих процесс проверки. Опции отделяются от ключевого слова и друг от друга косой чертой. Например:

verify = sender/callout
verify = recipient/defer ok/callout=10s

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

Если при выполнении маршрутизации проверки возникает ошибка отсрочки, ACL обычно возвращает «defer». Однако, если вы включаете defer_ok в опции, вместо этого условие становится истинным.

14.9.1 Проверка обратными вызовами

Для локальных адресов при маршрутизации обычно проверяется правильность локальной части адреса. Однако для удаленных адресов маршрутизация проверяет домен, но не может выполнять какую-либо проверку локальной части. Бывают ситуации, когда желательны некоторые средства проверки локальной части удаленных адресов. Один из способов сделать это — callback SMTP на хост-отправитель (для адреса отправителя) или callforward на последующий хост (для адреса получателя), чтобы увидеть, принимает ли хост адрес в SMTP-команде. Мы используем термин callout (вызов), чтобы охватить оба случая.

Если опция callout присутствует в условии ACL, которое проверяет адрес, второй этап проверки происходит, если адрес успешно маршрутизируется на один или несколько удаленных хостов. Exim устанавливает SMTP-соединения с удаленными хостами, чтобы проверить, приемлем ли адрес. Для адреса отправителя он ведет себя так, как будто передает сообщение об отказе и отправляет:

HELO <primary host name>
MAIL FROM:<>
RCPT TO:<<the address to be tested>>
QUIT

При проверке адреса получателя команда MAIL в вызове содержит адрес отправителя сообщения. Если ответом на команду RCPT является код 2xx, проверка завершается успешно. Если это 5xx, проверка не проходит. В любом другом случае Exim пробует следующий хост, если он есть. Если есть проблема со всеми удаленными хостами, ACL выдает «defer», если только не задан параметр вызова defer_ok, и в этом случае условие принудительно выполняется успешно.

Параметры вызова указываются после знака равенства после callout и разделяются запятыми, если их несколько. Например:

deny  !verify = sender/callout=defer_ok

Тайм-аут по умолчанию составляет 30 секунд для каждого подключения к хосту при попытке вызова. Это можно изменить, указав другое время в опции callout. Например:

require verify = sender/callout=5s

Для соединений с вызовом SMTP порт для подключения и исходящий интерфейс берутся из транспорта, на который был перенаправлен адрес, если это удаленный транспорт. В противном случае используется порт 25, а интерфейс не указывается.

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

Вы можете снизить стоимость вызовов, ограничив их использование определенными доменами или определенными хостами-отправителями. Например, одним из случаев, когда стоимость вызова может быть приемлемой, является корпоративный шлюз, который проверяет адреса в доменах, которые являются локальными для корпорации, но не локальными в смысле Exim, используя операторы ACL, такие как эти:

deny   sender_domains = +corporate_domains
      !verify         = sender/callout
deny   domains        = +corporate_domains
      !verify         = recipient/callout

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

Дополнительные функции вызова

Некоторые дополнительные функции, которые были добавлены в Exim после выпуска 4.10, кратко описаны в этом разделе.

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

deny  domains = +corporate_domains
     !verify  = recipient/callout=no_cache,10s

Если в качестве опции вызова указан random, сначала выполняется проверка с использованием получателя со «случайной» локальной частью. На самом деле это не случайно — это определяется расширением опции callout_random_local_part, которая по умолчанию имеет следующую строку:

$primary_host_name-$tod_epoch-testing

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

Если в качестве опции вызова указан postmaster, за успешной проверкой вызова следует аналогичная проверка для локальной части postmaster в том же домене. Если этот адрес отклонен, вызов не выполняется. Это обеспечивает выполнение требования RFC о том, что postmaster должен быть действительным локальным компонентом во всех доменах электронной почты.

14.9.2 Перенаправление при проверке

Возникает дилемма, когда локальный адрес перенаправляется с помощью алиасинга или переадресации во время проверки: следует ли проверять сами сгенерированные адреса, или для его проверки достаточно успешного расширения исходного адреса? Exim использует следующий прагматичный подход:

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

A.Wol:   awl123
awl23:   :fail: Gone away, no forwarding address

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

14.9.3 Тестирование проверки адреса

Вы можете проверить, как Exim отреагирует на запрос проверки определенного адреса отправителя или получателя, используя опции -bvs или -bv соответственно. Например:

exim -bvs homer@greece.example
exim -bv  virgil@rome.example

Существует разница между -bvs и -bv только в том случае, если ваша конфигурация различает отправителей и получателей при проверке (например, путем использования verify_sender на одном или нескольких роутерах).

Если конфигурация ваших роутеров включает какие-либо тесты значений, связанных с хостом-отправителем, например, проверку содержимого $sender_host_address, вы можете указать значения для теста, используя один из параметров командной строки, имена которых начинаются с -oM (20.2.3).

14.10 Функция local_scan()

Списки управления доступом, запускаемые для RCPT и DATA, предоставляют средства для простой и прямой проверки входящих сообщений. Однако их конструкция не подходит для более сложной проверки, например сканирования содержимого сообщений.

Методы сканирования сообщений, описанные в разделе 5.10, требуют, чтобы сообщение было принято, чтобы его можно было доставить программе сканирования. Однако вы можете организовать сканирование входящих сообщений непосредственно перед их принятием, предоставив собственную функцию local_scan(). Эта функция написана на C и связана с бинарным файлом Exim. Однако описание интерфейса программирования выходит за рамки этой книги; вы можете найти эти детали в справочном руководстве Exim.

14.11 Обработка входящих сообщений

Exim выполняет различные преобразования исходных адресов отправителя и получателя всех сообщений, которые он обрабатывает, а также строк заголовков сообщений. Некоторые из этих изменений являются необязательными и настраиваемыми, тогда как другие всегда имеют место. Вся эта обработка происходит в момент получения сообщения, до того, как оно будет впервые записано в буфер. Перезапись адресов рассматривается в главе 15; остальные изменения описаны здесь.

14.11.1 Resent- строки заголовка

RFC 2822 предусматривает, что строки заголовка, начинающиеся со строки Resent-, добавляются к сообщению, когда оно повторно отправляется первоначальным получателем кому-либо еще. Это заголовки Resent-Date:, Resent-From:, Resent-Sender:, Resent-To:, Resent-Cc:, Resent-Bcc: и Resent-Message-ID:. RFC говорит об этих строках заголовков следующее:

Resent поля носят исключительно информационный характер. Они НЕ ДОЛЖНЫ использоваться при обычной обработке ответов или других подобных автоматических действий над сообщениями.

Это описание довольно расплывчато в том, что касается других действий по обработке, таких как перезапись адреса. Exim обрабатывает строки заголовка Resent-: следующим образом:

14.11.2 Строка UUCP «From»

Сообщения, пришедшие от UUCP (и некоторых других приложений), часто начинаются со строки, содержащей отправителя конверта и метку времени, после слова From и пробела[11]. Ранее в этой главе (14.3.3) мы обсуждали, как эта строка может использоваться доверенным пользователем для предоставления адреса отправителя конверта для локально сгенерированного сообщения.

Для входящих SMTP-сообщений строка UUCP From обычно не распознается. Она синтаксически недействительна в качестве строки заголовка, поэтому обрабатывается как первая строка тела сообщения. Однако, поскольку есть неработающие программы, которые отправляют SMTP-сообщения с начальными строками From, есть опции, позволяющие Exim распознавать их при вводе SMTP. Однако их содержимое всегда игнорируется и удаляется из сообщения.

Для входящего SMTP через TCP/IP параметр ignore_fromline_hosts может быть установлен в список хостов, для которых игнорируется строка From; для SMTP через стандартный ввод и вывод (опция -bs) для параметра ignore_fromline_local должно быть установлено значение true. Эти параметры следует использовать только в крайнем случае, когда необходимо использовать неисправное программное обеспечение для отправки, которое нельзя исправить.

Во всех случаях распознается только одна строка From. Если их несколько, вторая обрабатывается как строка данных, с которой начинается тело сообщения.

  1. Подобная строка также используется в качестве разделителя в файлах почтовых ящиков «формата Беркли». Не путайте ее со строкой заголовка From:.

14.11.3 Строка заголовка From:

Если входящее сообщение не содержит строки заголовка From:, Exim добавляет строку, содержащую адрес отправителя. Это получается из конверта сообщения в случае удаленных сообщений; для локально сгенерированных сообщений имя пользователя и полное имя вызывающего пользователя используются для создания адреса в формате этого примера:

From: Zaphod Beeblebrox <zaphod@end.univ.example>

Полное имя пользователя получается из параметра командной строки -F, если он установлен. В противном случае его можно получить путем поиска вызывающего пользователя и извлечения поля «gecos» из ввода пароля. Если поле «gecos» содержит символ амперсанда, он заменяется логином, первая буква которого преобразуется в верхний регистр, как это принято в ряде операционных систем.

В некоторых средах поле «gecos» используется не только для хранения имени пользователя; оно также может содержать принадлежность к отделу, офис или номер телефона. Параметры gecos_pattern и gecos_name позволяют в таких случаях извлекать только имя пользователя. Когда они установлены, gecos_pattern рассматривается как регулярное выражение, которое должно быть применено к полю, и если оно совпадает, gecos_name расширяется и используется как имя пользователя[12]. Числовые переменные, такие как $1, $2 и т. д., можно использовать в расширении для выбора подполей, соответствующих шаблону. В HP-UX, где обычно встречаются запятые-разделители, можно использовать следующее:

gecos_pattern = ^([^,]*)
gecos_name = $1

Это извлекает все до первой запятой как полное имя пользователя.

Во всех случаях имя пользователя приводится в соответствие с RFC 2822, при необходимости полностью или частично заключаясь в кавычки. Если в имени пользователя появляются символы со значениями больше 127, Exim кодирует их, как описано в RFC 2047, который определяет способ включения не-ASCII-символов в строки заголовков. Однако, если установлен print_topbitchars, эти символы обрабатываются как обычные печатные символы.

Для совместимости с Sendmail, если входящее сообщение, отличное от SMTP, имеет заголовок From:, содержащий только неполное имя входа вызывающего пользователя, он заменяется адресом, содержащим имя входа и полное имя пользователя, как только что было описано.

  1. Перед сопоставлением любой амперсанд в поле «gecos» заменяется именем пользователя, как описано выше.

14.11.4 Строка заголовка Sender:

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

From: god@heaven.com

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

Поведение Exim по умолчанию подходит для многопользовательских систем, что, вероятно, имел в виду автор RFC 822 в 1980-х. Строки заголовков Sender: остаются нетронутыми в сообщениях, поступающих по TCP/IP. Однако для других сообщений, если только они не отправлены доверенным пользователем с помощью параметра командной строки -f или -bs или если для local_sender_retain не задано значение true, все существующие строки заголовка Sender: удаляются.

Для ненадежных вызывающих абонентов проверяется, является ли адрес, указанный в строке заголовка From:, правильным (локальным) отправителем сообщения. В противном случае к сообщению добавляется строка Sender:, содержащая истинный адрес отправителя. Это можно отключить, установив следующее:

local_from_check = false

Тем не менее, отправитель конверта по-прежнему должен быть идентификатором входа в подходящем домене для локально отправленных сообщений.

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

user@example.com
home-user@example.com
work-user@example.com

все могут относиться к одному и тому же пользователю, который может использовать префикс в файле фильтра для автоматического управления почтой. Если вы не хотите, чтобы появление префикса в строке From: вызывало добавление строки Sender:, вы можете установить local_from_prefix. Например:

local_from_prefix = *-

разрешает любой префикс, заканчивающийся дефисом, так что сообщение, содержащее:

From: anything-user@example.com

не приводит к добавлению заголовка Sender:, если user@example.com совпадает с фактическим адресом отправителя, составленным из имени для входа и уточнения домена. Опция local_from_suffix предоставляет те же возможности для суффиксов.

14.11.5 Строки заголовков Bcc:, Cc: и To:

Если Exim вызывается с опцией -t, чтобы взять адреса получателей из строк заголовков локально отправленного сообщения, он удаляет любую строку заголовка Bcc:, которая может существовать (после извлечения ее адресов), если в сообщении нет строк заголовков To: или Cc:, и в этом случае в сообщении остается строка заголовка Bcc: без адресов.

Если Exim вызывается для получения сообщения с адресами получателей, указанными в командной строке, и в сообщении нет строки заголовка Bcc:, To: или Cc:, добавляется пустая строка заголовка Bcc:.

Настаивание на наличии по крайней мере пустой строки Bcc: приводит сообщения в соответствие с RFC 822, хотя это требование фактически было удалено в RFC 2822.

14.11.6 Строки заголовков Return-path:, Envelope-to: и Delivery-date:

Строка заголовка Return-path: определена в RFC как нечто, что MTA должен вставить при окончательной доставке сообщения, чтобы записать адрес отправителя конверта.

Строка заголовка Envelope-to: не является частью стандартного набора заголовков RFC 2822, но Exim может быть сконфигурирован для ее добавления в окончательную доставку сообщения для записи адреса получателя конверта.

Строка заголовка Delivery-date: не является частью стандартного набора заголовков RFC 2822, но Exim может быть сконфигурирован для ее добавления к окончательной доставке сообщения, чтобы записывать время, когда оно было доставлено.

Ни одна из этих трех строк заголовка не должна присутствовать в передаваемых сообщениях, и Exim обычно удаляет все, что находит. Это действие можно отключить, указав любой или все из следующих параметров:

return_path_remove = false
envelope_to_remove = false
delivery_date_remove = false

14.11.7 Строка заголовка Date:

Если сообщение не имеет строки заголовка Date:, Exim добавляет ее, указывая текущую дату и время.

14.11.8 Строка заголовка Message-ID:

Если входящее сообщение не содержит строку заголовка Message-ID:, Exim создает ее и добавляет к сообщению. ID создается из внутреннего ID сообщения Exim, которому предшествует буква E, чтобы убедиться, что оно начинается с буквы, за которым следует @ и имя основного хоста. В этот заголовок можно включить дополнительную информацию, установив параметр message_id_header_text.

14.11.9 Строка заголовка Received:

Заголовок Received: добавляется в начале каждого сообщения, которое обрабатывает Exim. Содержимое этого заголовка определяется параметром received_header_text; Exim автоматически добавляет точку с запятой и отметку времени к сконфигурированной строке. Значение по умолчанию для этой опции:

received_header_text = Received: \
    ${if def:sender_rcvhost {from $sender_rcvhost\n\t}\
    {${if def:sender_ident {from $sender_ident }}\
    ${if def:sender_helo_name { (helo=$sender_helo_name)\n\t}}}}\
    by $primary_hostname \
    ${if def:received_protocol {with $received_protocol}} \
    ${if def:tls_cipher {($tls_cipher)\n\t}}\
    (Exim $version_number)\n\t\
    id $message_id\
    ${if def:received_for {\n\tfor $received_for}}

Ссылка на $tls_cipher опускается, когда Exim не скомпилирован для поддержки шифрования TLS. Использование условных расширений гарантирует, что этот параметр работает как для сообщений, сгенерированных локально, так и для сообщений, полученных с удаленных узлов. Он создает строки заголовка, такие как следующие:

Received: from scrooge.example ([192.168.12.25] ident=root)
        by marley.example with smtp (Exim 4.10)
        id E0tS3Ga-0005C5-00
        for cratchit@dickens.example; Mon, 25 Dec 2000 14:43:44 +0000
Received: from ebenezer by scrooge.example with local (Exim 4.04)
        id E0tS3GW-0005C2-00; Mon, 25 Dec 2000 14:43:41 +0000

Обратите внимание на автоматическое добавление даты и времени в нужном формате.

Глава 15
Перезапись адресов

Есть ряд обстоятельств, при которых адреса в сообщениях изменяются, когда они обрабатываются Exim. Это может относиться как к конвертам сообщений, так и к их заголовкам. Строки заголовка, которые могут быть затронуты: Bcc:, Cc:, From:, Reply-To:, Sender: и To:. Некоторые из этих изменений происходят автоматически, тогда как другие явно настраиваются администратором.

15.1 Автоматическая перезапись

Одним из случаев автоматической перезаписи является добавление домена к неквалифицированному адресу. Эта квалификация применяется к адресам в строках заголовка, а также к адресам в конвертах. Например, если сообщение отправлено на хост, где для qualify_domain установлено значение crete.example, с помощью этой команды:

$ exim daedalus
To: daedalus
...

неполная локальная часть daedalus преобразуется в полный адрес daedalus@crete.example как в конверте, так и в строке заголовка To:. Сообщения, поступающие с других хостов, не должны содержать неполные адреса; вам нужно установить sender_unqualified_hosts и/или recipient_unqualified_hosts, если вы хотите разрешить прием таких сообщений (14.4).

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

To: minos@knossos

можно переписать как:

To: minos@knossos.crete.example

при обнаружении на хосте в домене crete.example. Строго говоря, MTA не должен осуществлять доставку сообщения до тех пор, пока не будут маршрутизированы все его адреса, на случай, если какая-либо из строк заголовка должна быть изменена в результате маршрутизации. В противном случае существует риск отправки разных копий сообщения разным получателям.

Однако выполнение этого на практике может привести к задержке многих доставок в сообщениях на неоправданное количество времени, когда один адрес не может быть немедленно маршрутизирован (например, из-за тайм-аутов DNS). Таким образом, Exim не задерживает другие доставки, когда маршрутизация одного или нескольких адресов отложена. Адреса, которые не могут быть немедленно перенаправлены, обычно относятся к удаленным доменам; поскольку такие адреса обычно не перезаписываются этим процессом, вероятность того, что что-то получится «не так», довольно мала.

15.2 Настроенная перезапись

Некоторые люди считают, что настроенная перезапись — это смертный грех, потому что «MTA не должны вмешиваться в сообщения». Другие считают, что без нее жизнь невозможна. Exim предоставляет возможность; вам не нужно ее использовать. В общем, переписывание адресов с ваших собственных доменов имеет некоторую законность. Перезапись других адресов следует производить только с большой осторожностью и в особых обстоятельствах. Мое собственное мнение состоит в том, что переписывание следует использовать с осторожностью и в основном для «регуляризации» адресов в ваших собственных доменах. Хотя перезапись адресов получателей может использоваться в качестве инструмента маршрутизации, она не предназначена для этой цели, и такое использование перезаписи не рекомендуется.

Есть два часто встречающихся случая, когда используется перезапись адреса, как показано в этих примерах:

Мы кратко объясним, как работают эти правила перезаписи. Два вида перезаписи не являются взаимоисключающими, и очень часто оба они выполняются с использованием обоих правил в конфигурации.

Порядок, в котором перезаписываются адреса в сообщении, не определен, за исключением того, что адрес отправителя конверта всегда перезаписывается перед любыми строками заголовка. Если перезапись адреса в строке заголовка относится к $sender_address, используется перезаписанное значение. Однако нельзя предполагать, что, например, строка заголовка From: всегда перезаписывается перед строкой заголовка To: или наоборот.

Настроенная перезапись адреса может происходить на нескольких этапах обработки сообщения. Перезапись происходит при получении сообщения, но также может происходить при создании нового адреса во время маршрутизации (например, путем создания псевдонимов) и при транспортировке сообщения.

Два разных типа перезаписи адресов могут быть установлены администратором Exim. Их называют general (общим) и in-transport (транспортным) переписыванием. Они действуют аналогичным образом, но в разное время. Общая перезапись применяется ко всем копиям сообщения, в то время как перезапись в процессе транспортировки применяется только к тем копиям сообщения, которые проходят через конкретный транспорт.

15.2.1 Общее переписывание

Общая перезапись определяется набором правил, заданных в разделе файла конфигурации среды выполнения, представленном командой begin rewrite. Каждое правило определяет типы адресов, с которыми оно работает, и Exim применяет правила к каждому адресу, когда впервые встречает его.

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

15.2.2 Перезапись во время транспортировки

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

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

Это позволяет перезаписывать адреса в строках заголовка во время транспортировки (то есть, когда сообщение копируется в место назначения). В отличие от обычной перезаписи адреса конвертов не могут быть перезаписаны этим способом. Однако вы можете переписать отправителя конверта, используя опцию return_path в транспорте, но вы не можете переписать адреса получателей во время транспортировки.

Перезапись в транспорте настраивается путем установки параметра headers_rewrite на транспорте в список правил перезаписи, разделенных двоеточиями. Каждое правило имеет ту же форму, что и одно из общих правил перезаписи, которые применяются при получении сообщения (см. следующий раздел). Например:

headers_rewrite = a@b c@d f : \
                  x@y w@z

изменяет a@b на c@d в строках заголовка From: и x@y на w@z во всех строках заголовков, несущих адрес. Однако перезаписываются только исходные строки заголовка сообщения и те, которые были добавлены системным фильтром. Обратите внимание, что это отличается от обычной перезаписи, которая не применяется к строкам заголовков, добавленным системным фильтром. Если роутер или транспорт добавляет строки заголовков, эта перезапись не затрагивает их.

15.3 Правила перезаписи

Как при общей перезаписи, так и при перезаписи на транспорте весь набор правил применяется к одному адресу за раз. Другими словами, Exim не проходит по правилам один раз, применяя каждое правило к каждому релевантному адресу (что могло бы сработать). Вместо этого он полностью перезаписывает каждый адрес, прежде чем перейти к следующему.

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

Вот очень простое правило перезаписи, которое просто превращает один явный адрес в другой:

ph10@workshop.exim.example P.Hazel@exim. example

Как общее правило перезаписи, это будет занимать отдельную строку и применяться ко всем экземплярам адреса. Согласно правилу в транспорте, это будет значение headers_rewrite:

headers_rewrite = ph10@workshop.exim.example P.Hazel@exim.example

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

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

*@*.exim.example  $1@exim.example
*@exim.example    ${lookup{$1}dbm{/etc/realnames.db}\
                  {$value}fail}@exim.example frsF

Первое правило; удаляет первый компонент доменных имен, которые заканчиваются на .exim.example, а второе правило преобразует локальную часть посредством поиска файлов. Таким образом, адреса в домене exim.example переписываются в два этапа. frsF, который появляется в конце второго правила, представляет собой строку символов флага, которые объясняются в следующем разделе.

15.3.1 Формат правил перезаписи

В общем случае каждое правило перезаписи имеет вид:

<pattern> <replacement> <flags>

Шаблон завершается пробелом и соответствует тем адресам, которые должны быть перезаписаны этим правилом. Флаги представляют собой одиночные символы, некоторые из которых указывают местонахождение адреса (строка заголовка, поле конверта), к которому применяется правило; другие флаги управляют тем, как происходит перезапись. И шаблон, и флаги местоположения адреса должны совпадать, чтобы правило «сработало». Разрешенные форматы шаблонов и флагов описаны позже.

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

15.4 Шаблоны перезаписи

Шаблон в правиле перезаписи обрабатывается как список адресов из одного элемента и может быть любым элементом, который может появиться в списке адресов (18.7).

После сопоставления шаблона перезаписи могут быть установлены числовые переменные ($1, $2 и т. д.) в зависимости от типа совпадения. Их можно использовать в замещающей строке для вставки частей адреса субъекта. Следующие примеры охватывают некоторые из наиболее распространенных случаев:

15.5 Применение правил перезаписи

Если замещающая строка для правила представляет собой одну звездочку, совпадающий адрес не перезаписывается этим правилом, и никакие последующие правила перезаписи не сканируются для поиска адреса. Например:

hatta@lookingglass.example *

указывает, что hatta@lookglass.example никогда не следует перезаписывать.

В противном случае строка замены расширяется; во время раскрытия переменные $local_part и $domain ссылаются на перезаписываемый адрес. Любые буквы в этих переменных сохраняют свой первоначальный регистр; они не строчные.

Расширение должно либо дать полный адрес, либо быть прекращено принудительной ошибкой в элементе поиска или условного расширения. Принудительный сбой заставляет правило перезаписи не изменять адрес; это эквивалентно созданию следующего:

$local_part@$domain

в качестве замены (но немного эффективнее). Любая другая ошибка раскрытия (например, синтаксическая ошибка) приводит к отказу от всей операции перезаписи и записи записи в журнал паники.

15.5.1 Условная перезапись

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

*@*.hitch.example  "S${if !eq {$sender_host_address}{}\
                   {$1@hitch.example}fail}"

Значение $sender_host_address — это пустая строка для сообщений, созданных локально; это правило ограничивает перезапись случаями, когда оно не пусто (то есть случаями, когда сообщение пришло с удаленного хоста). Принудительный сбой приводит к отмене правила перезаписи для сообщений, созданных локально, но последующие правила по-прежнему применяются к адресу. Обратите внимание, что в этом примере для правила необходимо использовать кавычки, поскольку оно содержит символы пробела.

15.5.2 Перезапись, управляемая поиском

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

*@*  ${lookup...

с ключом поиска, полученным из $local_part и $domain. Шаблон соответствует каждому адресу, поэтому поведение полностью контролируется расширением строки замены.

15.6 Флаги перезаписи

Существует несколько различных типов флажков, которые могут появляться в правилах перезаписи:

Таблица 15-1: Флаги для выбора адресов
Флаг Значение
E Переписать все поля конверта
F Переписать поле конверта From
T Переписать поле конверта To
b Переписать строку заголовка Bcc:
c Переписать строку заголовка Cc:
f Переписать строку заголовка From:
h Переписать все строки заголовка
r Переписать строку заголовка Reply-To:
s Переписать строку заголовка Sender:
t Переписать строку заголовка To:

Для правил перезаписи при транспортировке, которые применяются только к строкам заголовков, флаги E, F, S и T не разрешены.

15.6.1 Флаги, указывающие, что перезаписывать

Если и флаговые буквы в таблице 15-1, и флаг S (см. далее в этой главе) отсутствуют, правило перезаписи применяется ко всем строкам заголовков и (для общей перезаписи, но не к перезаписи при транспортировке) к обеим строкам поля отправителя и получателя конверта. В противном случае правило перезаписи используется только при перезаписи адресов из соответствующих источников. Так, в этом примере, который был приведен ранее:

*@*.exim.example  $1@exim.example
*@exim.example    ${lookup{$1}dbm{/etc/realnames.db}\
                  {$value}fail}@exim.example frsF

первое правило применяется ко всем адресам, а второе используется только для строк заголовков From:, Reply-to: и Sender: и поля отправителя конверта (From).

15.6.2 Флаги, управляющие процессом перезаписи

Есть четыре флага, которые управляют тем, как работает процесс перезаписи. Они вступают в силу только при вызове правила. Для этого адрес должен быть правильного типа (соответствовать флагам выбора), а также соответствовать шаблону:

15.6.3 Флаг перезаписи времени SMTP

Флаг перезаписи S указывает правило, которое применяется к входящим адресам в конвертах во время SMTP, как только получена каждая команда MAIL или RCPT и перед любой другой обработкой; даже до проверки синтаксиса. Эта форма правила перезаписи позволяет обрабатывать адреса, которые не соответствуют RFC 2821 (например, «банговые пути» UUCP во входных данных SMTP или неправильно сформированные адреса от неработающих SMTP-клиентов).

Поскольку входные данные для правила перезаписи с флагом S не обязательно должны быть синтаксически допустимым адресом, шаблон должен быть регулярным выражением, а переменные $local_part и $domain недоступны во время расширения строки замены. Шаблон сопоставляется со всем текстом, предоставленным командой MAIL или RCPT, включая любые окружающие угловые скобки, и результат перезаписи заменяет исходный адрес в команде MAIL или RCPT. Например, предположим, что один из ваших локальных SMTP-клиентов неисправен и отправляет некорректные SMTP-команды, такие как:

RCPT TO: internet:ceo@plic.example

В этом адресе есть две ошибки: отсутствие окружающих угловых скобок и наличие нежелательной строки internet: в начале. Очевидно, что лучше всего было бы починить клиент, но иногда это невозможно, по крайней мере, не быстро. Используя флаг перезаписи S, вы можете настроить Exim на исправление таких плохих адресов:

\N^\s*internet:(.*)$ <S1> S

Шаблон регулярного выражения сопоставляет адреса, начинающиеся с internet: (с необязательным начальным пробелом), и обеспечивает захват остатка с помощью круглых скобок. Строка замены заключает захваченную подстроку в угловые скобки, необходимые в SMTP-командах, поэтому результат обрабатывается так, как если бы команда была:

RCPT TO:<ceo@plc.example>

Другой случай, когда этот вид перезаписи полезен, — это взаимодействие с системами, использующими адресацию UUCP bdng-path, в которой адреса имеют форму:

host1!host2!host3...!user

Exim поддерживает только адресацию на основе доменов Интернет, и поэтому не распознает банг пути. Однако в некоторых случаях для преобразования адресов bang-path можно использовать перезапись. Если это делается во время SMTP с использованием флага S, перезаписанные адреса подлежат обычной проверке и проверке ретрансляции, что вам и нужно[1]. Следующие правила преобразуют почтовые пути в более традиционные интернет-адреса во время SMTP:

\N^(?=.*?!) (?!.*?@)(.*)S               $1@bang.path      S
\N^ ([^!]+)!([^%]+)([^@]*)@bang\.path$  $2%$1$3@bang.path SR
\N^(.*)%([^@]*)@bang\.paths             $1@$2             S

Первое правило распознает строки, которые содержат хотя бы один восклицательный знак, но не содержат символов @, и добавляет псевдодомен bang.path, тем самым позволяя двум другим правилам работать с ними. Обычные интернет-адреса, которые содержат восклицательные знаки, не затрагиваются этим изменением.

Второе правило изменяет локальную часть этих специальных адресов, меняя порядок частей и заменяя восклицательные знаки знаками процента. Например, a!b@bang.path становится b%a@bang.path, а a!b!c!d@bang.path становится d%c%b%a@bang.path. Этому правилу нужен флаг R (повтор), потому что при каждом запуске оно обрабатывает только один восклицательный знак. Последнее правило удаляет псевдодомен и заменяет последний знак процента на @.

Используя эти правила, путь из двух частей, такой как a!b, превращается в b@a, а более длинный путь, такой как a!b!c!d, становится d%c%b@a. Эту нотацию иногда называют «процентным взломом» (percent hack), и она использовалась в интернет-адресации для явной маршрутизации почты, хотя и не является стандартом. В настоящее время выходит из употребления.

  1. Поскольку адрес bang-path является синтаксически допустимой локальной частью, вы можете настроить Exim для приема неполных адресов, а затем позже переписать локальные части, содержащие восклицательные знаки. Однако это не очень хорошая идея, так как она игнорирует проверку релея.

15.7 Еще один пример перезаписи

Возможность перезаписи адресов может использоваться по-разному. Чаще всего перезапись используется для удаления локальных имен хостов и преобразования имен для входа в «настоящие имена» в сообщениях, которые отправляются из локальной сети в более широкий Интернет. Ранее мы показали простой способ реализации такой перезаписи:

*@*.exim.example  $1@exim.example
*@exim.example    ${lookup{$1}dbm{/etc/realnames.db}\
                  {$value}fail}@exim.example frsF

Этот пример можно расширить для предоставления дополнительных функций, таких как отклонение сообщений, локальные адреса отправителей которых невозможно распознать. Сначала мы покажем полную конфигурацию, а затем объясним, как она работает. Есть пять правил:

\N^(?>.*)(?<!\.exim\.example)  *
root@*.exim.example "admin@exim.example (root@$1)"  hFwq
*@*.exim.example \
  ${lookup{$local_part@$2}lsearch{/etc/realnames}{$value}\
  {"$1@$2-is-not-known"}}@exim.example  Fq
*@*,exim.example \
  ${lookup{$local_part@$2}lsearch{/etc/realnames}{$value@exim.example}\
  {$sender_address}}  fsrq
*@*.exim.example \
  ${lookup{$local_part@$2}lsearch{/etc/realnames}{$value}{unknown}}\
  @exim.example

Поскольку мы перезаписываем только те адреса, которые заканчиваются на .exim.example, мы можем сэкономить некоторые ресурсы, используя начальное правило, которое распознает другие домены и отказывается от любых попыток их перезаписи:

\N^(?>.*)(?<!\.exim\.example)  *

Шаблон представляет собой регулярное выражение, которое соответствует всем адресам, не заканчивающимся на .exim.example, а одиночная звездочка в качестве строки замены означает «не переписывать этот адрес и больше не сканировать правила». Это означает, что в остальные правила передаются только адреса, оканчивающиеся на .exim.example.

Необходимо некоторое объяснение регулярного выражения. Есть несколько способов написать шаблон для реализации «заканчивается на», но это самый эффективный. \N в начале шаблона не позволяет расширителю строки вносить в него какие-либо изменения. Само регулярное выражение начинается с этой последовательности:

^(?>.*)

что соответствует всей строке. ^ соответствует началу строки, а .* соответствует любому количеству произвольных символов, но, поскольку он заключен в круглые скобки (?>), возврат не допускается, поэтому, достигнув конца строки, текущая точка остается там. Однако сам шаблон не закончен. У нас все еще есть:

(?<!\.exim\.example)

Это отрицательное обратное утверждение; оно проверяет, что символы, непосредственно предшествующие текущей позиции (то есть те, что в конце строки), являются .exim.example. Если оно терпит неудачу, сопоставление с образцом завершается неудачно (поскольку не допускается откат, чтобы попробовать этот тест в любой другой позиции). Если этот тест завершается успешно, сопоставление шаблона завершается успешно, поскольку достигнут конец шаблона.

Более «очевидное» регулярное выражение для проверки того, что строка заканчивается на .exim.example:

\.exim\.examples$

Это способ менее эффективен, поскольку сканирует строку символ за символом в поисках точки; всякий раз, когда ее находит, проверяет, следует ли за ней exim.example и конец строки. Это больше работы, чем одна проверка в конце строки.

Второе правило перезаписи в этом примере обрабатывает почту от root. Такая почта может генерироваться cron или другими системными заданиями на локальных хостах. Хотя обычно это адресовано локальным пользователям, всегда существует вероятность того, что такие пользователи перенаправили свою почту за пределы офиса. Если имеется более одного локального хоста, то при перезаписи root таким же образом, как и имя пользователя для входа в систему, теряется информация о том, root какого локального хоста на самом деле отправил сообщение. Это правило перезаписи является одним из способов сохранения этой информации:

root@*.exim.example "admin@exim.example (root@$1)" hFwq

Шаблон соответствует root на любом из локальных хостов, а флаги hF ограничивают это правило строками заголовков и отправителем конверта. Новый адрес является стандартным, но сохраняет исходное имя хоста в комментарии. Флаг w гарантирует, что комментарий будет сохранен во всех строках заголовков, которые будут перезаписаны, например:

From: Charlie Root <root@host1.exim.example>

переписывается как:

From: admin@exim.example (root@host1)

Это, конечно, позволяет оставить имя локального хоста в сообщении. Сайты, которые боятся скрывать свои локальные имена хостов, не захотят этого делать[2]. Флаг q в этом правиле приводит к прекращению перезаписи после выполнения правила, поэтому последующие правила не применяются к адресам root.

Третье правило переписывает отправителя конверта сообщения, ища адрес без завершающего .exim.example в файле, содержащем такие строки:

jc@hostl:  J.Caesar
jc@host2:  Jiminy.Cricket

который можно назвать /etc/realnames. Другими словами, это позволяет использовать неуникальные локальные части среди локальных хостов. При условии, что этот файл не слишком велик, допускается использование линейного поиска[3]. Шаблон не требует проверки домена, поскольку мы знаем, что правило применяется только к доменам, оканчивающимся на .exim.example, поэтому правило выглядит следующим образом:

*@* ${lookup{$local_part@$2}lsearch{/etc/realnames}{$value}\
  {"$1@$2-is-not-known"}}@exim.example  Fq

Флаг F гарантирует, что это правило применяется только к отправителям конвертов. Если адрес не может быть найден в файле, он перезаписывается в магическую последовательность. Неизвестный адрес отправителя, такой как xxxx@host1.exim.example, превращается в следующий:

"xxxx@host1-is-not-known"@exim.example

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

Четвертое правило переписывает адреса отправителей в строках заголовка From:, Sender: и Reply-To: сообщения. Это почти то же самое, что и предыдущее правило, за исключением того, что неспособность найти исходный адрес здесь не рассматривается как такая серьезная ошибка; неизвестный адрес заменяется адресом отправителя из конверта:

*@* ${lookup{$local_part@$2}lsearch{/etc/realnames}\
  {$value@exim.example}{$sender_address}}  fsrq

Exim всегда перезаписывает адрес отправителя конверта перед перезаписью строк заголовка[4], поэтому мы знаем, что адрес отправителя был проверен.

Последнее правило не имеет флагов, и поэтому теоретически применяется ко всем адресам, но из-за использования флага q в предыдущих правилах оно всегда применяется только к получателям конверта и адресам в строках заголовка получателя To: и Cc:Bcc: если есть). Оно заменяет в нашем домене локальные части из списка реальных имён, заменяя на unknown те, которые не может найти:

*@* ${lookup{$local_part@$2}lsearch{/etc/realnames}{$value}{unknown}}\
  @exim.example
  1. Вероятно, они также захотят удалить файл строки заголовка Received:, которые показывают имена локальных хостов.

  2. Как только он превышает сотню строк или около того, его следует преобразовать в какой-либо вид индексированного поиска, например, в файл cdb или DBM (см. главу 16).

  3. Это единственный указанный порядок перезаписей.

15.8 Тестирование правил перезаписи

Общая конфигурация перезаписи Exim'а может быть протестирована опцией командной строки -brw. Она принимает адрес (который может быть полным адресом RFC 2822) в качестве аргумента. Результатом является список того, как адрес будет преобразован общими правилами перезаписи для каждого из различных мест, где он может появиться (то есть для каждой отдельной строки заголовка и для полей отправителя и получателя конверта). Например:

exim -brw ph10@workshop.exim.example

может произвести вывод:

  sender: Philip.Hazel@exim.example
    from: Philip.Hazel@exim.example
      to: ph10@workshop.exim.example
      cc: ph10@workshop.exim.example
     bсc: ph10@workshop.exim.example
reply-to: Philip.Hazel@exim.example
env-from: Philip.Hazel@exim.example
  env-to: ph10@workshop.exim.example

который показывает, что для этого адреса была настроена общая перезапись, когда он используется в любом из исходных полей, но не когда он появляется в качестве адреса получателя. Если для каких-либо правил перезаписи установлен флаг S, к выходным данным добавляется еще одна строка, показывающая перезапись, которая должна произойти во время SMTP.

Глава 16
Поиск файлов и баз данных

Мы представили и дали краткие объяснения того, как Exim может быть сконфигурирован для поиска данных в файлах и базах данных в предыдущих главах. Поиски дают вам гибкость в том, как вы храните данные, управляющие поведением Exim. Они также позволяют Exim использовать общие общекорпоративные базы данных, которые можно использовать совместно с другими программами. Поиски могут использоваться в нескольких различных типах элементов конфигурации, но в каждом случае они работают одинаково. В этой главе подробно рассматриваются лежащие в основе механизмы поиска; в книге есть много примеров использования поиска.

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

Существует несколько различных типов поиска, и каждый реализуется отдельным модулем кода, который включается в Exim только в том случае, если он запрашивается при сборке двоичного файла. Это позволяет легко добавлять новые виды поиска, в то же время не требуя, чтобы каждый бинарный файл Exim включал все возможные варианты поиска. Конфигурация времени сборки по умолчанию включает только поиск lsearch и dbm, поэтому, если вы хотите использовать любой другой вид, вы должны убедиться, что Exim был собран с их включением. В большинстве случаев вам также потребуется установить какое-либо другое программное обеспечение (например, определенный пакет базы данных).

Есть два разных способа, которыми основная часть Exim может вызвать модуль поиска, но каждый отдельный модуль использует только один из них. Это известно как стиль (style) поиска.

Чтобы выполнить поиск, Exim передает необходимые данные соответствующему модулю поиска и получает взамен строку данных, которые были просмотрены, или указание на то, что поиск не удался. Каждый поиск, даже когда он закодирован полностью внутри самого Exim, рассматривается как «черный ящик», внутренности которого не видны остальной части программы. Это показано на рис. 16-1.

Рисунок 16-1: Поиск файлов и баз данных

Нет никаких ограничений на то, где можно использовать различные виды поиска. Любой вид поиска может использоваться везде, где разрешен поиск.

  1. Это упрощение; на самом деле есть два особых случая: параметры domains и local_parts в роутерах (и условия ACL с аналогичным названием), где данные сохраняются для последующего использования.

16.1 Типы поиска с одним ключом

Поиск с одним ключом обеспечивает способ поиска набора данных, состоящего из пар (ключ, значение), где ключи представляют собой фиксированные строки. Такие данные часто отображаются в виде последовательности строк с ключом в начале каждой строки, отделенных от данных двоеточием, даже если в процессе работы данные фактически хранятся каким-либо другим способом, например, в картах (maps) NIS. Например:

root:        postmaster@simple.example,
postmaster:  simon@simple.example

Файл, содержащий данные в этом формате, фактически можно искать напрямую с помощью типа поиска 1search, который немного обобщает формат. В файле выполняется линейный поиск строки, начинающейся с ключа, который завершается двоеточием, пробелом или концом строки. Пробелы между ключом и двоеточием разрешены (и игнорируются). Оставшаяся часть строки с удаленными начальными и конечными пробелами — это данные. Это можно продолжить на последующие строки, начав их с любого количества пробелов, но в данных на таком стыке сохраняется только один символ пробела. Если данные начинаются с двоеточия, ключ должен заканчиваться двоеточием, а не пробелом, как в следующем примере:

exuser:  :fail: This person has gone away.

Пустые строки и строки, начинающиеся с #, игнорируются, даже если они находятся в середине элемента. Это традиционный формат файлов псевдонимов, и в большинстве систем пример можно увидеть в /etc/aliases. Ключи в файлах 1searched представляют собой буквенные строки и никак не интерпретируются.

16.1.1 Файлы DBM

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

Самый распространенный такой формат называется DBM. В большинстве современных Unix-подобных операционных систем стандартно установлена библиотека DBM, хотя это не относится к некоторым более старым системам. Двумя наиболее распространенными библиотеками DBM являются ndbm (стандартная для Solaris и IRIX) и Berkeley DB версии 2, 3 или 4 (стандартная для нескольких свободных операционных систем)[2]. Exim поддерживает оба из них, а также более старую версию 1 Berkeley DB, gdbm и tdb. Выбор библиотеки DBM для использования делается при сборке Exim.

Поскольку DBM широко используется, Exim поставляется с утилитой exim_dbmbuild, которая создает файл DBM (или файлы — некоторые библиотеки DBM используют более одного файла) из файла в традиционном формате псевдонимов. Например:

exim_dbmbuild /etc/aliases /etc/aliases.db

создает DBM-версию файла системных псевдонимов и называет ее /etc/aliases.db. Затем это можно использовать, изменив тип поиска и имя файла в роутере, который обрабатывает псевдонимы, чтобы вместо линейного поиска теперь использовался поиск по ключу в DBM:

system_aliases:
  driver = redirect
  data = ${lookup{$local_part}dbm{/etc/aliases.db}}
  ...

Одна из сложностей при реализации поиска DBM заключается в том, включают ли ключевые строки завершающий двоичный нулевой байт или нет. Как сам Exim, так и утилита exim_dbmbuild включают завершающий ноль. Однако, если вы используете какие-либо другие средства создания файлов DBM (например, функции DBM в Perl), вы можете получить файлы, в ключах которых нет этого дополнительного символа. Exim может читать такие файлы, используя тип поиска dbmnz вместо dbm.

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

  1. Информацию о Berkeley DB можно найти на http://www.sleepycat.com.

16.1.2 Файлы cdb

Тип поиска cdb ищет файл постоянной базы данных[3]. Формат cdb предназначен для индексированных файлов, которые часто читаются и никогда не обновляются, за исключением полного воссоздания. Таким образом, он особенно подходит для больших файлов, содержащих псевдонимы или другие индексированные данные, на которые ссылается MTA.

Дистрибутив cdb не нужен для сборки Exim с поддержкой cdb, потому что код для чтения файлов cdb включен непосредственно в сам Exim. Тем не менее, Exim не предоставляет никаких средств для создания или тестирования файлов cdb, потому что они доступны в дистрибутиве cdb. Обычный способ построить файл cdb из «плоского» файла — запустить команду следующего вида:

cdbmake-12 /etc/aliases.cdb /etc/aliases.tmp < /etc/aliases

Для этого используется утилита, входящая в состав дистрибутива cdb. Однако он работает только с входными файлами, в которых есть пробелы, разделяющие ключи и данные (двоеточие специально не обрабатывается), и нет строк продолжения, поэтому обычные файлы псевдонимов могут сначала нуждаться в редактировании. Команда cdbmake-12 сначала преобразует плоский файл в формат, содержащий длину ключа и данных в начале каждой строки. Затем он передает эти данные команде cdbmake, которая использует их для создания файла cdb во временном файле, имя которого является вторым аргументом. В случае успеха временный файл переименовывается в первый аргумент.

  1. Информацию о cdb можно найти по адресу http://www.pobox.com/~djb/cdb.html.

16.1.3 Карты NIS

Тип поиска nis задает вызов NIS, используя имя файла в качестве имени карты NIS, в которой нужно искать ключ[4]. Exim не распознает псевдонимы имен карт NIS; всегда должно использоваться полное имя, как в этом роутере:

system_aliases:
  driver = redirect
  data = ${lookup{$local_part}nis{mail.aliases}}
  ...
  1. Завершающий ноль обычно исключается из ключа, но существует вариант, называемый nis0, который включает в ключ завершающий двоичный ноль.

16.1.4 Поиск в каталоге

Тип поиска dsearch ищет в каталоге файл, имя которого соответствует ключу. Это может быть полезно в конфигурациях для виртуальных доменов. Например, ранее мы обсуждали следующий роутер виртуального домена (5.2):

virtuals:
  driver = redirect
  domains = cdb;/etc/virtuals
  data = ${lookup{$local_part}lsearch{/etc/$domain.aliases}}
  no_more

Это требует обслуживания отдельного файла (/etc/virtuals), содержащего список виртуальных доменов. Если вместо этого мы поместим файлы псевдонимов в отдельный каталог, назвав каждый из них в соответствии с его доменом, мы можем использовать поиск dsearch для поиска в этом каталоге и, таким образом, избежать необходимости поддерживать отдельный список доменов. Следующий роутер демонстрирует этот подход:

virtuals:
  driver = redirect
  domains = dsearch;/etc/valiases
  data = ${lookup{$local_part}lsearch{/etc/valiases/$domain}}
  no_more

Поиск dsearch в опции domains заставляет Exim искать в каталоге /etc/valiases файл, имя которого совпадает с доменом адреса, который он обрабатывает. Если файл найден, то есть совпадение и роутер запускается. Таким образом, существование виртуального домена теперь зависит только от существования его файла псевдонимов.

16.2 Типы поиска в стиле запроса

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

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

16.2.1 Цитирование данных поиска

Когда данные из входящего сообщения включаются в поиск в стиле запроса, специальные символы в данных могут вызывать синтаксические ошибки в запросе. Например, запрос NIS+, содержащий:

[name=$local_part]

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

[name="$local_part"]

но это оставляет проблему двойной кавычки в данных. Правило для NIS+ заключается в том, что двойные кавычки должны быть удвоены. Другие типы поиска имеют другие правила, и, чтобы справиться с различными требованиями, для каждого поиска в стиле запроса предоставляется оператор раскрытия, который заключает в кавычки в соответствии с правилами поиска. Например, безопасный способ написать запрос NIS+:

[name="${quote_nisplus:$local_part}"]

Оператор кавычек может использоваться для всех типов поиска, но не имеет никакого эффекта для поиска по одному ключу, поскольку в их ключевых строках кавычки никогда не требуются.

16.2.2 NIS+

Хотя NIS+ не стал таким популярным, как NIS, тем не менее, он используется на некоторых сайтах. Используя жаргон NIS+, запрос состоит из проиндексированного имени (indexed name), за которым следует необязательное двоеточие и имя поля. Если имя поля включено, результатом успешного запроса является содержимое именованного поля; если имя поля не включено, результат состоит из конкатенации пар field-name=field-value, разделенных пробелами. Пустые значения и значения, содержащие пробелы, заключаются в кавычки. Например, следующий запрос:

[name=mg1456],passwd.org_dir

может вернуть строку:

name=mg1456 passwd="" uid=999 gid=999 gcos="Martin Guerre"
home=/home/mg1456 shell=/bin/bash shadow=""

(разделено здесь на две строки, чтобы поместиться на странице), тогда как:

[name=mg1456],passwd.org_dir:gcos

просто вернул бы поле gcos как:

Martin Guerre

без кавычек. Поиск NIS+ завершается ошибкой, если NIS+ возвращает более одной записи таблицы для заданного проиндексированного имени. В следующем примере показан роутер, использующий NIS+ для обработки псевдонимов:

system_aliases:
  driver = redirect
  data = ${lookup nisplus \
    {[name="${quote_nisplus:$local_part}"],aliases.org_dir:address}}

Этот запрос предполагает наличие таблицы NIS+ с именем aliases.org_dir, содержащей по крайней мере два поля: name и address.

16.2.3 LDAP

Все более популярным протоколом для доступа к базам данных, содержащим информацию о пользователях, является LDAP. Exim поддерживает запросы LDAP в форме URL, как определено в RFC 2255. Например, в конфигурации роутера с псевдонимом могут быть следующие настройки:

system_aliases:
  driver = redirect
  data = ${lookup ldap {ldap:///\
            cn=${quote_ldap:$local_part},o=University%20of%20Cambridge,\
            c=UK?mailbox?base?}}

При этом выполняется поиск записи, поле cn которой является локальной частью, и извлекается поле ее почтового ящика. В этом примере не указывается, какой сервер LDAP запрашивать, но можно указать конкретный сервер LDAP, начав запрос с:

ldap://hostname:port/...

Если порт (и предыдущее двоеточие) опущены, используется стандартный порт LDAP (389).

Если в запросе не указан сервер, список серверов по умолчанию берется из параметра конфигурации ldap_default_servers. Он предоставляет список серверов, разделенных двоеточием, которые пробуются по очереди, пока один из них не обработает запрос или не произойдет серьезная ошибка. Успешная обработка либо возвращает запрошенные данные, либо указывает, что они не существуют. Серьезные ошибки связаны с синтаксическими ошибками или поиском нескольких значений, когда ожидается только одно значение. Ошибки, которые вызывают попытку следующего сервера, — это сбои соединения, сбои связывания и тайм-ауты. Другими словами, ожидается, что серверы в списке будут содержать идентичные данные, а более поздние будут действовать как резервные копии для более ранних.

Для каждого имени сервера в списке может быть указан номер порта. Стандартным способом указания хоста и порта является использование разделителя двоеточия (RFC 1738). Поскольку ldap_default_servers представляет собой список, разделенный двоеточием, такие двоеточия должны быть удвоены. Например:

ldap_default_servers = \
  ldapl.example.com::145 : ldap2.example.com

Если ldap_default_servers не установлен, URL-адрес без имени сервера передается в библиотеку LDAP без имени сервера, и используется библиотека по умолчанию (обычно локальный хост).

Синтаксис URL-адреса LDAP не позволяет передавать на сервер аутентификационную и другую управляющую информацию. Чтобы сделать это возможным, URL-адресу в запросе LDAP может предшествовать любое количество настроек name=value, разделенных пробелами. Если значение содержит пробелы, оно должно быть заключено в двойные кавычки. Распознаются следующие имена:

user
Задает Distinguished Name (отличительное имя) для аутентификации привязки LDAP.
pass
Задает пароль для аутентификации привязки LDAP.
size
Устанавливает ограничение на количество возвращаемых записей.
time
Устанавливает максимальное время ожидания запроса.

Значения могут быть даны в любом порядке. Вот параметр data предыдущего примера с добавленными данными аутентификации:

data = ${lookup ldap {\
          user="cn=admin,o=University of Cambridge, c=UK" \
          pass = secret \
          ldap:///\
          cn=${quote_ldap:$local_part},o=University%20of%20Cambridge,\
          c=UK?mailbox?base?}}

Проблема с размещением пароля непосредственно в таком запросе заключается в том, что значения настроек конфигурации Exim'а могут быть получены с помощью опции командной строки -bP, так что любой пользователь системы, который может запустить Exim, может видеть эту информацию. Вы можете предотвратить это, поместив слово hide перед настройкой параметра:

hide data = ${lookup ldap {\
     ...

Когда присутствует hide, только пользователи с правами администратора могут извлечь значение, используя -bP. Другой способ сохранить пароль в секрете — поместить его в отдельный файл, доступный только пользователю Exim, и использовать элемент расширения readfile для получения его значения.

Механизм аутентификации LDAP можно использовать для проверки паролей как части аутентификации SMTP, используя условие расширения 1dapauth (17.7.5).

16.2.4 Данные, возвращаемые поиском LDAP

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

Вы можете управлять тем, что произойдет, если будет найдено более одной записи, путем изменения типа поиска. Существует три типа поиска LDAP; они ведут себя по-разному при обработке результатов запроса:

ldap

Этот тип требует, чтобы результат содержал только одну запись; если их больше, возвращается ошибка. Однако из записи может быть взято более одного значения атрибута.

ldapdn

Этот тип также требует, чтобы результат содержал только одну запись, но возвращается Distinguished Name, а не какие-либо значения атрибутов.

ldapm

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

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

ldap:///o=base?attr1,attr2?sub?(uid=fred)

ldap:///o=base?attrl,attr2?sub? (uid=фред)

может дать:

attr1="value one" attr2="value2"

Если вы не укажете какие-либо атрибуты в поиске, этот же формат будет использоваться для всех атрибутов в записи. Например:

ldap:///o=base??sub?(uid=fred)

может дать:

objectClass="top" attr1="value one" attr2="value2"

Оператор extract в расширении строк можно использовать для выделения отдельных полей из таких данных.

16.2.5 MySQL, PostgreSQL и Oracle

MySQL, PostgreSQL и Oracle — это пакеты баз данных, чьи запросы выражаются на языке SQL. Первые два являются программным обеспечением с открытым исходным кодом. Обработка псевдонимов с использованием MySQL может быть настроена следующим образом:

system_aliases:
  driver = redirect
  data = ${lookup mysql \
           {select mailbox from userdata \
           where id='${quote_mysql:$local_part}'}}

В этом примере предполагается существование таблицы userdata с полями mailbox и id. Конечно, вы можете использовать любые имена таблиц и полей, которые вам нравятся. Для PostgreSQL или Oracle конфигурация идентична, за исключением того, что mysql заменяется соответственно pgsql или oracle, где бы он ни появлялся.

Если результат запроса содержит более одного поля, возвращаются данные для каждого поля в строке, которым предшествует его имя, поэтому результат следующего запроса:

select home,name from userdata where id=’ph10'

вернет, например:

home=/home/ph10 name="Philip Hazel"

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

Если результат запроса содержит только одно поле, значение возвращается дословно, без имени поля, например:

Philip Hazel

Если результат запроса дает более одной строки, все они объединяются с новой строкой между данными для каждой строки.

Прежде чем Exim сможет использовать поиск MySQL, PostgreSQL или Oracle, ему должно быть сообщено, где находятся соответствующие серверы. Нет сервера по умолчанию, как для LDAP. Exim также должен знать, какое имя пользователя и пароль использовать при подключении к серверу и в какой базе данных выполнять поиск. Это делается путем настройки mysql_servers, pgsql_servers или oracle_servers (соответственно) на список данных, разделенных двоеточием, для каждого сервера. Каждый элемент содержит имя хоста, имя базы данных, имя пользователя и пароль, разделенные косой чертой. В этом примере перечислены два сервера:

hide pgsql_servers = localhost/userdb/root/secret:\
                     otherhost/userdb/root/othersecret

Обратите внимание на использование префикса hide для защиты значения переменной. Когда присутствует скрытие, только пользователи с правами администратора могут извлечь значение, используя -bP. Для каждого запроса серверы проверяются по порядку, пока соединение и запрос не будут успешными. Хост может быть указан как <name>:<port>, но поскольку это список, разделенный двоеточиями, двоеточие должно быть удвоено.

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

hide mysql_servers = localhost//root/secret

В этом случае идентификатор базы данных должен быть указан в каждом запросе. Например:

select mailbox from userdb.userdata where ...

это запрос, который делает выборку из таблицы с именем userdata в базе данных с именем userdb. Это средство недоступно в PostgreSQL или Oracle.

16.2.6 Запросы DNS

Прямой доступ к DNS доступен в конфигурации Exim через поиск в стиле запроса, который называется dnsdb. Запрос состоит из имени типа записи DNS и доменного имени DNS, разделенных знаком равенства. Результатом поиска является правая часть всех найденных записей DNS, разделенных символами новой строки, если их несколько, в том порядке, в котором они были возвращены резолвером DNS. Например, эта строка заменяется на хосты MX для домена a.b.example:

${lookup dnsdb{mx=a.b.example}{$value}}

Поддерживаемые типы DNS: A (IPv4-адрес), AAAA (IPv6-адрес, доступный, когда Exim скомпилирован с поддержкой IPv6), CNAME (каноническое имя), MX (почтовый шлюз), NS (сервер имен), PTR (указатель) и TXT (текст). Если выбран тип MX, данные для каждой записи состоят из значения предпочтения и имени хоста, разделенных пробелом. Когда тип PTR, адрес должен быть указан как обычно; он внутренне конвертируется в необходимый формат in-addr.arpa (IPv4) или ip6.arpa (IPv6). Например:

${lookup dnsdb{ptr=192.168.4.5}{$value}}

Если тип опущен, по умолчанию используется TXT для обратной совместимости с более ранними версиями Exim.

16.2.7 Запросы Whoson

Whoson[5] — это предлагаемый интернет-протокол, который позволяет серверным программам проверять, выделен ли в настоящее время конкретный динамически выделяемый IP-адрес известному (доверенному) пользователю, и, при необходимости, получать личность указанного пользователя. В Exim'е это может быть использовано для реализации проверки "POP before SMTP", используя операторы ACL, такие как следующие:

require condition = \
  ${lookup whoson {$sender_host_address}{yes}{no}}

Запрос состоит из одного IP-адреса. Если поиск завершается успешно, возвращаемое значение является именем аутентифицированного пользователя. Если IP-адрес в настоящее время не назначен известному пользователю, поиск завершается ошибкой.

  1. См. http://whoson.sourceforge.net.

16.3 Временные ошибки поиска

Функции поиска могут возвращать временные коды ошибок, если поиск не может быть завершен. Например, сервер базы данных может быть недоступен. Когда это происходит на транспорте или роутере, доставка сообщения откладывается, как и в случае любой другой временной ошибки. В других случаях Exim генерирует временную ошибку, если это возможно. Не рекомендуется использовать поиск, который может откладывать важные данные конфигурации, такие как список локальных доменов в ACL.

16.4 Значения по умолчанию при поиске по одному ключу

В этом контексте «значение по умолчанию» “default value) — это значение, указанное администратором для использования вместо результата поиска, если поиск не может найти какие-либо данные. Этот раздел относится только к поиску с одним ключом; для поиска в стиле запроса обычно есть некоторая функция в языке запросов, которая может предоставить аналогичные возможности.

Если к имени типа поиска с одним ключом (например, lsearch*) добавляется звездочка и первоначальный поиск завершается неудачно, ключ, состоящий из буквальной строки *, ищется в файле, чтобы предоставить значение по умолчанию.

Если к имени типа поиска по одному ключу добавляется *@ (например, dbm*@), и первоначальный поиск завершается неудачей, а ключ содержит символ @, выполняется повторный поиск, при этом все, что предшествует последнему @ в ключе, заменяется на *. Если второй поиск терпит неудачу (или не выполняется из-за отсутствия @ в ключе), * ищется как окончательное значение по умолчанию.

Например, если исходный ключ jim@domain1.example, тип поиска с одним ключом, такой как cdb*@, ищет следующие ключи:

jim@domain1.example
*@domain1.example
*

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

16.5 Частичное совпадение при поиске по одному ключу

Обычно поиск по одному ключу ищет в файле точное совпадение с заданным ключом. Однако в ряде ситуаций, когда выполняется поиск доменов, полезно иметь возможность выполнять частичное сопоставление, когда только конечные компоненты ключа поиска соответствуют ключу в файле. Предположим, например, что вы хотите сопоставить все домены, оканчивающиеся на date.example. Во-первых, вы должны создать специальный ключ:

*.dates.example

в файле. Ни один нормальный поиск домена не может найти этот элемент, потому что домены не могут содержать звездочки. Однако, если вы добавите строку partial- в начало поиска с одним ключом, Exim будет вести себя по-другому. Например, в ACL у вас может быть следующее:

accept domains = partial-lsearch;/etc/relay/domains

Когда указано partial-, Exim сначала ищет исходный ключ; если это не удается, в начало ключа добавляется компонент звездочки, и он снова ищется. Если это не удается, Exim начинает удалять компоненты, разделенные точками, из начала ключа, один за другим, и добавлять компонент звездочки впереди того, что осталось.

По умолчанию требуется минимум два компонента, отличных от звездочки. Если исходный ключ 2250.future.dates.example, Exim выполняет следующие поиски в следующем порядке:

2250.future.dates.example
*.2250.future.dates.example
*.future.dates.example
*.dates.example

Как только один ключ в последовательности совпадает с ключом в файле, поиск завершается. Минимальное количество компонентов можно настроить, включив число перед дефисом в типе поиска. Например, partial3-lsearch указывает минимум три компонента без звездочки в измененных ключах. Если используется partial0-, исходный ключ укорачивается вплоть до нулевой строки, а окончательный поиск выполняется только для звездочки.

Если тип поиска заканчивается на * или *@ (16.4), поиск окончательного значения по умолчанию, которое это подразумевает, происходит после того, как все частичные поиски завершились неудачей. Если указано partial0-, добавление * к типу поиска не имеет никакого эффекта, поскольку ключ * уже включен в последовательность частичных поисков.

Использование * в частичном совпадении отличается от его использования в качестве подстановочного знака в списках доменов и т.п., где он просто означает «любую последовательность символов». Частичное совпадение работает только с компонентами, разделенными точками, и после звездочки в ключе темы частичного совпадения всегда должна следовать точка. Например, вы не можете использовать следующее:

*key.example

в качестве ключа в файле, чтобы соответствовать частичным поискам donkey.example и monkey.example.

16.6 Кэширование поиска

Процесс Exim кэширует самый последний результат поиска для каждого файла для типов поиска с одним ключом и держит соответствующие файлы открытыми. В некоторых типах конфигурации это может привести к тому, что многие файлы будут оставаться открытыми для сообщений со многими получателями. Чтобы избежать превышения лимита операционной системы на количество одновременно открытых файлов, Exim закрывает последний использованный файл, когда ему нужно открыть больше файлов, чем его собственный внутренний лимит, который можно изменить с помощью опции lookup_open_max. Для поиска в стиле запросов для каждого процесса Exim сохраняется один кэш данных для каждого типа поиска.

Глава 17
Раскрываемые строки

Комбинация раскрытий и поиска позволяет настраивать Exim многими различными способами. Если вы хотите изучить эти различные возможности, вам нужно понять, что могут сделать для вас раскрытие строк. Ряд примеров рассмотрен в предыдущих главах; эта глава содержит полное объяснение механизма и описания всех различных элементов раскрытия. Справочная сводка раскрытия строки, включая список всех переменных раскрытия, приведена в приложении А.

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

Before-${substr_4_2:$local_part}-After

раскрыватель копирует начальную подстроку Before-, затем обрабатывает элемент раскрытия ${substr_4_2:$local_part} для получения следующей части результата и, наконец, добавляет подстроку -After в конце.

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

17.1 Экранирование литеральных подстрок

Чтобы сделать возможным включение буквенных символов доллара и фигурных скобок в выходные данные раскрытия, обратная косая черта обрабатывается раскрывателем как escape-символ. Распознается ряд специальных последовательностей, таких как \n для новой строки[1]. Если за обратной косой чертой следует какой-либо другой символ, он рассматривается как литерал без особого значения. Таким образом, буквальный знак доллара вводится как \$, а буквальный обратный слэш — как \\.

Экранирование отдельных символов может быть утомительным и подверженным ошибкам, если строка содержит много буквальных долларов или обратную косую черту. В этом случае обычно проще использовать специальную управляющую последовательность \N. Это приводит к буквальному копированию последующих символов до следующего вхождения \N. Например, раскрытие \NA$B\C\N — это A$B\C. Эта функция особенно полезна для регулярных выражений, где большинство буквенных символов доллара и обратной косой черты встречаются в раскрытых строках. Например, регулярное выражение:

^\d{8}@example\.com$

соответствует адресу электронной почты, где локальная часть состоит ровно из восьми цифр, а домен — example.com. Предположим, вы хотите поместить почтовые ящики таких пользователей в специальный каталог. Тогда как почтовый ящик обычного пользователя — это:

/var/mail/$local_part

вы хотите настроить, чтобы почтовые ящики этой группы пользователей были:

/var/mail/special/$local_part

Вы можете использовать такое значение для опции file в транспорте appendfile:

file = /var/mail/\
  ${if match {$local_part}{^\d{8}@example\.com$}\
  {special/}}$local_part

Детали того, как работает элемент раскрытия if, описаны позже, в разделе об условиях, но идея состоит в том, чтобы сопоставить локальную часть с регулярным выражением и, если она совпадает, вставить special/ в имя файла.

Однако установка этого параметра не работает, так как при раскрытии строки доллар и обратная косая черта, являющиеся частью регулярного выражения, интерпретируются раскрывателем до того, как он попытается найти соответствие регулярному выражению, и возникает ошибка, поскольку $} недействительный элемент раскрытия. Наиболее удобное решение выглядит следующим образом:

file = /var/mail/\
  ${if match {$local_part}{\N^\d{8}@example\.com$\N}\
  {special/}}$local_part

Это «защищает» регулярное выражение от раскрытия, заключая его между двумя экземплярами \N. Альтернативой является использование отдельных экранирований следующим образом:

file = /var/mail/\
  ${if match {$local_part}{^\\d{8}@example\\.com\$}\
  {special/}}$local_part

где перед каждым долларом и обратной косой чертой в регулярном выражении вставлена дополнительная обратная косая черта[2].

  1. Это точно такой же набор, как те, которые распознаются кодом чтения строк.

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

17.2 Замена переменной

Мы много раз используем переменную $local_part в примерах конфигурации, а также упоминаем несколько других переменных. На самом деле существует большое количество переменных, содержащих данные, которые могут быть использованы в конфигурационных файлах, а также в системных и пользовательских фильтрах. Полный список приведен в приложении А.

В большинстве случаев нам удавалось сослаться на $local_part, указав выражение:

$local_part

в настройках конфигурации, которые мы использовали. Этот формат удобен при условии, что за именем переменной не следует ни буква, ни цифра, ни знак подчеркивания. Если это так, альтернативный синтаксис:

${local_part}

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

17.3 Вставка заголовка

Содержимое конкретной строки заголовка сообщения можно вставить в строку с помощью элемента вида:

$header_from:

Вместо $header можно использовать аббревиатуру $h, а имена заголовков не чувствительны к регистру. В этом примере вставляется содержимое строки заголовка From:. Это похоже на вставку значения переменной, но есть несколько важных отличий:

На содержимое строк заголовков чаще всего ссылаются из файлов фильтров (см. главу 10) в таких командах, как:

if "$h_subject:" contains "Make money fast" then ...

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

Если имеется более одной строки заголовка с одинаковым именем (обычно для заголовков Received:), они объединяются и вставляются вместе до максимальной длины 64 КБ. В каждом соединении вставляется новая строка. Кроме того, для тех, которые заведомо содержат списки адресов (например, To: и Cc:), также ставится запятая[4].

  1. Любые печатные символы, кроме двоеточия и пробела, разрешены RFC 2822.

  2. RFC 2822 указывает, что должен быть только один экземпляр таких строк заголовков, но это правило часто игнорируется.

17.4 Операции над подстроками

Ряд элементов раскрытия выполняют некоторую операцию над частью строки раскрытия, предварительно раскрыв ее самостоятельно. Все они имеют вид:

${<operator-name>: <substring>}

В некоторых случаях за именем оператора следует одно или несколько значений аргумента, разделенных символом подчеркивания. Подстрока начинается сразу после двоеточия и может иметь значительные начальные и/или конечные пробелы. В реальной конфигурации он всегда содержит хотя бы один элемент раскрытия; нет смысла писать литеральную строку для операции, потому что вы можете выполнить операцию самостоятельно и вместо этого записать результат.

17.4.1 Извлечение начальной части подстроки

Рассмотрим эту общую настройку параметра file в транспорте appendfile:

file = /var/mail/$local_part

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

file = /var/mail/${length_1:$local_part}/$local_part

которая вставляет другой уровень каталога. Имя промежуточного каталога вычисляется из локальной части с помощью оператора раскрытия length, который извлекает из своего аргумента начальную подстроку, предварительно раскрыв ее. Предположим, что локальная часть — это caesar. При встрече со следующим:

${length_1:$local_part}

в процессе раскрытия Exim сначала раскрывает аргумент оператора, преобразуя элемент в:

${length_1:caesar}

Затем оператор length_1 извлекает первый символ caesar, прежде чем продолжить работу с остальной частью строки, так что окончательное имя почтового ящика становится таким:

/var/mail/c/caesar

Предполагая, что все локальные части начинаются с буквы, это распределяет почтовые ящики пользователей по 26 подкаталогам. Если бы этого было недостаточно, можно было бы использовать больше символов из локальной части:

file = /var/mail/${length_2:$local_part}/$local_part

Теоретически это создает 676 (26x26) подкаталогов. Проблема с этим подходом заключается в том, что буквы в именах пользователей обычно неравномерно распределены по алфавиту, поэтому некоторые подкаталоги используются более активно, чем другие. В разделе 17.4.3 мы обсудим лучший способ справиться с этим.

17.4.2 Извлечение произвольной части подстроки

Помимо length, в Exim есть оператор substr, который можно использовать для извлечения произвольных подстрок. Например:

${substr_3_2:$local_part}

извлекает два символа, начиная со смещения 3 в локальной части. Первый символ в строке имеет нулевое смещение. Предположим, вы хотите по какой-то причине включить название месяца в имя файла. Переменная $tod_full содержит текущую дату и время в виде:

Fri, 07 Apr 2000 14:25:48 +0100

из которого месяц можно извлечь:

${substr_8_3:$tod_full}

Если начальное смещение substr больше длины строки, результатом будет пустая строка; если длина плюс начальное смещение больше длины строки, результатом будет правая часть строки, начиная со смещения.

Оператор substr может принимать отрицательные значения смещения для отсчета от правого конца своего операнда. Последний символ имеет смещение -1, предпоследний — смещение -2 и так далее. Например:

${substr_-5_2:1234567}

дает 34. Если абсолютное значение отрицательного смещения больше длины строки, подстрока начинается в начале строки, а длина уменьшается на величину превышения. Например:

${substr_-5_2:12}

дает пустую строку, но:

${substr_-3_2:12}

дает 1. Если второе число опущено в substr, остаток строки берется, если смещение положительное. Если оно отрицательное, берутся все символы в строке, предшествующие точке смещения. Например:

${substr_4:penguin}   yields  uin
${substr_-4:penguin}  yields  pen

То есть смещение -n без длины дает все, кроме последних n символов подстроки.

17.4.3 Операторы хеширования

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

file = /var/mail/${hash_1:$local_part}/$local_part

Оператор hash_1 применяет алгоритм хеширования, который создает хэш-строку длины 1, выбранную из набора букв и цифр верхнего и нижнего регистра. Имя подкаталога теперь состоит из одного символа из 62 возможных. Более длинные хэш-строки могут быть запрошены путем указания других чисел после хеша, а набор символов, из которого они выбираются, может управляться вторым параметром.

Однако более новая функция числового хеширования (nhash) обеспечивает более равномерное распределение результатов и может обрабатывать большее количество возможностей. Используя числовой хэш, настройка:

file = /var/mail/${nhash_62:$local_part}/$local_part

достигает аналогичного эффекта, хотя теперь имена подкаталогов представляют собой числовые строки в диапазоне 0-61. Для использования в очень больших системах можно запросить nhash для получения двух чисел, вычисленных из одного хэша строки. Например:

file = /var/mail/${nhash_8_64:$local_part}

Когда это будет сделано, два числа будут разделены косой чертой, поэтому почтовый ящик caesar теперь может быть:

/var/mail/7/49/caesar

17.4.4 Принудительный регистр букв

Два других оператора, которые иногда бывают полезны, — это uc и lc. Они переводят свои аргументы в верхний или нижний регистр соответственно. В качестве примера предположим, что пользователь хочет использовать фильтр для хранения входящих сообщений в разных почтовых папках для разных отправителей, независимо от регистра. Можно использовать такую команду фильтра:

save Shome/mail/${lc:$sender_address}

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

17.5 Перевод символов

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

${tr {a,b,c}{,}{:}}  yields  a:b:c

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

17.6 Замена текста

Доступна общая функция замены строк. Он называется sg, потому что работает как sed и оператор Perl s с параметром /g (глобальный). Он принимает три аргумента: строку темы, регулярное выражение и строку замены. Например:

${sg {abcdefabcdef}{abc}{**}}  yields  **def**def

Шаблон сопоставляется со строкой темы, и совпадающая часть заменяется строкой замены. Затем ищется новое совпадение с оставшейся частью субъекта, и это продолжается до тех пор, пока не будет достигнут конец субъекта. Если совпадений нет, результатом является неизмененная тематическая строка. В замещающей строке числовые переменные $1, $2 и т. д. могут использоваться для ссылки на захваченные подстроки при совпадении регулярного выражения. Поскольку все три аргумента раскрываются перед использованием, любые символы $, которые требуются в регулярном выражении или в строке замены, должны быть экранированы или защищены с помощью \N. Например:

${sg {abcdef}{^(...)(...)\S}{\N$2$1\N}}  yields  defabc

17.7 Условное раскрытие

Большая часть силы механизма раскрытия заключается в его способности варьировать результаты элементов раскрытия в зависимости от определенных условий. Базовый элемент условного раскрытия имеет следующую форму:

${if <condition> {<string1>}{<string2>}}

Условие проверяется, и если оно истинно, <string1> расширяется и используется как замена для всего элемента; если нет, вместо этого используется <string2>. Вы можете полностью опустить <string2> и окружающие его фигурные скобки, если это пустая строка. Доступен ряд различных условий.

17.7.1 Проверка конкретной строки

Для проверки точного значения строки используется условие eq. Вот простой способ реализовать исключительное расположение почтового ящика только для одного пользователя:

file = ${if eq{$local_part}{john}\
  {/home/john/inbox}\
  {/var/mail/$local_part}}

Проверяется условие eq{$local_part}{john}; после имени условия eq в фигурных скобках даются две подстроки. Каждое отдельно раскрывается, а затем сравнивается на равенство. Если они равны, используется первая из следующих подстрок; в противном случае используется вторая. В этом примере, если локальная часть — john, почтовый ящик — /home/john/inbox, тогда как для всех остальных локальных частей это результат раскрытия — /var/mail/$local_part.

Часто бывает полезно размещать условия и другие сложные строки раскрытия в нескольких строках таким образом, потому что это облегчает их чтение. Поскольку подстроки раскрываются независимо друг от друга, они могут содержать свои собственные условные раскрытия, которые могут сделать текст очень нечитаемым. Например, если вы хотите иметь исключительное расположение почтового ящика для двух пользователей, это можно сделать, установив:

file = ${if eq{$local_part}{john}\
       { /home/john/ inbox}\
       {\
         ${if eq{$local_part}{jack}\
         {/home/jack/inbox}\
         {/var/mail/$local_part}}\
       }}

В таком виде его довольно легко понять по сравнению с:

file = ${if eq{$local_part}{john\
       }{/home/john/inbox}{${if eq\
       {$local_part}{jack}{/nome/jack/inbox}{\
       /var/mail/$local_part}}}}

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

17.7.2 Отрицательные условия

Отдельного условия для проверки неравенства не предусмотрено, поскольку существует общий механизм отрицания условий. Любое условие, перед которым стоит восклицательный знак, отменяется, например:

${if ! eq{$local_part}{john}{...

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

17.7.3 Сопоставление регулярных выражений

Если вы хотите проверить строку на наличие чего-то другого, кроме простого равенства, вы можете использовать регулярное выражение. Условие match, как и eq, принимает следующие две подстроки, заключенные в фигурные скобки, в качестве своих аргументов. Вторая интерпретируется как регулярное выражение и сопоставляется с первой. Например:

${if match {$local_part}{\N^x\d\d\N}{...

проверяет, начинается ли локальная часть с x, за которым следуют как минимум две цифры. Регулярное выражение заключено в \N, чтобы защитить внутреннюю обратную косую черту от интерпретации раскрывателем. Приложение B содержит справочное описание регулярных выражений, поддерживаемых Exim.

Если регулярное выражение содержит скобки захвата, то захваченные подстроки доступны в переменных $1, $2 и т. д. во время раскрытия строки «success». Например:

${if match {$local_part}{\N^x(\d\d)\N}({$1}}

не только проверяет $local_part, как и раньше, но и возвращает значение двух цифр в результате раскрытия $1. Если совпадения нет, его выходом является пустая строка. Когда тесты такого типа являются вложенными, значения числовых переменных ($1, $2 и т. д.) запоминаются в начале обработки элемента if и впоследствии восстанавливаются.

17.7.4 Сравнение зашифрованных строк

Когда сервер Exim аутентифицирует SMTP-соединение (13.2), ему может потребоваться сравнить незашифрованный пароль с зашифрованным (например, пароль пользователя из /etc/passwd или аналогичный). Это делается путем шифрования открытого текста и сравнения результата с зашифрованным значением, которое уже есть у Exim. Может показаться, что все, что нужно, — это оператор для шифрования строки, но из-за способа шифрования паролей Unix этого недостаточно. Зашифрованный пароль хранится в двухсимвольной строке «salt», и такое же значение необходимо для того, чтобы таким же образом зашифровать проверяемую строку. Сохраненная строка содержит как соль, так и результат шифрования[5]. Чтобы избежать необходимости заключать ее дважды в кавычки (один раз для шифрования и один раз для сравнения), существует одно условие, называемое crypteg, которое выполняет обе задачи одновременно.

${if crypteg {mysecret}{ksUCNd4Cs61SI}{...

В этом примере открытый текст mysecret сначала шифруется функцией crypt() с использованием соли ks, а затем результат сравнивается с UCNd4Cs61SI. В большинстве реальных случаев, конечно, зашифрованное значение не включается непосредственно в такую строку. Второй аргумент для crypteq, скорее всего, будет подрасширением, которое ищет значение в файле или базе данных.

В некоторых установках используются формы шифрования, отличные от используемых функцией crypt(). LDAP ввел нотацию, согласно которой зашифрованной строке предшествует строка в фигурных скобках, указывающая, как она была зашифрована. К счастью, открывающая фигурная скобка недействительна в качестве символа «salt». Если зашифрованная строка не начинается с фигурной скобки, шифрование с помощью crypt() предполагается условием crypteq. В противном случае содержимое фигурных скобок должно быть либо crypt (имеет тот же эффект), либо sha1, либо md5, как в следующем примере:

{md5}CY9rzUYh03PK3k6DJie09g==

Это указывает на то, что зашифрованная строка является хэшем MD5 открытого текста; sha1 будет означать хэш SHA-1. Если вы включаете такую строку непосредственно в расширенную строку, вам придется заключать фигурные скобки в кавычки, используя обратную косую черту, потому что в противном случае они будут восприняты как часть синтаксиса расширения. Например:

${if crypteq{test}{\{md5\}CY9rzUYh03PK3k6DJie09g==}{1}{0}}

Условие crypteq автоматически включается в бинарный файл Exim, когда он собран с поддержкой аутентификации SMTP, но в противном случае его нужно специально запрашивать во время сборки.

  1. Подробнее о шифровании паролей см. в спецификации функции crypt() для вашей операционной системы.

17.7.5 Другие тесты аутентификации

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

Аутентификация PAM

Условие pam предоставляет интерфейс к библиотеке PAM, доступной в некоторых операционных системах. PAM расшифровывается как Pluggable Authentication Modules и обеспечивает основу для поддержки различных методов аутентификации. Вызывающая сторона PAM предоставляет имя службы и начальную строку, идентифицирующую пользователя. Затем функция аутентификации запрашивает от вызывающего объекта ноль или более строк данных и использует их в качестве входных данных для своей логики аутентификации. В наиболее распространенном случае запрашивается одна строка данных, которая фактически является паролем.

Условие раскрытия pam имеет один аргумент, состоящий из списка строк, разделенных двоеточиями. PAM вызывается с именем службы exim и первой строкой в качестве имени пользователя. Остальные строки передаются в PAM в ответ на его запросы данных. Условие истинно, если PAM успешно аутентифицируется. Например:

${if pam{mylogin:mypassword}{yes}{no}

возвращает yes, если пользователь mylogin успешно аутентифицируется с помощью единственной строки данных mypassword, и no в противном случае. Данные для pam редко представляют собой фиксированную строку, подобную этой; обычно он состоит из переменных, содержащих значения из команды SMTP AUTH (13.2).

Если пароли, которые должны использоваться с PAM, содержат символы двоеточия, только что приведенный пример не будет работать, потому что двоеточие в пароле интерпретируется как разделитель в списке строк, передаваемых в PAM. Этой проблемы можно избежать, удвоив любые двоеточия, которые могут присутствовать. Например, если пароль указан в $2, это можно использовать:

${if pam{mylogin:${sg{$2}{:}{::}}{yes}{no}}

В некоторых операционных средах аутентификация PAM может использоваться только корневым процессом. Когда Exim получает входящую почту с удаленных хостов, он работает как пользователь Exim, что иногда вызывает проблемы с PAM в этих средах. Пока нет простого решения этой проблемы, хотя одним из возможных решений является использование демона Cyrus pwcheck.

Аутентификация pwcheck

Когда пароли хранятся в «shadow» файлах в целях безопасности, пользователи без полномочий root, такие как exim, не могут получить к ним доступ. Использование демона Cyrus pwcheck — это один из способов проверки паролей процессом, который не запущен от имени пользователя root. Это поддерживается условием раскрытия, называемым pwcheck. Он принимает один аргумент, который должен быть именем пользователя и паролем, разделенными двоеточием. Например, в конфигурации аутентификатора LOGIN у вас может быть следующее:

server_condition = ${if pwcheck{$1:$2}{1}{0}}

Вам не нужно устанавливать полный пакет программного обеспечения Cyrus, чтобы использовать демон pwcheck. Вы можете скомпилировать и установить только одного демона из библиотеки Cyrus SASL. Убедитесь, что exim — единственный пользователь, имеющий доступ к каталогу /var/pwcheck.

Аутентификация Radius

Аутентификация Radius (RFC 2865) поддерживается аналогично PAM и pwcheck. Условие radius имеет один аргумент. Он должен быть расширен до имени пользователя и пароля, разделенных двоеточием. Они передаются клиентской библиотеке Radius, которая вызывает сервер Radius. Условие истинно, если аутентификация прошла успешно. Например, в аутентификаторе LOGIN у вас может быть следующее:

server_condition = ${if radius{$1:$2}{yes}{no}}

Аутентификация LDAP

Условие ldapauth поддерживает аутентификацию пользователя с использованием LDAP. Подробная информация об использовании LDAP в поиске и синтаксис запросов LDAP описаны в разделе 16.2.3. При использовании с условием ldapauth запрос должен содержать имя пользователя и пароль. Основная часть запроса не используется и может быть пустой. Условие истинно, если имя пользователя и пароль принимаются сервером LDAP, и ложно в противном случае. В следующем примере показан типичный параметр аутентификатора LOGIN:

server_condition = ${if idapauth \
  {user="uid=${quote_ldap:$1},ou=people,o=example.org" \
  pass="$2" \
  ldap://ldap.example.org/}{yes}{no}}

17.7.6 Числовые сравнения

Exim предоставляет ряд условий для проверки числовых значений. В них используются знакомые символы, такие как > и =. Они записываются в префиксной нотации: сначала условие, за которым следуют две подстроки, которые должны (после их собственного подраскрытия) принимать форму десятичных целых чисел с необязательным знаком. За ними может дополнительно следовать одна из букв K или M (в верхнем или нижнем регистре), обозначающая умножение на 1024 или 1024x1024 соответственно. Например:

S{if > {$message_size}{10M}{...

проверяет, превышает ли размер сообщения (которое содержится в переменной $message_size) 10 МБ. Доступные числовые условия:

=  равно
== равно
>  больше
>= больше или равно
<  меньше
<= меньше или равно

Общая возможность отрицания обеспечивает проверку неравенства.

17.7.7 Пустые переменные и несуществующие строки заголовков

Условие def проверяет, содержит ли переменная какое-либо значение или существует ли в сообщении определенная строка заголовка. В первом случае за def следует двоеточие и имя переменной, и это будет соответствовать true только в том случае, если переменная не пуста. Например:

${if def:sender_host_address {remote}{local}}

возвращает строку remote, если $sender_host_address не пуст (что указывает на то, что сообщение не было создано на локальном хосте), и local в противном случае. Обратите внимание, что имя переменной дается в условии без начального символа $. Если переменная не существует, возникает ошибка.

Во втором варианте за def следует двоеточие, строка header_ или h_ и имя строки заголовка сообщения, заканчивающееся другим двоеточием, как в следующем примере:

${if def:header_reply-to:{$h_reply-to:}{$h_from:

Условие проверяет наличие строки заголовка в обрабатываемом сообщении, поэтому в этом случае результатом раскрытия является содержимое Reply-To: если оно существует; в противном случае это содержимое From:. Раскрыватель не проверяет, действительно ли в строке заголовка есть символы данных.

17.7.8 Наличие файла

Бывают случаи, когда вы можете захотеть применить разные стратегии в зависимости от того, существует ли конкретный файл или каталог. Условие exists имеет одну строку в качестве аргумента. Он вызывает функцию stat(), чтобы увидеть, существует ли строка в качестве пути в файловой системе, и является ли она истинной, если функция завершается успешно. Предположим, вы перемещаете почтовые ящики пользователей из одного каталога в другой. Если почтовый ящик существует в старом каталоге, вы хотите, чтобы Exim использовал его; в противном случае вы хотите использовать или создать почтовый ящик в новом каталоге. Можно использовать такую настройку параметра файла:

file = /var/\
  ${if exists{/var/oldmail/$local_part}{old}{new}}\
  mail/$local_part

Для локальной части sue это раскрывается до /var/oldmail/sue, если этот файл существует; в противном случае до /var/newmail/sue.

17.7.9 Состояние доставки сообщения

Есть два условия, с которыми не связаны никакие данные: first_delivery истинна во время первой попытки доставки сообщения, но ложна во время любых последующих попыток доставки; queue_running имеет значение true во время попыток доставки, запущенных процессом запуска очереди, в противном случае — значение false. Это позволяет вам применять различные стратегии маршрутизации в разное время, если вам это понадобится. Если вы хотите, чтобы роутер использовался только при первой попытке доставки, вы можете установить такую опцию:

condition = ${if first_delivery {yes}{no}}

Во время первой доставки условие истинно, и строка раскрывается до yes. При последующих попытках доставки результат no, поэтому роутер пропускается.

17.7.10 Комбинирование условий раскрытия

Несколько условий можно объединить в одно условие с помощью логических операторов and и or. За каждым из них следует любое количество условий, каждое из которых заключено в фигурные скобки. Весь список также заключен в фигурные скобки, чтобы показать, где он заканчивается. Это может привести к очень нечитаемому тексту, если вы не будете осторожны. Условие and истинно, только если истинны все его подусловия, тогда как условие or истинно, если истинно хотя бы одно подусловие. Вот причудливый пример, проверяющий, является ли сегодняшний день високосным, разделенный на несколько строк для удобочитаемости:

${if and
  {
  {eq {${substr_5_2:$tod_log}}{02}}
  {eq {${substr_8_2:$tod_log}}{29}}
  }
  {Today's the day}{Not today}}

Переменная $tod_log содержит текущую дату и время в следующем формате:

2000-02-29 14:42:00

Первое условие проверяет две цифры месяца, а второе проверяет номер дня. Подусловия проверяются слева направо, и полностью оценивается только то количество, которое необходимо для установления общего условия. Подусловия могут сами использовать and и or при необходимости.

17.7.11 Ошибка принудительного раскрытия

Мы определили элемент условного раскрытия как условие, за которым следуют две подстроки: строка «true» и строка «false». Если вторая строка пуста (то есть если она будет закодирована как {}), ее можно опустить. Однако иногда вы не хотите использовать пустую строку, если условие не выполняется; вы хотите принять более решительные меры. Вместо второй строки можно указать слово fail, но не в фигурных скобках. Это приводит к сбою всего раскрытия, но таким образом, что вызывающий код может это обнаружить. Мы говорим: «раскрытие вынуждено потерпеть неудачу».

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

headers_add = ${if eq {$sender_host_address}{}\
  {X-Postmaster: <postmaster@example.com>}\
  fail}

Exim добавляет заголовок X-Postmaster: к любым сообщениям, полученным локально (адрес хоста пуст). Когда $sender_host_address не пуст, ошибка приводит к сбою раскрытия строки, что в данном конкретном случае приводит к отмене добавления заголовков. Всякий раз, когда сбой в раскрытии имеет такой особый эффект, он документируется вместе с параметром, к которому он применяется.

17.8 Поиск в строках раскрытия

Возможность вызывать функции поиска Exim'а при раскрытии строки — мощная функция. Часть строки может быть заменена данными, полученными из файла или базы данных, либо наличие определенного ключа в файле или базе данных может повлиять на результат раскрытия.

Элемент раскрытия поиска — это форма условного раскрытия, содержащая две подстроки, следующие за спецификацией поиска. Если поиск успешен, первая подстрока раскрывается и используется; во время раскрытия переменная $value содержит искомые данные. Если поиск не удался, вторая подстрока раскрывается и используется. Как и в случае условия if, вторая подстрока может отсутствовать или может использоваться слово fail, как описано в предыдущем разделе.

Если вы также опускаете первую подстроку, Exim ведет себя так, как будто это {$value}. Например, обычный роутер для обработки псевдонимов выглядит следующим образом:

system_aliases:
  driver = redirect
  data = ${lookup{$local_part}lsearch{/etc/aliases}}

Это точно так же, как следующее, в котором явно включены подстроки:

system_aliases:
  driver = redirect
  data = ${lookup{$local_part}lsearch{/etc/aliases}{$value}{}}

Существует два разных формата элементов lookup в раскрытых строках, в зависимости от того, используется ли поиск по одному ключу или в стиле запроса. Мы представим их, расширив пример, который использовался ранее.

17.8.1 Поиск по одному ключу в строках раскрытия

Напомним, что следующая настройка:

file = ${if eq{$local_part}{john}\
       {/home/john/inbox} \
       {/var/mail/$local_part}}

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

john:   /home/john/inbox
jill:   /hnome/jill/inbox
alex:   /nome/alex/mail/inbox

Этот файл можно использовать непосредственно в транспорте appendfile:

file = ${lookup {$local_part} lsearch {/the/file} \
       {$value} {/var/mail/$local_part}}

Для типа поиска с одним ключом за вводным ${lookup следует подстрока в фигурных скобках, которая определяет ключ для поиска после того, как он был отдельно раскрыт. В этом случае маршрутизируется локальная часть адреса. Затем идет слово, указывающее тип поиска, в данном случае lsearch, за которым следует подстрока, содержащая данные для поиска, которая для типа поиска с одним ключом является именем файла, который необходимо найти.

В нашем примере, если локальная часть — jill, поиск завершается успешно, и $value содержит /home/jill/inbox во время раскрытия первой строки замены. Поскольку он состоит только из $value, результатом всего поиска будет /home/jill/inbox. Если ищется локальная часть, которой нет в файле, данные не найдены, поэтому вторая строка замены, /var/mail/$local_part, раскрывается и используется.

17.8.2 Частичный поиск в строках раскрытия

Частичный поиск (16.5) может использоваться для типов поиска с одним ключом, и когда частичный поиск завершается успешно, переменные $1 и $2 содержат подстановочную и неподстановочную части ключа во время раскрытия замещающей подстроки. Они возвращаются к своим прежним значениям в конце элемента поиска. Рассмотрим файл доменных имен, содержащий:

one.example
*.two.example

и элемент поиска вида:

${lookup {$domain} partial-lsearch {/the/file}{wild="$1" notwild="$2"}}

Когда $domain содержит one.examp1e, результат будет таким:

wild="" notwild="one.example"

потому что не было выполнено частичное сопоставление. Однако, если $domain содержит twenty.two.example, результат будет:

wild="twenty" notwild="two.example"

17.8.3 Ошибки поиска по одному ключу

Если Exim не может выполнить поиск из-за какой-то проблемы (например, если он не может открыть файл), раскрытие всей строки завершается ошибкой, подобно синтаксической ошибке. Вы можете защититься от конкретного случая несуществующего файла, используя проверку существования, описанную ранее. Например:

file = ${if exists{/the/file}\
       {\
       ${lookup {$local_part} lsearch {/the/file} \
       {$value}\
       {/var/mail/$local_part}}\
       }\
       {/var/mail/$local_part}}

17.8.4 Поиск в раскрытых строках в стиле запроса

Большой линейный файл должен быть преобразован в какую-либо индексированную структуру (DBM или cdb) для повышения производительности, или данные могут быть сохранены в одной из поддерживаемых Exim баз данных. Вот параметр конфигурации, который ищет почтовый ящик с помощью MySQL:


file = ${lookup mysql \
       {select mbox from users where \
       id='${quote_mysqi:$local_part}'} \
       {$value} \
       {/var/mail/$local_part}}

В этом примере предполагается, что существует таблица с именем users и полями с именами id и mbox. Когда поиск в стиле запроса используется в строке раскрытия, вы не указываете отдельный ключ. Вместо этого имя типа поиска следует за ${lookup сразу, а за ним следует подстрока, формирующая запрос к базе данных. Затем следуют подстроки успеха и неудачи, как и раньше.

17.8.5 Уменьшение количества запросов к базе данных

В загруженной системе поиск, подобный предыдущему примеру, который требует обращения к базе данных для каждой доставки, может привести к снижению производительности. Улучшение можно было бы получить, выгружая данные из базы данных каждую ночь и создавая (например) файл cdb, который дал бы гораздо лучшую производительность. Однако дополнения к базе данных не вступят в силу немедленно. Лучшее из обоих миров может быть достигнуто путем проверки сначала файла cdb и выполнения поиска в базе данных только в случае неудачи. Например:

file = \
  ${lookup {$local_part} cdb {/the/cdb/file} \
    {$value}\
      {\
      ${lookup mysql \
      {select mbox from users where \
      id='${quote_mysgql:$local_part}'} \
      {$value} \
      {/var/mail/$local_part}}\
      }\
    }

Есть еще несколько замечаний по теме уменьшения количества запросов к базе данных в разделе 17.10.2.

17.8.6 Значения по умолчанию для поиска в строках раскрытия

При использовании одноключевого поиска в строках раскрытия имя типа поиска может сопровождаться * или *@ для запроса поиска по умолчанию (16.4). В качестве альтернативы, подстрока «false» может содержать явный второй поиск, который должен быть выполнен, если первый не сработает, и это работает как для поиска с одним ключом, так и для поиска в стиле запроса.

17.9 Вставка целых файлов

Вы можете вставить все содержимое файла в строку раскрытия с помощью элемента следующего вида:

${readfile{<file name>}{<end-of-line string>}}

Имя файла и строка end-of-line сначала раскрываются отдельно. Затем файл считывается, и его содержимое заменяет весь элемент. Все символы новой строки в файле заменяются строкой end-of-line, если она присутствует. Если это пустая строка, новые строки удаляются.

17.10 Извлечение полей из подстрок

Есть несколько элементов раскрытия, которые извлекают поля данных из подстрок, предварительно раскрыв их.

17.10.1 Разделение адресов

local_part и domain — это операторы, которые извлекают локальную часть и домен из адреса соответственно. Например, если отправителем сообщения является brutus@rome.example.com, то:

local part is ${local_part:$sender_address}
domain is ${domain:$sender_address}

раскрываются до:

local part is brutus
domain is rome.example.com

17.10.2 Извлечение именованных полей из строки данных

Предположим, вы хотите выбрать значения отдельных полей из файла данных, содержащего такие строки:

trajan: uid=142 gid=241 home=/homes/trajan

Поиск lsearch по ключу trajan возвращает остальную часть строки данных; элемент раскрытия extract может быть использован для его разделения. Например, чтобы получить значение поля uid:

${extract{uid}{${lookup{trajan}lsearch{/the/file}{$value}fail}}}

За extract следуют две подстроки; первая — это имя, а вторая — строка элементов <name>=<value>, из которых извлекается значение, соответствующее заданному имени. В этом примере строка получается поиском, что является наиболее распространенным случаем. Поиск ключа trajan дает:

uid=142 gid=241 home=/homes/trajan

поэтому элемент extract становится:

${extract{uid}{uid=142 gid=241 home=/homes/trajan}}

до применения операции извлечения. Если какие-либо значения в данных содержат пробелы, они должны быть заключены в двойные кавычки, а внутри двойных кавычек происходит обычная обработка экранирования[6].

Если имя не найдено в строке данных, элемент заменяется пустой строкой. Однако элемент extract также можно использовать аналогично элементу поиска или условному элементу. Если заданы две дополнительные строки аргументов, первая используется при успешном извлечении, где $value содержит извлеченную подстроку, а вторая используется при неудачном извлечении. Например:

${extract{uid}{uid=142 gid=241 home=/homes/trajan}{userid=$value}}

дает userid=142. Опять же, вместо второй подстроки можно указать «fail», чтобы вызвать сбой раскрытия.

Если вы используете поиск в стиле запросов, вы часто можете выбирать отдельные поля в языке запросов. Однако, если требуется несколько полей, лучше считывать их из базы данных вместе и использовать extract для их разделения, поскольку результаты поиска в базе данных кэшируются. Рассмотрим следующие два параметра, которые могут появиться в конфигурации локального транспорта, чтобы указать uid и gid, под которыми он должен работать:

user = ${lookup mysql \
  {select uid from accounts where \
  id='${quote_mysql:$local_part}'}}
group = ${lookup mysql \
  {select gid from accounts where \
  id='${quote_mysql:$local_part}'}}

Требуются два отдельных запроса к базе данных, по одному для каждой опции. Если вместо этого используются эти настройки:

user  = ${extract{uid}{\
        ${lookup mysql \
        {select uid,gid from accounts where \
          id='${quote_mysql:$local_part}'}}\
        }}
group = ${extract {gid}{\
        ${lookup mysql \
        {select uid,gid from accounts where \
          id='${quote_mysql:$local_part}'}}\
        }}

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

Другой способ сохранить результат поиска во время маршрутизации и доставки адреса — использовать опцию address_data (6.3.1).

  1. Например, \n преобразуется в новую строку, а обратная косая черта должна отображаться как \\.

17.10.3 Извлечение неименованных полей из строки данных

Существует вторая форма элемента раскрытия extract, которую можно использовать для строк данных, разделенных определенными символами. Хорошим примером является файл паролей etc/passwd, где поля разделены двоеточием. Чтобы извлечь настоящее имя пользователя из этого файла, можно использовать такое расширение:

${extract{4}{:}{${lookup{hadrian}lsearch{/etc/passwd}{$value}fail}}}

Если строка в /etc/passwd:

hadrian:x:42:99:Hadrian IV::/bin/bash

поиск дает:

*:42:99:Hadrian IV::/bin/bash

и результатом извлечения является четвертое поле, разделенное двоеточием, в этих данных, а именно, Hadrian IV.

Эта форма extract отличается от другой тем, что первый аргумент полностью состоит из цифр. Второй аргумент — это список символов-разделителей полей, любой из которых может использоваться в данных. Другими словами, разделители всегда являются одним символом, а не строкой. Два последовательных разделителя означают, что поле между ними пустое (пятое поле в предыдущем примере). Если номер поля в раскрытии равен нулю, возвращается вся строка; если он больше, чем количество полей, возвращается пустая строка. Отрицательное число может использоваться для подсчета полей справа; -1 извлекает последнее поле, -2 предпоследнее поле и так далее.

Как и в случае другой формы extract, вы можете дополнительно указать две дополнительные подстроки, которые используются аналогично подстрокам в элементе поиска или условном элементе. Первая используется, если поле найдено с $value, содержащим извлеченные данные, а вторая подстрока используется в противном случае. Опять же, вместо второй подстроки можно указать «fail», чтобы вызвать сбой расширения.

17.11 Маскировка IP-адреса

IP-сеть определяется с использованием IP-адреса и маски. Например, 192.168.34.192/26 определяет сеть, состоящую из всех IP-адресов, у которых старшие 26 битов такие же, как у 192.168.34.192. Чтобы проверить, находится ли данный хост в этой сети, необходимо замаскировать младшие биты его IP-адреса (то есть преобразовать их в нули) перед сравнением с сетевым адресом. Есть несколько встроенных хост-тестов, которые автоматически заботятся о маскировании, но для того, чтобы вы могли писать собственные тесты, существует оператор раскрытия маскирования. При обработке сообщения, пришедшего с хоста 192.168.34.199, строка:

${mask:$sender_host_address/26}

раскрывается до строки 192.168.34.192/26. Это можно сравнить с фиксированным значением или найти в файле. Это создает небольшую проблему в случае адресов IPv6, которые обычно записываются с использованием двоеточий для разделения компонентов, потому что двоеточие является ключевым разделителем в файлах данных в формате lsearch (то есть в той же форме, что и файлы псевдонимов). Поэтому обычный адрес IPv6 не может быть ключом в таком файле. Чтобы можно было использовать IPv6-адреса в качестве ключей lsearch, оператор mask выводит их, используя в качестве разделителей точки вместо двоеточий. Например, строка:

${mask:3ffe:ffff:836f:0a00:000a:0800:200a:c031/99}

раскрывается до:

5f03.1200.836f.0a00.000a.0800.2000.0000/99

Буквы в IPv6-адресах всегда выводятся оператором mask в нижнем регистре.

17.12 Цитирование

Когда данные из сообщения включаются в строку раскрытия с помощью вставки переменной или заголовка, могут возникнуть проблемы, если вставленные данные содержат непредвиденные символы. Локальные части могут содержать всевозможные специальные символы, если они правильно заключены в кавычки в соответствии с правилами RFC 2822. Все следующие адреса действительны:

O'Reilly@ora.com.example
double\"quote@weird.example
"two words"@weird.example
"abc@pqr"@some.domain.example
abc\@pqr@some.domain.example

Когда Exim получает адрес, он убирает цитирование RFC 2822, чтобы получить «каноническое» представление локальной части. Последние два примера имеют одинаковые канонические локальные части. Предположим, что $local_part используется в поисковом запросе MySQL, содержащем следующий фрагмент:

... where id='$local_part' ...

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

17.12.1 Экранирование адресов

Оператор кавычек заключает свой аргумент в двойные кавычки, если он содержит что-либо, кроме букв, цифр, знаков подчеркивания, точек или дефисов. Любые вхождения двойной кавычки или обратной косой черты экранируются обратной косой чертой. Такое цитирование полезно, если по какой-то причине новый почтовый адрес создается из старого. Например, предположим, что вы хотите отправить неизвестные локальные части из вашего локального домена в какой-то другой домен, сохранив ту же локальную часть, но изменив домен. Вы можете сделать это, используя роутер redirect, такой как ваш последний роутер:

send_elsewhere:
  driver = redirect
  data = Slocal_part@dead-letter.example.com

Поскольку это последний роутер, ему будут переданы локальные части, которые не могут обработать другие роутеры. Пример будет работать нормально, пока кто-нибудь не отправит сообщение с локальной частью, содержащей символ @, например:

"malicious@example"@your.domain

Exim сообщит о синтаксической ошибке в новом адресе, потому что значение $local_part будет malicious@example после удаления цитирования RFC 2822. Чтобы защититься от этого, параметр лучше определить следующим образом:

data = ${quote:$local_part}@dead-letter.example.com

Это приводит к тому, что локальная часть будет заключена в кавычки, если это необходимо.

17.12.2 Экранирование данных для регулярных выражений

Оператор rxquote вставляет обратную косую черту перед любыми небуквенно-цифровыми символами в своем аргументе. Как следует из названия, он используется при вставке данных, которые должны интерпретироваться буквально, в регулярные выражения. Если вы хотите проверить, упоминается ли текущий адрес доставки в заголовке сообщения To:, вы можете написать условие раскрытия, например следующее:

${if match{$h_to:}{^.*$local_part@$domain} {...

Обратите внимание, что в этом случае \N не используется для защиты регулярного выражения от раскрытия, потому что мы используем раскрытие для построения выражения. Однако любые метасимволы регулярного выражения в локальной части или в домене могут нарушить регулярное выражение, а поскольку таким символом является точка, домен почти наверняка вызовет проблемы. Правильный способ написания этого примера выглядит следующим образом:

${if match{$h_to:}{^.*${rxquote:$local_part@$domain}} {...

17.12.3 Экранирование данных в поисковых запросах

Существуют специальные операторы кавычек для каждого типа поиска в стиле запроса:

NIS+

Эффект оператора раскрытия quote_nisplus заключается в удвоении любых символов кавычек в тексте.
LDAP

В запросах LDAP требуются два уровня кавычек: первый для самого LDAP, а второй, потому что запрос LDAP представлен в виде URL-адреса. Оператор раскрытия quote_ldap реализует следующие правила:

  • Для цитирования LDAP перед символами #,+"\<>;*() должна стоять обратная косая черта[7].

  • Для цитирования URL все символы, кроме буквенно-цифровых и !$'()*+-._ заменяются на %xx, где xx — шестнадцатеричный код символа. Обратите внимание, что в URL-адресе необходимо заключать обратную косую черту в кавычки, поэтому символы, экранированные для LDAP, в конечном кодировании будут предваряться %5C.

MySQL

Оператор раскрытия quote_mysql1 преобразует символы новой строки, табуляции, возврата каретки и backspace в \n, \t, \r и \b соответственно, а символы '"\ экранируются обратной косой чертой. Проценты и подчеркивание используются только в тех контекстах, где они могут быть подстановочными знаками, и MySQL не разрешает заключать их в кавычки где-либо еще, поэтому на них не влияет оператор quote_mysql.

Oracle

Оператор раскрытия quote_oracle имеет тот же эффект, что и quote_mysql.

PostgreSQL

Оператор раскрытия quote_pgsql преобразует новую строку, табуляцию, возврат каретки и backspace в \n, \t, \r и \b соответственно, а символы '"\ экранируются обратной косой чертой, как и в quote_mysql. Однако процент и подчеркивание обрабатываются по-разному. PostgreSQL позволяет заключать их в кавычки в контекстах, где они не являются особыми. Например, такой фрагмент SQL, как:

where id="ab\%cd"

имеет тот же эффект, что и:

where id="ab%cd"

что не относится к MySQL. Поэтому процент и подчеркивание экранируются quote_pgsql.

  1. На самом деле, только некоторые из них должны заключаться в кавычки в Distinguished Names, а другие — в фильтрах LDAP, но единое правило цитирования для всех них не помешает.

17.12.4 Экранирование данных печати

Последний оператор кавычек называется escape и используется, когда вставляемые данные должны содержать только печатные символы. Он преобразует любые непечатаемые символы в escape-последовательности, начинающиеся с обратной косой черты[8]. Например, символ новой строки преобразуется в \n, а символ backspace в \010. Локальные части, которые цитируются в адресах электронной почты, могут содержать такие символы, хотя обычно их создают только люди, помешанные на вреде. Строки заголовков сообщений — это еще одно место, где могут встречаться непечатаемые символы. В качестве примера того, где можно использовать escape, рассмотрим создание автоматического ответа на входящее сообщение. Подробности того, как это сделать, можно найти ранее (9.7), но вам не нужно их знать, чтобы следовать этому примеру, который просто показывает, как может быть указана строка Subject: ответа:

subject = Re: message from $sender_address to $local_part

Это не должно вызывать проблем с доставкой, каким бы ни было содержимое локальной части и адрес отправителя, но может быть очень запутанным, если, например, в одной из переменных есть пробелы. Более безопасный способ написать это:

subject = Re: message from ${escape:$sender address} \
  to ${escape:$local_part}
  1. Считаются ли символы с установленным старшим битом (так называемые «8-битные» символы) печатаемыми или непечатаемыми, управляется параметром print_topbitchars.

17.13 Повторное раскрытие

Оператор expand сначала раскрывает свою подстроку аргумента, как и все другие операторы, но затем передает результат через раскрыватель во второй раз. Чаще всего используется, когда первое раскрытие выполняет поиск, потому что оно позволяет искомым данным содержать переменные раскрытия. Предположим, что опция файла для локальной доставки написана так:

file = ${lookup{$local_part}lsearch{/etc/mailboxes}\
       {${expand:$value}}{/var/mail/$local_part}}

Чтобы найти имя файла почтового ящика, локальная часть ищется в /etc/mailboxes. Если данные не найдены, строка /var/mail/$local_part раскрывается и используется. В противном случае используется искомое значение, но сначала оно повторно раскрывается. Файл /etc/mailboxes может содержать такие строки:

jim:  /home/jim/inbox

для которых дополнительное раскрытие не имело бы никакого эффекта, но оно также могло бы содержать такие строки:

jon:  ${if eq{$h_precedence:}{bulk}{/dev/null1}{/var/mail/jon}}

которые отбрасывают сообщения со строкой заголовка Precedence:, значение которой равно bulk, заставляя их записываться в /dev/null[9].

  1. Это не рекомендуемый способ достижения этого эффекта; это просто пример для демонстрации оператора expand.

17.14 Вызов внешнего кода

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

17.14.1 Запуск внешней программы

Элемент раскрытия выполнения позволяет включить результат выполнения внешней команды в строку. Синтаксис следующий:

${run {<command> <args>}{<string1>}{<string2>}}

Команда и ее аргументы сначала раскрываются отдельно, а затем команда запускается в отдельном процессе, но с теми же uid и gid, что и вызывающий процесс. Как и при выполнении других команд из Exim, оболочка по умолчанию не используется. Если вам нужна оболочка, вы должны явно закодировать ее.

Если команда завершается успешно (дает нулевой код возврата), первая строка раскрывается и заменяет весь элемент; во время этого раскрытия стандартный вывод команды находится в переменной $value. Если команда завершается ошибкой, вторая строка, если она присутствует, раскрывается. Если он отсутствует, результат пустой. В качестве альтернативы, вторая строка может быть словом fail (не в фигурных скобках), чтобы вызвать ошибку раскрытия, если команда не удалась. Если обе строки опущены, результатом будет стандартный вывод в случае успеха и ничего в случае неудачи.

Код возврата из команды помещается в переменную $runrc, и впоследствии она остается установленной, поэтому в файле фильтра вы можете писать такие команды, как:

if "${run{x y z}{}}$runre" is 0 then ...
  elif $runre is 1 then ...
  ...
endif

При раскрытии первой строки Exim запускает команду x с двумя строками аргументов, y и z. Пустая пара фигурных скобок приводит к отбрасыванию любого вывода команды; поэтому значение всей раскрытой строки является просто кодом возврата, взятым из $runrc.

17.14.2 Запуск встроенного Perl

Если вы хотите использовать Perl из строк раскрытия, Exim должен быть собран с поддержкой встроенного Perl (см. главу 22). Доступ к подпрограммам Perl осуществляется через параметр конфигурации, называемый perl_startup, который определяет набор подпрограмм Perl, и оператор строки раскрытия ${perl...}, который вызывает их запуск. Если в конфигурационном файле Exim нет опции perl_startup, интерпретатор Perl не запускается, и для Exim почти нет накладных расходов (поскольку ни одна из библиотек Perl не загружается, если она не используется).

При наличии параметра perl_startup связанное значение принимается за код Perl, который выполняется во вновь созданном интерпретаторе Perl. Он не раскрывается в смысле Exim, так что вам не нужна обратная косая черта перед какими-либо символами, чтобы экранировать специальные значения. Вариант обычно должен быть чем-то вроде следующего:

perl_startup = do '/etc/exim.pl'

где /etc/exim.pl содержит код Perl, определяющий подпрограммы, которые вы хотите использовать. Exim может быть настроен либо на запуск интерпретатора Perl, как только он начнет выполняться, либо на ожидание до тех пор, пока интерпретатор не понадобится в первый раз. Запуск интерпретатора в начале гарантирует, что это будет сделано, пока Exim все еще имеет свою привилегию setuid, которая может понадобиться для получения доступа к файлам инициализации, но налагает ненужные накладные расходы, если Perl не используется в конкретном запуске. По умолчанию интерпретатор запускается только тогда, когда это необходимо, но это можно изменить следующим образом:

Когда файл конфигурации включает параметр perl_startup, элементы раскрытия строки могут вызывать подпрограммы Perl, определенные кодом perl_startup следующим образом:

${perl{func}{argument1}{argument2} ... }

Элемент, подобный этому, вызывает подпрограмму func с заданными аргументами (предварительно раскрыв аргументы). Можно передать минимум ноль и максимум восемь аргументов. Прохождение большего числа приводит к сбою раскрытия. Возвращаемое значение подпрограммы вставляется в раскрытую строку, если только возвращаемое значение не равно undef. В этом случае раскрытие завершается ошибкой так же, как явный fail в элементе if или lookup. Если подпрограмма прерывается, подчиняясь Perl-функции die, раскрытие завершается неудачей с сообщением об ошибке, которое было передано в die.

Интерпретатор Perl не запускается в отдельном процессе, поэтому, когда он вызывается из строки раскрытия, его uid и gid такие же, как у процесса Exim. В частности, инициализация интерпретатора, когда Exim начинает работать, приводит к тому, что он работает как root только во время инициализации; это не приводит к тому, что впоследствии вызываемые подпрограммы запускаются от имени пользователя root.

В любом коде Perl, вызываемом из Exim, функция Exim::expand_string доступна для обратного вызова в раскрыватель строк Exim. Это помогает уменьшить количество аргументов, которые необходимо передать подпрограмме Perl. Например, код Perl:

my $lp = Exim::expand_string('$local_part');

делает текущее значение $local_part доступным в переменной Perl $lp. Обратите внимание на использование одинарных кавычек для защиты от интерполяции $local_part как переменной Perl.

Если раскрытие строки принудительно завершится ошибкой, результатом Exim::expand_string будет undef. Если в строке раскрытия есть синтаксическая ошибка, вызов Perl из строки раскрытия Exim завершится ошибкой с соответствующим сообщением об ошибке, точно так же, как если бы использовалось die.

17.15 Блокировка определенных элементов раскрытия в файлах фильтров

Некоторые системные администраторы могут захотеть заблокировать использование более мощных функций раскрытия строк в файлах фильтров пользователей. Например, использование run и perl может считаться нежелательным, а в системе, где у пользователей нет учетных записей для входа в систему, может быть желательно предотвратить попытки пользователей получить доступ к произвольным файлам. Роутер redirect имеет ряд опций, имена которых начинаются с forbid_, которые используются для отключения отдельных элементов раскрытия (7.6.19).

17.16 Тестирование строк раскрытия

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

exim -be

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

$ exim -be '$tod_ log'
2000-02-10 15:51:00
$ exim -be
> ${lookup{root}lsearch{/etc/passwd}}
x:0:1:Super-User:/:/bin/sh
>

Это средство позволяет вам протестировать общую функциональность раскрытия, но, поскольку сообщения не обрабатываются, вы не можете использовать переменные, такие как $local_part, относящиеся к сообщениям.

Глава 18
Списки доменов, хостов и адресов

Списки доменов, хостов, адресов и локальных частей используются во многих опциях Exim и показаны в примерах в этой книге. В этой главе мы рассмотрим все типы элементов, которые вы можете использовать в этих списках, которые всегда раскрываются перед поиском.

Каждый список можно рассматривать как определяющий набор доменов, хостов, адресов или локальных частей соответственно. Когда Exim проверяет, соответствует ли домен (или хост, адрес или локальная часть) элементу в списке, он задает вопрос «Этот домен (или хост, адрес или локальная часть) находится в наборе, определенном этим списком?" Он просматривает список слева направо, проверяя каждый элемент по очереди. Как только элемент совпадает, сканирование останавливается.

Во всех списках по умолчанию в качестве символов-разделителей используются двоеточия, а пробелы в конце элемента игнорируются. Если вам нужно включить буквальное двоеточие в элемент, его нужно удвоить. К сожалению, это необходимо для всех двоеточий, которые появляются в адресах IPv6. Например:

local_interfaces = 127.0.0.1: ::1

содержит два элемента: адрес IPv4 127.0.0.1 и адрес IPv6 ::1. Пробел после первого двоеточия жизненно важен; без него список будет неправильно интерпретироваться как два элемента 127.0.0.1:: и 1.

Альтернативой удвоению двоеточий является изменение символа-разделителя для списка. Если список начинается с символа <, за которым сразу следует небуквенно-цифровой печатный символ (за исключением пробела), этот символ используется в качестве разделителя. Пример local_interfaces можно переписать, чтобы использовать + в качестве разделителя следующим образом:

local_interfaces = <+ 127.0.0.1 + ::1

18.1 Исключаемые элементы в списках

Иногда полезно иметь исключения из шаблонов подстановочных знаков. Например, предположим, что все домены, оканчивающиеся на .cities.example, являются локальными доменами. У нас может быть следующая настройка на роутере, чтобы заставить его обрабатывать адреса в этих доменах:

domains = *.cities.example

Теперь предположим, что мы хотим исключить athens.city.example при сохранении всех остальных. Существующий элемент списка является включаемым (positive) элементом. В случае совпадения ответ на вопрос «Есть ли домен в этом списке?» будет «да». Также возможны исключаемые (negative) элементы, которые, если они совпадают, приводят к ответу «нет», и это обеспечивает именно ту функцию, которая нам нужна. Восклицательный знак перед любым элементом отменяет его[1]. Рассмотрим этот пример:

domains = !athens.cities.example:\
          *.cities.example

Если домен athens.cities.example, то первый элемент совпадает. Поскольку он отрицается, это приводит к ответу «нет». В противном случае домен сопоставляется с *.cities.example.

Если последний элемент в списке является исключаемым элементом, это меняет то, что происходит, когда достигается конец списка без совпадений. В примерах, которые мы использовали до сих пор, достижение конца списка вызывает ответ «нет». Однако для такого списка:

queue_domains = !*.mydomain.example

достижение конца списка вызывает ответ «да», потому что это кажется естественной интерпретацией такого списка. По сути, список, оканчивающийся исключаемым элементом, обрабатывается так, как если бы он имел в конце дополнительный элемент «соответствует всем», поэтому этот пример ведет себя точно так же, как:

queue_domains = !*.mydomain.example : *

Другой способ думать о включаемых и исключаемых элементах в списках — читать соединитель как «или» после положительного элемента и как «и» после отрицательного элемента.

  1. Между восклицательным знаком и элементом может быть необязательный пробел.

18.2 Список элементов в файлах

В любом из этих списков элемент, начинающийся с косой черты, интерпретируется как имя файла, содержащего элементы вне строки, по одному в строке. Пустые строки в файле игнорируются. Другие строки интерполируются в список точно так же, как если бы они были встроенными, за исключением того, что они не раскрываются, и файл считывается заново каждый раз при сканировании списка. Однако файл не может содержать имена других файлов.

Для списков доменов и хостов, если в строке файла появляется символ #, он и все последующие символы в строке игнорируются. Для списков адресов и локальных частей символ # должен стоять в начале строки или ему должен предшествовать пробел, чтобы его можно было распознать как введение комментария, потому что # может допустимо появляться в локальной части. Если простому имени файла предшествует восклицательный знак, смысл любого совпадения в файле инвертируется. Например, с настройкой:

hold_domains = !/etc/nohold-domains

и файл, содержащий строки:

!a.b.c
*.b.c

a.b.c находится в наборе доменов, определенных с помощью hold_domains, а любой домен, соответствующий *.b.c, — нет.

18.3 Поиск элементов в списках

Списки также могут содержать элементы поиска; они работают по-разному для четырех типов списков, поэтому они будут описаны отдельно позже. Однако важно понимать, что поиск, даже если он использует метод поиска lsearch, не то же самое, что интерполированный файл, как только что было описано. Показанный пример файла нельзя использовать для поиска lsearch, поскольку данные в файле поиска фиксированы: он не может содержать никаких отрицаний или подстановочных знаков[2]. Следовательно, существует важное различие между (например):

hold_domains = /etc/hold-domains

и:

hold_domains = lsearch;/etc/hold-domains

хотя файл читается последовательно в обоих случаях. В первом случае каждая строка интерполируется так же, как если бы она была встроенной, и файл может содержать элемент любого типа, кроме дополнительного имени файла. Например, он может содержать элементы, начинающиеся со звездочек, или регулярные выражения. Однако, если используется поиск (второй случай), ключи в файле специально не интерпретируются; они всегда являются буквальными строками.

  1. Частичные и стандартные функции одноключевого поиска реализуются с помощью нескольких проверок файла.

18.4 Именованные списки

Именованные списки были введены ранее (4.4) в следующем примере:

domainlist local_domains = localhost:my.dom.example

Любому списку доменов, хостов, адресов электронной почты или локальных частей может быть присвоено имя, которое используется со знаком плюс для ссылки на список в другом месте конфигурации, как в первом роутере в конфигурации по умолчанию, которая выглядит следующим образом:

dnslookup:
  driver = dnslookup
  domains = ! +local_domains
  transport = remote_smtp
  no_more

Четыре типа именованных списков создаются строками конфигурации, начинающимися со слов domainlist, hostlist, addresslist или localpartlist соответственно. Затем следует имя, которое вы определяете, за которым следует знак равенства и сам список. Например:

hostlist    relay_hosts = 192.168.23.0/24 : my.friend.example
addresslist bad_senders = cdb;/etc/badsenders

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

domainlist  dom1 = first.example : second.example
domainlist  dom2 = +dom1 : third.example
domainlist  dom3 = fourth.example : +dom2 : fifth.example

Именованные списки могут иметь преимущество в производительности. Когда Exim маршрутизирует адрес или проверяет входящее сообщение, он кэширует результат проверки именованных списков. Итак, если у вас есть такая настройка, как следующая:

domains = +local_domains

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

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

18.5 Списки доменов

Списки доменов используются для указания наборов почтовых доменов для различных целей, например, какие домены являются локальными или какие домены приемлемы для ретрансляции. Вот (нереалистичный) пример списка доменов, в котором используются элементы нескольких разных типов:

domainlist local_domains = \
  @:\
  lib.unseen.edu.example:\
  *.foundation.fict.example:\
  \N^[1-2]\d{3}\.fict\.example$\N:\
  partial-dbm;/opt/penguin/example:\
  nis;domains.byname:\
  nisplus;[name=$domain,status=local],domains.org_dir

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

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

18.5.1 Элементы списка доменов

Существует ряд различных типов элементов, которые могут отображаться в списке доменов.

Соответствие локальному имени хоста

Если элемент состоит из одного символа @, он соответствует локальному имени хоста, указанному в параметре primary_hostname. Это позволяет использовать один и тот же файл конфигурации на нескольких разных хостах, отличающихся только своими именами. Следующий пример:

domainlist local_domains = @ : plc.com.example

гарантирует, что имя локального хоста является локальным доменом.

Сопоставление локальных IP-интерфейсов

Если элемент состоит из строки @[], он соответствует любому локальному IP-адресу интерфейса, заключенному в квадратные скобки, как в адресе электронной почты, который содержит литерал домена. Использование доменных литералов в современном Интернете исчезает, и это не рекомендуется.

Сопоставление доменов MX с локальным хостом

Если элемент состоит из строки @mx_any, он соответствует любому домену, в котором есть запись MX, указывающая на локальный хост, или на любой хост, указанный в hoststreat_as_local. Элементы @mx_primary и @mx_secondary аналогичны, за исключением того, что первый соответствует только тогда, когда основной целью MX является локальный хост, а второй — только тогда, когда первичная цель MX не является локальным хостом, а вторичная цель MX является. «Primary» означает запись MX с наименьшим значением предпочтения; конечно, их может быть больше одного.

Совпадение концов доменных имен

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

domainlist local_domains = *key.example

соответствует donkey.example, а также cipher.key.example.

Сопоставление по регулярному выражению

Если элемент начинается с символа циркумфлекса (^), он обрабатывается как регулярное выражение и сопоставляется с доменом с помощью функции сопоставления регулярных выражений. Поскольку списки всегда раскрываются перед сканированием, вы должны либо защитить регулярное выражение от раскрытия с помощью \N, либо экранировать любые символы обратной косой черты, доллара и фигурных скобок, чтобы предотвратить их интерпретацию раскрывателем строки (если вы действительно хотите построить регулярное выражение путем раскрытия).

Раскрытие происходит перед интерпретацией каждого элемента, поэтому \N можно поставить перед начальным циркумфлексом, как в следующем примере:

domainlist local_domains = \N^mta\d{3}\.plc\.example$\N

Это указывает, что любой домен, имя которого mta, за которым следуют три цифры, и .plc.example является локальным доменом. Циркумфлекс, вводящий регулярное выражение, рассматривается как часть выражения. Это означает, что он «привязывает» выражение к началу доменного имени. Однако вы можете начать с ^.*, чтобы разместить произвольные начальные символы, если вам это нужно. Описание регулярных выражений, которые поддерживает Exim, можно найти в приложении B.

Поиск по одному ключу

Если элемент начинается с имени типа поиска с одним ключом, за которым следует точка с запятой (например, dbm; или 1search;), оставшаяся часть элемента должна быть именем файла в формате, подходящем для типа поиска. Например, для dbm; это должен быть абсолютный путь:

hold_domains = dbm;/etc/holddomains.db

Соответствующий тип поиска выполняется в файле с использованием доменного имени в качестве ключа. Если поиск успешен, домен соответствует элементу.

Сопоставление с частичным поиском по одному ключу

Любому из имен типов поиска с одним ключом может предшествовать partial<N>-, где <n> является необязательным, например:

partial-dbm;/partial/domains

Это приводит к вызову логики частичного соответствия (16.5).

Сопоставление с поиском в стиле запроса

Если элемент начинается с имени типа поиска в стиле запроса, за которым следует точка с запятой (например, nisplus; или ldap;), оставшаяся часть элемента должна быть соответствующим запросом для типа поиска. Например:

domainlist local_domains = \
  mysql; select domain from localdomains where \
  domain='$domain';

Если поиск успешен, проверяемый домен соответствует элементу. Обратите внимание, что это не поиск списка доменов для тестирования; он проверяет один домен, выполняя запрос (который обычно относится к домену)[3].

  1. Если вы хотите использовать поиск для создания списка доменов, вы можете использовать обычный синтаксис расширения ${lookup...}.

Соответствие буквальному доменному имени

Если ни один из предыдущих случаев не применим, между элементом и доменом выполняется текстовое сравнение без учета регистра.

18.5.2 Данные поиска в списках доменов

Поиск в списках доменов — это способ получить ответ «да» или «нет» о принадлежности домена к определенному набору доменов. Данные, которые просматриваются как часть теста, обычно отбрасываются. Однако есть один случай, когда они сохраняются. Если опция domains или local_parts на роутере, или одно из условий с идентичными именами в ACL, соответствует посредством поиска, Exim устанавливает переменные $domain_data и $local_part_data, соответственно, на данные, которые были просмотрены. В роутере эти значения сохраняются в течение всего времени работы роутера и, если он направляет адрес на транспорт, также устанавливаются во время работы транспорта. В ACL значения сохраняются до конца оператора ACL.

18.6 Списки хостов

Списки хостов используются для управления тем, что разрешено делать удаленным хостам (например, использовать локальный хост в качестве ретранслятора или выполнять команду ETRN). Хосты можно идентифицировать двумя способами: по IP-адресу или по имени. В списке узлов некоторые типы элементов сопоставляются с IP-адресом, а некоторые — с именем. Вы должны быть особенно осторожны с этим, когда задействованы поиски с одним ключом, чтобы гарантировать, что правильное значение используется в качестве ключа.

18.6.1 Специальные шаблоны списка хостов

Если элемент списка хостов представляет собой пустую строку, он соответствует только тогда, когда не задействован удаленный хост. Это тот случай, когда на стандартный ввод поступает сообщение от локального процесса (с использованием или без использования SMTP), то есть когда соединение TCP/IP не используется. Пример этого можно увидеть в ACL в конфигурации по умолчанию, которая содержит этот оператор:

accept  hosts = :

Другой специальный элемент в списке хостов — это одиночная звездочка, которая соответствует любому хосту или не соответствует никакому хосту, как в этом примере:

auth_advertise_hosts = *

Ни IP-адрес, ни имя на самом деле не проверяются для этого элемента.

18.6.2 Проверка хоста по IP-адресу

Когда Exim получает SMTP-соединение от другого хоста, единственной надежной идентификацией, которую он изначально имеет для хоста, является его IP-адрес. Это используется для проверки типов элементов, описанных в этом разделе.

Соответствие IP-адресу

Если элемент представляет собой IP-адрес, он сравнивается с IP-адресом хоста субъекта. В ACL, например, следующий оператор:

accept hosts = 10.8.43.23

может использоваться для разрешения ретрансляции с хоста с этим конкретным IP-адресом.

Сопоставление IP-адреса локального интерфейса

Если шаблон — @[], он соответствует IP-адресу любого IP-интерфейса на локальном хосте. Например, если хост имеет один адрес внешнего интерфейса 10.45.23.56, эти два оператора ACL имеют одинаковый эффект:

accept hosts = 127.0.0.1 : 10.45.23.56
accept hosts = @[]

Соответствие замаскированному IP-адресу

Если элемент представляет собой IP-адрес, за которым следует косая черта и длина маски, например:

host_lookup = 10.11.42.0/24

он сопоставляется с IP-адресом узла-субъекта под заданной маской. Маска указывается в нотации CIDR, при этом ее значение интерпретируется как количество битов адреса, которые должны совпадать, начиная с самого старшего конца. Таким образом, вся сеть хостов может быть включена (или исключена) одним элементом.

Адреса IPv4 в списках хостов задаются в обычном формате «dotted-quad». Адреса IPv6 задаются в формате, разделенном двоеточиями, но двоеточия должны быть удвоены, чтобы не восприниматься как разделители элементов. В этом примере показаны оба вида адресов:

recipient_unqualified_hosts = 192.168.0.0/12: \
                              3ffe::ffff::836f::::/48

Двоеточия в адресах IPv6 должны удваиваться, только если такие адреса отображаются в списке узлов. Дублирование не требуется (и не должно выполняться), когда адреса IPv6 появляются в файле. Например:

recipient_unqualified_hosts = /opt/exim/unqualnets

может использовать файл, содержащий:

192.168.0.0/12
3ffe:ffff:836f::/48

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

recipient_unqualified_hosts = <+ 192.168.0.0/12+ \
                                 3ffe:ffff£:836f::/48

Сопоставление по замаскированному IP-адресу с поиском по одному ключу

Поиск по одному ключу с использованием IP-адреса в качестве основы для ключа может быть указан элементом следующей формы:

net<number>-<search-type>;<search-data>

Например:

net24-dbm;/etc/networks.db

Во-первых, IP-адрес узла-субъекта маскируется с использованием <number> в качестве длины маски. Затем из замаскированного значения создается текстовая строка, за которой следует маска, и она используется в качестве ключа для поиска. Например, если IP-адрес хоста — 192.168.34.6, в этом примере ищется ключ 192.168.34.0/24.

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

Сопоставление по немаскированному IP-адресу с поиском по одному ключу

Если поиск по одному ключу указан без маски, как в следующем примере:

net-cdb;/etc/spechosts

текстовая форма IP-адреса узла-субъекта используется без маски в качестве ключевой строки поиска. Это не то же самое, что указать net32 для адреса IPv4 или net128 для адреса IPv6, поскольку значение маски не включено в ключ. Однако адреса IPv6 по-прежнему преобразуются в несокращенную форму с использованием строчных букв с точками в качестве разделителей.

Сопоставление по IP-адресу в стиле запроса

Та же форма может использоваться для поиска в стиле запроса, как в следующем примере:

accept hosts = net-mysql;select host from relays \
               where ip='$sender_host_address'

Вы можете использовать оператор раскрытия строки mask, если хотите использовать замаскированные IP-адреса в таком поиске.

18.6.3 Проверка хоста с использованием прямого поиска

Если элемент в списке хостов является обычным доменным именем, например:

accept hosts = my.friend.example

Exim вызывает функцию поиска системного хоста, чтобы найти его IP-адреса. Обычно это вызывает прямой поиск имени в DNS. В некоторых случаях могут использоваться другие источники информации, такие как /etc/hosts, в зависимости от того, как настроена ваша операционная система. Результат сравнивается с IP-адресом проверяемого хоста. Основное имя локального хоста может быть включено в список хостов элементом, состоящим только из символа @; это позволяет использовать одну и ту же конфигурацию на нескольких хостах, отличающихся только своими именами.

18.6.4 Проверка хоста с помощью обратного поиска

Остальные типы элементов, которые могут отображаться в списках хостов, представляют собой шаблоны подстановочных знаков для сопоставления с именем хоста. Если хост еще не известен, Exim вызывает системную функцию, чтобы получить его с помощью IP-адреса. Обычно это вызывает обратный поиск DNS, хотя могут использоваться и другие источники информации, такие как /etc/hosts, в зависимости от конфигурации вашей операционной системы.

Если поиск терпит неудачу (то есть, если Exim не может найти имя хоста для IP-адреса), он ведет себя так, как будто хост не соответствует списку. Одним из побочных эффектов этого является то, что последующие элементы в списке не проверяются, даже если для них не требуется знать имя хоста[4]. По этой причине вы всегда должны помещать элементы, связанные с IP-адресами, на первое место, если это возможно. Предположим, у вас есть следующие настройки в ACL:

accept hosts = *.myfriend.example : 192.168.5.4

Когда приходит соединение с любого хоста, Exim должен найти имя хоста, прежде чем он сможет проверить первый элемент в списке. Если имя не может быть найдено, условие не выполняется, даже если IP-адрес хоста равен 192.168.5.4. Размещение элементов в обратном порядке обеспечивает успешное принятие для соединений с этого хоста, независимо от того, можно ли найти его имя. Это также более эффективно, потому что обратный поиск даже не предпринимается.

Если вы действительно хотите сначала выполнить проверку имени и по-прежнему распознавать IP-адрес в случае сбоя обратного поиска, вы можете переписать ACL следующим образом:

accept hosts = *.myfriend.example
accept hosts = 192.168.5.4

Если первый accept терпит неудачу, Exim переходит ко второму.

В некоторых случаях может быть желательно рассматривать хост как соответствующий списку, если обратный поиск не удался. Для этого в списке хостов на верхнем уровне может появиться специальный элемент +include_unknown; он не распознается в интерполированном файле. Если какие-либо последующие элементы требуют имя хоста, а обратный поиск завершается ошибкой, предполагается, что хост соответствует списку (то есть поведение противоположно значению по умолчанию). Например:

deny hosts = +include_unknown : *.enemy.example

запрещает доступ с любого хоста, чье имя соответствует *.enemy.example, но только если он может найти имя хоста по входящему IP-адресу. Это опасно, если вы действительно имеете дело со злонамеренным врагом, потому что блокировку можно легко обойти, отменив регистрацию вызывающих хостов. Однако, если вы должны принимать почту от незарегистрированных хостов, а также должны блокировать другие зарегистрированные хосты по имени, это средство может быть полезным.

В результате псевдонимов хосты могут иметь более одного имени. При обработке любого из следующих элементов проверяются все имена хостов.

  1. Порядок элементов в списке имеет значение из-за существования отрицательных элементов, поэтому неразрешимый элемент нельзя просто пропустить.

Сопоставление концов имен хостов

Если элемент в списке хостов начинается со звездочки, оставшаяся часть элемента должна совпадать с окончанием имени хоста. Например, *.b.c соответствует всем хостам, имена которых заканчиваются на .b.c. Звездочка может стоять только в начале элемента; эта специальная простая форма предоставляется, потому что это очень распространенное требование. Другие виды подстановочных знаков требуют использования регулярных выражений.

Сопоставление по регулярному выражению

Если элемент начинается с циркумфлекса, он считается регулярным выражением, которое сопоставляется с именем хоста. Например:

accept hosts = \N^{ab]\.c\.d$\N

соответствует одному из двух хостов a.c.d или b.c.d. Как и в случае со списком доменов, циркумфлекс интерпретируется как часть регулярного выражения.

Сопоставление по имени хоста

Если элемент имеет вид <search-type>;<filename-or-query>, как в следующем примере:

accept hosts = dbm;/host/accept/list

имя хоста ищется с использованием типа поиска и имени файла или запроса (соответственно). Если поиск успешен, элемент соответствует. Полученные данные не используются.

При использовании такого элемента с поиском по одному ключу в качестве ключей в файле должны быть указаны имена хостов, а не IP-адреса. Если вы хотите выполнять поиск на основе IP-адресов, вы должны указать перед типом поиска net- (18.6.2). Однако нет никаких причин, по которым вы не могли бы использовать два элемента в одном списке, один для поиска адреса, а другой для поиска имени, при этом оба используют один и тот же файл.

Для поиска в стиле запроса, что искать, указано явно в запросе, но Exim всегда гарантирует, что имя хоста доступно перед запуском запроса для этого типа шаблона. Если вы не используете имя хоста в своем запросе, вы должны использовать форму поиска net-, описанную ранее, чтобы Exim не искал имя хоста без необходимости.

18.7 Списки адресов

Списки адресов используются в ряде опций для изменения поведения Exim для определенных адресов отправителя или получателя. Например, в ACL у вас может быть следующий оператор, чтобы запретить доступ определенным отправителям:

deny senders = cleo@egypt.example : tony@egypt.example

Разделы rewrite и retry файла конфигурации содержат правила, которые требуют, чтобы элемент списка адресов был их первым компонентом. В этих случаях может появиться только один элемент, но он может быть любого типа, описанного в этом разделе.

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

senders = :

Наличие двоеточия создает пустой элемент. Если вы вообще не предоставляете никаких данных, список пуст и ничему не соответствует. В других случаях каждый элемент в списке адресов сопоставляется с почтовым адресом в форме <local_part>@<domain>.

Сопоставление по регулярному выражению

Если элемент начинается с циркумфлекса, регулярное выражение сопоставляется с полным адресом, используя весь элемент в качестве регулярного выражения. Например:

deny senders = \N^(cleo|tony)@egypt\.example$\N

Как и в случае списков доменов и хостов, циркумфлекс интерпретируется как часть регулярного выражения.

Сопоставление списка локальных частей для каждого домена

Если элемент начинается с @@, за которым следует элемент поиска с одним ключом (например, @@lsearch;/some/file), проверяемый адрес разбивается на локальную часть и домен. Домен ищется в файле. Если он не найден, совпадения нет. Если он найден, искомые данные обрабатываются как список элементов локальной части, разделенных двоеточием, каждый из которых по очереди сопоставляется с локальной частью субъекта. Как и во всех списках, разделенных двоеточиями в Exim, двоеточие может быть включено в элемент путем удвоения.

Поиск может быть частичным или может привести к поиску файла по умолчанию с ключом *. Элементы локальной части, которые ищутся, могут быть регулярными выражениями, начинаться с * или даже быть дальнейшими поисками. Они также могут быть независимо отвергнуты. Например, с:

deny senders = @@dbm;/etc/reject-by-domain

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

baddomain.example:  !postmaster : *

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

Если последний элемент в списке локальных частей начинается с правой угловой скобки, оставшаяся часть элемента берется в качестве нового ключа для поиска, чтобы получить продолжение списка локальных частей. Это называется цепочкой (chaining). Новый ключ может быть любой последовательностью символов. Таким образом, могут быть такие записи, как следующие:

hl.example.com: spammer1 : spammer2 : >*
h2.example.com: spammer3 : >*
*:       ^\d{8}$

Они определяют совпадение восьмизначных локальных частей для всех доменов в дополнение к конкретным локальным частям, перечисленным для каждого отдельного домена. Конечно, использование этой функции требует повторного поиска каждый раз, когда выполняется цепочка, но усилия, необходимые для обслуживания данных, сокращаются. Используя эту возможность, можно создавать циклы, и для того, чтобы их поймать, количество раз, когда Exim следует по цепочке, ограничено 50.

Стиль элемента @@<lookup> также можно использовать с поиском в стиле запроса, но в этом случае средство объединения в цепочку недоступно. Поиск может вернуть только один список локальных частей.

Сопоставление по поиску адреса

Полные адреса можно искать с помощью элемента, состоящего из типа поиска, точки с запятой и данных для поиска. Например:

deny senders = cdb;/etc/blocked.senders : \
  mysql;select address from blocked where \
  address='${quote_mysql:$sender_address}'

Для поиска с одним ключом Exim использует полный адрес в качестве ключа. Частичное совпадение использовать нельзя, оно игнорируется, если указано, с записью в журнал паники.

Сопоставление локальной части и домена отдельно

Если элемент содержит символ @, но не является регулярным выражением или поиском, как только что описано, локальная часть адреса субъекта сравнивается с локальной частью элемента, который может начинаться со звездочки. Если локальные части совпадают, домен проверяется точно так же, как и элемент в списке доменов. Например, домен может быть подстановочным, ссылаться на именованный список или быть поиском, как в следующем примере:

deny senders = *@*.spamsite.example: \
               *@+hostile_domains: \
               bozo@partial-lsearch;/list/of/dodgy/sites

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

Соответствие только домену

Если элемент не является одной из описанных выше синтаксических форм, то есть если элемент, который не является регулярным выражением или поиском, не содержит символ @, он сопоставляется с доменной частью адреса субъекта. Локальная часть не проверяется. Единственными двумя форматами, которые распознаются таким образом, являются литеральный домен или шаблон домена, начинающийся с *.

18.7.1 Регистр букв в списках адресов

Домены в адресах электронной почты всегда обрабатываются без учета регистра любых букв в их именах, но регистр локальных частей может иметь значение в некоторых системах (5.9). Однако RFC 2505 (Anti-Spam Recommendations for SMTP MTAs) предлагает сопоставлять адреса со списками блокировки без учета регистра. Так как большинство списков адресов в Exim используются для такого контроля, Exim пытается сделать это по умолчанию.

Доменная часть адреса всегда преобразуется в нижний регистр перед сопоставлением со списком адресов. Локальная часть по умолчанию преобразуется в нижний регистр, и любые сравнения строк выполняются без учета регистра. Это означает, что данные в самом списке адресов, в интерполированных файлах и в любом файле, который просматривается с помощью механизма @@, могут быть в любом случае. Однако ключи в файлах, которые просматриваются с помощью типа поиска, отличного от 1search (который работает без учета регистра), должны быть в нижнем регистре, поскольку эти типы поиска чувствительны к регистру.

Чтобы разрешить сопоставление списков адресов с учетом регистра, если строка +caseful включена в качестве элемента в список адресов, исходный регистр локальной части восстанавливается для всех последующих сравнений, а сравнения строк становятся чувствительными к регистру. Это не влияет на домен, который остается в нижнем регистре.

18.7.2 Списки локальных частей

Списки локальных частей сопоставляются так же, как и списки доменов, за исключением того, что специальные элементы, относящиеся к локальному хосту (@, @[], @mx_any, @mx_primary и @mx_secondary)), не распознаются. То есть поддерживаются не только буквальные совпадения, но и элементы, начинающиеся со звездочки, регулярные выражения и поиск.

Чувствительность к регистру в локальных списках частей обрабатывается так же, как и в списках адресов, и при необходимости можно использовать элемент +caseful.

Глава 19
Разное

Эта глава содержит ряд пунктов, которые не вписываются естественным образом в другие главы, но которые слишком малы, чтобы оправдать отдельные главы.

19.1 Вопросы безопасности

Мы используем слово «безопасность» для описания аспектов работы Exim, связанных с обеспечением безопасности сообщений и других данных, и позволяющих Exim выполнять привилегированные действия, которые не разрешены обычным пользовательским программам. Есть три основных вопроса:

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

В следующих разделах вопросы безопасности обсуждаются более подробно.

19.1.1 Использование привилегий root

Прежде чем мы углубимся в детали того, как Exim использует привилегию root, мы дадим краткий обзор используемых возможностей Unix.

Как Unix использует uid для управления привилегиями

С самого начала в Unix существовала концепция реального uid и эффективного uid для каждого процесса. Оба они устанавливаются в одно и то же значение, когда пользователь входит в систему. Действующий uid — это тот, который используется для проверки привилегий (например, доступ к файлу), и его можно изменить при запуске новой программы, установив флаг setuid в права владельца исполняемого файла. Это приводит к тому, что эффективный uid устанавливается на идентификатор владельца файла; реальный uid неизменен. Современные версии Unix также имеют сохраненный uid, для которого устанавливается то же значение, что и для эффективного uid, когда последний изменяется при запуске программы.

Способы, с помощью которых программы могут манипулировать этими uid во время их работы, не совсем одинаковы во всех Unix-подобных системах, но процесс, эффективным uid которого является root, может установить для всех uid любое значение, и любой процесс может изменить свой эффективный uid на реальный или сохраненный uid по своему усмотрению. Это означает, что существует два разных способа, которыми процесс с привилегиями root (тот, чей эффективный uid равен root) может отказаться от этой привилегии:

Временный отказ от привилегии является менее безопасным действием, поскольку ошибка может привести к ее восстановлению в неподходящий момент[1]. Ранние версии Exim использовали оба вида отказа от привилегий для различных целей, но Exim 4 использует только постоянный вид.

  1. Для тех, кто знаком с системными функциями Unix: постоянный отказ реализуется вызовом setuid(), тогда как временный отказ реализуется вызовом seteuid() (или, в некоторых системах, setresuid()).

Зачем Exim'у нужны привилегии root?

Exim делает две вещи, которые требуют, чтобы он был привилегированным:

Из-за этих требований двоичный файл Exim обычно имеет права root. В некоторых особых обстоятельствах (например, когда демон не используется и нет обычных локальных доставок), может быть возможно запустить его с setuid для какого-либо пользователя, отличного от root (обычно это пользователь Exim). Эта возможность будет обсуждаться позже, но в подавляющем большинстве установок Exim вывод ls -l команда должна выглядеть так:

-rwsr-xr-x  1 root  smd  560300 Jun 14 08:53 exim

То есть бинарник принадлежит пользователю root, а для владельца установлен флаг s. Это означает, что всякий раз, когда программа запускается, эффективный uid изменяется на root. Группа (в данном случае smd) обычно не имеет значения. Хотя он всегда запускается как root, Exim отказывается от привилегий root, когда он больше не нуждается в этом, меняя uid на пользователя Exim. В частности, он делает это при получении сообщений из любого источника.

19.1.2 Запуск локальных доставок от имени root

Обычно считается плохой идеей запускать локальные доставки от имени пользователя root, поскольку это позволяет избежать чрезмерных привилегий там, где они не нужны. Большинство инсталляций устанавливают root как псевдоним для системного администратора, что позволяет обойти эту проблему, но на всякий случай, если этого не сделать, конфигурация Exim по умолчанию содержит «защиту от срабатывания» в виде следующей настройки:

never_users = root

Всякий раз, когда Exim собирается запустить процесс доставки, он проверяет, является ли требуемый uid одним из перечисленных в never_users. Если это так, регистрируется ошибка и доставка откладывается.

19.1.3 Запуск непривилегированного exim

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

Если выбран второй подход, если Exim не вызывается из процесса root, он в конечном итоге работает с реальными uid и gid, установленными на те же, что и у вызывающего процесса, а эффективные uid и gid установлены на значения Exim'а. В идеале следует отбросить любую связь со значениями вызывающего процесса (то есть реальный и сохраненный uid и gid должны быть сброшены до действующих значений). Некоторые операционные системы имеют функцию, которая разрешает это действие для эффективного uid без полномочий root, но некоторые из них этого не делают. Из-за отсутствия стандартизации Exim не решает эту проблему. По этой причине, если вас беспокоит этот вопрос, возможно, лучше всего использовать первый подход.

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

Ограничения следующие:

19.2 Использование идентификации RFC 1413

RFC 1413 (Identification Protocol) понимается неправильно. Он определяет протокол (обычно называемый ident), с помощью которого сервер, получив соединение от клиента, может выполнить обратный IP-вызов клиенту и получить идентификационную информацию, относящуюся к исходному соединению. В контексте SMTP происходит следующая последовательность:

  1. Хост C (клиент) подключается к порту SMTP на хосте S (сервер) через произвольный порт.

  2. Хост S соединяется с идентификационным портом хоста C, передавая номер вызывающего порта хоста C.

  3. Хост C отправляет идентификационные данные хоста S, относящиеся к соединению SMTP.

  4. Хост S записывает данные с входящим сообщением.

Распространенным заблуждением является мнение, что информация должна быть полезна серверу. Это не так; это то, что сервер может записать и передать обратно администратору клиента, если есть запрос.

Рассмотрим большую общую машину с тысячами зарегистрированных пользователей, имеющих учетные записи для входа. Если пользователь этой системы устанавливает TCP/IP-соединение с другим хостом и каким-то образом злоупотребляет им, у менеджера общей системы при расследовании полученной жалобы будет гораздо более легкая задача, если вызываемый хост записал полученную идентификационную информацию RFC 1413 от вызывающего хоста.

Многие хосты просто отправляют имена для входа в ответ на соединения RFC 1413, и в этом случае виновник сразу же идентифицируется. Некоторые хосты содержат больше информации, например время и источник входа в систему, а по соображениям конфиденциальности некоторые хосты шифруют эту информацию. RFC 1413 помогает найти человека, ответственного за конкретное соединение TCP/IP в многопользовательской системе. Это не имеет значения в контексте однопользовательских клиентов.

Exim по умолчанию выполняет обратные вызовы RFC 1413; любая полученная идентификационная информация включается в строку журнала для каждого полученного входящего сообщения. Вы можете ограничить обратные вызовы Exim RFC 1413 определенными хостами, установив rfc1413_hosts. Этот параметр по умолчанию:

rfc1413_hosts = *

то есть обратные вызовы выполняются для всех хостов. К этим соединениям применяется тайм-аут, управляемый rfc1413_query_timeout. По умолчанию это 30s (30 секунд). Если установлено нулевое значение времени, соединения RFC 1413 не устанавливаются. Это рекомендуемый способ отключения этих обратных вызовов.

19.3 Привилегированные пользователи

Привилегированный пользователь — это тот, кому разрешено просить Exim делать то, что обычные пользователи не могут. Существует два разных типа действий, поэтому существует два разных класса привилегированных пользователей, называемых trusted (доверенными) пользователями и admin пользователями. В описаниях параметров командной строки в главе 20 ограничение для доверенных пользователей или пользователей с правами администратора отмечено для тех параметров, к которым оно применяется.

19.3.1 Доверенные пользователи

Доверенным пользователям разрешено переопределять определенную информацию при отправке сообщений через командную строку (то есть не через TCP/IP). Пользователь Exim и root автоматически становятся доверенными, а дополнительные доверенные пользователи могут быть определены опцией trust_users, например:

trusted_users = uucp : majordom

Кроме того, если текущая группа или любая из дополнительных групп процесса, вызывающего Exim, является группой Exim или любой группой, указанной в опции trust_groups, то вызывающая программа является доверенной.

Установка отправителя локально отправленного сообщения

Когда обычный (не доверенный) пользователь отправляет сообщение локально, адрес отправителя создается из имени входа реального пользователя вызывающего процесса и определяемого домена по умолчанию. Этот адрес указан как адрес отправителя в конверте сообщения. Он также помещается в добавленную строку заголовка Sender: (14.11.4), если заголовок From: его не содержит, хотя это можно отключить, установив для параметра local_from_check значение false.

Доверенный пользователь может переопределить адрес отправителя с помощью параметра -f. Например:

exim -f 'alice@carroll.example'

заставляет адрес отправителя быть alice@carroll.example. Если вы используете программное обеспечение списка рассылки, которое является внешним по отношению к Exim, вы должны сделать так, чтобы оно работало как доверенный пользователь, чтобы оно могло указывать адреса отправителя при передаче сообщений в Exim для доставки подписчикам.

Происхождение концепции доверенных пользователей лежит в многопользовательских системах, где администрация хочет гарантировать, что аутентифицированный адрес отправителя присутствует в каждом отправляемом сообщении[2]. В такой среде доверенные пользователи — это те, кому разрешено «подделывать» адреса отправителей при отправке сообщений с использованием интерфейса командной строки.

На небольших рабочих станциях, где все, что делается, может быть поручено нескольким людям, различие между доверенными и недоверенными пользователями менее полезно, особенно в случае адресов отправителей. Если вы используете такую систему, вы можете снять ограничение на использование -f. Вы можете сделать это, установив параметр untrusted_set_sender. Значение untrusted_set_sender — это список адресов, содержащий шаблоны, соответствующие адресам, которые разрешено устанавливать ненадежным пользователям. Если вы хотите разрешить ненадежным пользователям устанавливать адреса отправителей конвертов без ограничений, вы можете установить это следующим образом:

untrusted_set_sender = *

С другой стороны, следующий пример ограничивает пользователей установкой отправителей, которые начинаются с их идентификатора входа, за которым следует дефис:

untrusted_set_sender = ^$sender_ident-

Переменная $sender_ident содержит идентификатор входа для локально отправленных сообщений.

  1. Для сообщений, отправляемых напрямую через TCP/IP из пользовательских процессов, протокол ident может помочь обеспечить аналогичную отчетность (19.2).

Установка другой информации в локально отправленном сообщении

В дополнение к адресу отправителя доверенный пользователь может предоставить дополнительную информацию, такую как IP-адрес, как если бы сообщение было получено с удаленного хоста (20.2).

Эти средства позволяют администраторам вводить сообщения с «удаленными» характеристиками с помощью командной строки. Это может быть полезно при передаче сообщений, пришедших через какую-либо другую транспортную систему, например UUCP, или при повторном добавлении сообщений, которые изначально были доставлены антивирусному сканеру (5.10.1).

19.3.2 Пользователи-администраторы

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

Если вы хотите, вы можете «открыть» два действия, обычно разрешенные только пользователям-администраторам, чтобы любой пользователь мог их запросить:

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

admin_groups = sysadmin

делает каждого пользователя в группе sysadmin администратором Exim. Тем не менее, есть преимущество сделать это другим способом (т.е. явно добавить всех ваших администраторов в группу Exim). Если вы сделаете это, и если вы сделаете так, чтобы спул и лог-файлы Exim'а имели режим 0640, это даст администраторам доступ на чтение к этим файлам, что необходимо, если они хотят запустить программу монитора eximon или просмотреть лог-файлы напрямую.

19.4 Соответствие RFC

Первоначальные RFC, определяющие базовые почтовые службы Интернета, устарели. RFC 821 и 822 были опубликованы в 1982 году; некоторые разъяснения были опубликованы в RFC 1123 в 1989 году. Последующие RFC в основном касались добавления таких функций, как MIME, и расширения протокола SMTP.

Интернет резко изменился с 1982 года, и MTA должны были измениться вместе с ним, в некоторых случаях принимая новые соглашения, которых нет в RFC, а в других предпочитая игнорировать рекомендации RFC или ослабляя их ограничения. Некоторые, но не все, эти изменения включены в пересмотренные версии RFC 821 и 822. Они были опубликованы как RFC 2821 и 2822 в апреле 2001 г.

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

Есть ряд причин, по которым Exim не соответствует строго RFC. Некоторые из них очень незначительны, другие вы можете контролировать, устанавливая параметры, но некоторые из них имеют основополагающее значение для работы программы и не могут быть отключены. Эти различия отражают предубеждения автора.

<р4>19.4.1 8-битные символы

Хотя TCP/IP всегда был 8-битной транспортной средой, RFC для почты по-прежнему настаивают на том, что почта является 7-битной службой. Символы с установленным старшим битом (то есть со значением больше 127) запрещены.

Передача 8-битного материала может быть согласована в некоторых обстоятельствах, но в других случаях предполагается, что MTA каким-то образом кодирует 8-битные символы перед их передачей. Обратите внимание, что это относится не к двоичным вложениям (которые уже закодированы в 7-битные символы MUA, создавшим сообщение), а скорее к «сырым» 8-битным символам, полученным MTA. Наиболее распространенная причина, по которой они встречаются, — это использование акцентированных и других специальных букв в европейских языках и именах.

Требование к MTA не передавать 8-битные символы без специальных действий поднимает технические проблемы и вопросы принципа проектирования. Если MTA получил сообщение, содержащее 8-битные символы, а удаленный MTA, которому он хочет отправить сообщение, не указал поддержку 8-битных передач (что является расширением SMTP), передающий MTA должен выбрать одну из трех возможностей:

Строгое соблюдение RFC допускает только первые два из них. Однако первая не очень полезна, а вторая вполне может превратить сообщение в форму, которая не будет корректно отображаться для конечного получателя[3]. Однако нарушение правил (отправка 8-битных символов как есть) с высокой вероятностью приведет к ожидаемому результату, а именно к передаче этих символов от отправителя к получателю.

Чтобы принять какое-либо решение о 8-битных символах, MTA должен проверить тело сообщения на их наличие. Некоторые люди (включая автора) твердо убеждены, что работа MTA заключается в перемещении сообщений, а не в трате ресурсов на проверку или изменение их содержимого. По этой причине Exim является «8-bit clean». Он не изменяет тела сообщений и не обращает внимания на содержащиеся в них 8-битные символы; они транспортируются в неизмененном виде.

Однако есть вариант, связанный с 8-битными символами. Когда Exim действует как сервер, он с радостью принимает 8-битные символы в сообщениях в соответствии с только что описанной философией, но не все клиенты готовы отправлять такие символы так, как это делает Exim. RFC определяют расширение SMTP под названием 8BITMIME для передачи 8-битных данных. Если для параметра accept_8bitmime установлено значение true в файле конфигурации времени выполнения Exim, Exim объявляет расширение 8BITMIME в своем ответе на команду EHLO. Это приводит к тому, что некоторые клиенты отправляют 8-битные данные без изменений, а не кодируют их; они используют параметр BODY= в командах MAIL, чтобы указать это. Exim распознает этот параметр, но никак не влияет на его действия. Таким образом, установка accept_8bitmime — это всего лишь способ убедить клиентов не кодировать 8-битные данные. Exim обрабатывает такие данные одинаковым образом, независимо от того, используется параметр BODY или нет.

  1. Этим печально известно преобразование сообщений в формат «quoted-printable».

19.4.2 Синтаксис адреса

Синтаксически неверные адреса, как в конвертах, так и в строках заголовков, — удручающе распространенное явление. Exim выполняет проверку синтаксиса для всех адресов RFC 2821, полученных в SMTP-командах, но не проверяет строки заголовков, если инструкция

verify = header_syntax

указывается в ACL или извлекает адреса конвертов из строк заголовков в результате использования параметра -t.

Встроенные расширения синтаксиса адреса

Всегда допускается ряд расширений синтаксиса, указанного в RFC:

  1. Exim принимает строку заголовка, такую как:

    To: A.N.Other <ano@somewhere.example>

    что строго недопустимо, поскольку точка является специальным символом в RFC 2822; строка действительно должна быть:

    To: "A.N.Other" <ano@somewhere.example>

    но многие почтовые программы допускают форму без кавычек.

  2. Строго говоря, локальная часть без кавычек не может начинаться или заканчиваться точкой или содержать соседние точки, например:

    .ABC@somewhere.example
    P.H.@somewhere.example
    A..Z@somewhere.example
    

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

  3. «Пары в кавычках» в локальных частях без кавычек, например:

    abc\@xyz@somwhere.example

    разрешены RFC 2821, но не RFC 2822; для простоты Exim всегда их принимает.

  4. При чтении команд SMTP MAIL и RCPT Exim не требует, чтобы адреса были заключены в угловые скобки.

Настраиваемые расширения синтаксиса адреса

Два других расширения адреса можно включить, установив параметры:

  1. Неправильно настроенные почтовые программы иногда отправляют адреса в дополнительных парах угловых скобок, как в следующем примере:

    MAIL FROM: <<xyz@bad.example>>

    Обычно это вызывает синтаксическую ошибку, но если для strip_excess_angle_brackets установлено значение true, Exim удаляет лишние скобки.

  2. Использование DNS точки в конце для обозначения полного домена иногда вызывает путаницу и приводит к использованию почтовых адресов, заканчивающихся точкой, как в следующем примере:

    dotty@dot.example.

    Опять же, это обычно вызывает синтаксическую ошибку, но если strip_trailing_dot установлена в true, завершающая точка молча удаляется.

Литеральные адреса домена

RFC разрешают использование адресов с доменными литералами (domain literal), которые имеют следующую форму:

shirley@[10.8.3.4]

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

Использование доменных литералов настоятельно не рекомендуется. Это разрешено только как средство обхода временных системных ограничений, таких как неполные таблицы имен.

В современном Интернете адресация сообщений конкретным хостам по их IP-адресам многими рассматривается как крайне нежелательная. По этой причине Exim по умолчанию не распознает синтаксис доменных литералов. Если вы хотите разрешить использование этой функции, вы должны установить для allow_domain_literals значение true в конфигурации времени выполнения. Это просто позволяет распознать синтаксис; вы также должны настроить свою конфигурацию, чтобы адреса доменных литералов маршрутизировались соответствующим образом.

Исходные маршрутизируемые адреса

Наконец, что касается синтаксиса адресов, Exim распознает так называемые «source routed» адреса в форме:

@relay1,@relay2,@relay3:user@domain

Однако использование таких адресов не рекомендуется, начиная с RFC 1123, и MTA имеет право игнорировать всю информацию о маршрутизации и рассматривать такой адрес как:

user@domain

Это именно то, что делает Exim.

19.4.3 Канонизация адресов

Когда почтовый домен является именем записи CNAME в DNS, исходные RFC предполагают, что MTA должен автоматически менять его на «каноническое имя» по мере обработки сообщения, и существуют агенты передачи сообщений, которые вносят это изменение. Однако новые RFC не содержат этого предложения, и Exim не выполняет эту перезапись.

19.4.4 Работа с поврежденными записями MX

Правая часть записи MX определяется как имя хоста. Некоторые администраторы DNS не понимают этого и настраивают записи MX с IP-адресами справа, например:

clueless.example.  MX  1  192.168.43.26

К сожалению, существуют сломанные MTA, которые не возражают против этих записей, что заставляет людей думать, что они всегда будут работать. Exim не является одним из них; он рассматривает правую часть как доменное имя, пытается найти его записи адресов и, естественно, терпит неудачу[4].

Если вы находитесь в ситуации, когда вам просто нужно доставить почту на такие домены (возможно, вы хотите послать сообщение администратору почты, указывающее на ошибку), вы можете заставить Exim вести себя неправильно, установив значение allow_mx_to_ip в true в конфигурации времени выполнения. Этот параметр не рекомендуется для общего использования.

  1. Однако он замечает, что происходит, и добавляет соответствующий комментарий к сообщению об ошибке.

19.4.5 Терминаторы строки в SMTP

Спецификация SMTP утверждает, что строки заканчиваются двухсимвольной последовательностью, состоящей из возврата каретки (CR) и перевода строки (LF), и это то, что Exim использует, когда отправляет SMTP. Однако для входящего SMTP есть клиенты, которые, как известно, нарушают правила, просто используя перевод строки для завершения строки. По этой причине Exim принимает такой ввод.

19.4.6 Синтаксис HELO и EHLO

Одной из наиболее распространенных ошибок в клиентских реализациях SMTP является отправка синтаксически недопустимых команд EHLO или HELO. Наиболее распространенной ошибкой является использование символов подчеркивания в имени хоста, которое является аргументом команды. Exim отклоняет синтаксически некорректные команды EHLO и HELO по умолчанию, но есть некоторые опции, которые можно использовать для изменения этого поведения (14.5.3).

19.5 Временные метки

Exim использует метку времени для каждой строки, которую он записывает в любой из своих лог-файлов, и для каждого заголовка Received:, который он создает. По умолчанию эти метки времени находятся в местном часовом поясе. Однако вы можете изменить это, установив параметр timezone. Например, если вы установите:

timezone = EST

метки времени будут указаны по Eastern Standard Time. Если вы хотите, чтобы все метки времени были в формате Universal Coordinated Time (UTC, также известного как GMT), этот параметр:

timezone = UTC

К сожалению, по-видимому, нет стандартного способа, которым программа в Unix-подобной системе может указать использование реального локального времени, не зная местного часового пояса. Это можно сделать в одних операционных системах, но не в других. По этой причине настройка по умолчанию для timezone берется из настройки переменной окружения TZ во время сборки Exim, в надежде, что в большинстве случаев это будет правильно.

Если TZ не установлен, когда Exim собирается, или timezone установлен в пустую строку, Exim удаляет любую существующую переменную TZ из окружения, когда он вызывается. В GNU/Linux, Solaris и операционных системах, производных от BSD, это приводит к использованию реального времени.

19.6 Проверка места в спуле

Проверка доступного места в спуле упоминалась ранее в связи с параметром SIZE команды MAIL (13.3.1). Это только один частный случай, в котором это происходит. Существует четыре варианта, которые запрашивают проверку дисковых ресурсов перед принятием нового сообщения.

Если значение check_spool_space или check_spocl_inodes больше нуля, например:

check_spool_space = 50M
check_spocl_inodes = 100

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

Если вы настроили Exim для записи своих файлов журнала в другой раздел для файлов спула, вы можете установить check_log_space и check_log_inodes таким же образом, чтобы проверить этот раздел.

Если места или индексных узлов меньше, чем запрошено, Exim отказывается принимать входящую почту. В случае ввода по протоколу SMTP это делается путем выдачи временного ответа об ошибке 452 на команду MAIL. Если в команде MAIL есть параметр SIZE, его значение добавляется к значению check_spool_space, и проверка выполняется, даже если check_spool_space равно нулю, если только smtp_check_spool_space не установлено в значение false.

Для не-SMTP-ввода и для пакетного SMTP-ввода тест выполняется при запуске: при сбое сообщение записывается в стандартный поток ошибок, и Exim завершает работу с ненулевым кодом, так как он, очевидно, не может отправить сообщение об ошибке любого типа.

19.7 Управление поиском DNS

В обычной комплектации. Exim широко использует DNS при обработке почты. В большинстве случаев это «просто случается», и вам не нужно беспокоиться о деталях поиска DNS. Однако проблемы с DNS не совсем неизвестны; иногда их можно облегчить, изменив способ, которым Exim выполняет поиск в DNS.

В то время как сама DNS может хранить доменные имена, содержащие практически любые символы, домены, используемые в электронной почте, ограничены буквами, цифрами, дефисами и точками. Было замечено, что некоторые резолверы DNS выдают временные ошибки, если их просят найти доменное имя (например, для записи MX), которое содержит другие символы. Чтобы избежать этой проблемы, Exim проверяет доменные имена перед передачей их резолверу, сопоставляя их с регулярным выражением, заданным dns_check_names_pattern. Значение по умолчанию:

dns_check_names_pattern =
  (?i)^(?>(?(1)\.|())[^(\W_](?>[a-z0-9-]*[^\W_])?)+$

который разрешает только буквы, цифры и дефисы в компонентах доменного имени и требует, чтобы они не начинались и не заканчивались дефисом. Если имя содержит недопустимые символы, Exim ведет себя так, как если бы поиск DNS вернул «not found». Это поведение проверки можно подавить, установив dns_check_names_pattern в пустую строку.

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

Наконец, параметры dns_retrans и dns_retry могут использоваться для установки параметров повторной передачи и повторной попытки для поиска DNS. Нулевые значения (значения по умолчанию) оставляют системные настройки по умолчанию без изменений. Значение dns_retrans — это время в секундах между повторными попытками, а dns_retry — количество повторных попыток. Неясно, как именно эти настройки влияют на общее время, которое может занять поиск DNS.

19.8 Обработка рикошетов

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

19.8.1 Ответ на рикошеты

Когда Exim генерирует рикошет, он вставляет строку заголовка From:, определяющую отправителя как Mailer-Daemon в подходящем домене по умолчанию. Например:

From: Mail Delivery System <Mailer-Daemon@myhost.example>

Опыт показывает, что многие люди либо случайно, либо по незнанию отвечают на такие сообщения[5]. Если вы хотите видеть эти сообщения, обычно вы должны сделать так, чтобы mailer-daemon был псевдонимом для postmaster. Еще одна вещь, которую вы можете сделать, это установить errors_reply_to, который предоставляет текст для строки заголовка Reply-To: в рикошетах. Например:

errors_reply_to = postmaster@myhost.example
  1. Кроме того, некоторые программы, в нарушение RFC, генерируют автоматические ответы на рикошеты, извлекая адрес из строк заголовка.

19.8.2 Копирование рикошетов

Иногда требуется отслеживать рикошеты, которые создает Exim. Опцию errors_copy можно использовать для того, чтобы копии локально сгенерированных рикошетов, отправляемых на определенные адреса, копировались на другие адреса. Значение представляет собой список элементов, разделенных двоеточием; каждый элемент состоит из шаблона и списка адресов, разделенных пробелами. Если шаблон соответствует получателю рикошета, сообщение копируется на адреса в списке. Элементы сканируются по порядку, и как только совпадение найдено, дальнейшие элементы не проверяются. Например:

errors_copy = spgr@mydomain   postmaster@mydomain :\
              rqps@mydomain  postmaster@mydomain,\
                              hostmasteremydomain

делает копии любых отказов, отправленных на spgr@mydomain или rqps@mydomain. Отказы копируются на postmaster@mydomain в обоих случаях; те, что для rgps@mydomain, также копируются в hostmaster@mydomain. Чтобы отправлять копии всех рикошетов почтовому мастеру, вы можете использовать:

errors_copy = *@*  postmaster

В каждом элементе шаблоном может быть любой отдельный элемент, который может появиться в списке адресов. Список адресов раскрывается и должен заканчиваться списком, разделенным запятыми. Переменные расширения $local_part и $domain задаются от исходного получателя сообщения об ошибке, и если в шаблоне есть какие-либо совпадения с подстановочными знаками, переменные расширения $0, $1 и т. д. устанавливаются обычным образом.

19.8.3 Предупреждающие сообщения о задержке

Если сообщение задерживается (то есть, если оно остается в очереди Exim'а в течение длительного времени), Exim посылает предупреждающее сообщение отправителю с интервалами, заданными опцией delay_warning, при соблюдении определенных условий (описанных ниже). Значение по умолчанию для delay_warning — 24 часа; если оно установлено на нулевой временной интервал, предупреждения не отправляются. Данные представляют собой разделенный двоеточием список временных периодов, через которые следует отправлять предупреждающие сообщения. Можно повторять до десяти раз. Если сообщение находилось в очереди дольше, чем в прошлый раз, последний интервал между этими периодами используется для вычисления последующих периодов предупреждения. Например, с:

delay_warning = 1h

предупреждения отправляются каждый час, тогда как с:

delay_warning = 4h:8h:24h

первое сообщение отправляется через 4 часа, второе — через 8 часов, а последующие — каждые 16 часов. Чтобы прекратить предупреждения после заданного времени, установите большое последующее время, например:

delay_warning = 4h:24h:99w

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

Эта строка раскрывается во время отправки предупреждающего сообщения. Если все отложенные адреса имеют один и тот же домен, он устанавливается в $domain во время расширения; в противном случае $domain пуст. Если результатом расширения является принудительный сбой, пустая строка или строка, соответствующая любому из значений 0, no или false (сравнение выполняется без учета регистра), предупреждающее сообщение не отправляется. Значение по умолчанию для опции:

delay_warning_condition = \
  ${if match{$h_precedence:}{(?i)bulk|list|junk}{no}{yes}}

которое подавляет отправку предупреждений для сообщений, содержащих bulk, list или junk в строке заголовка Precedence:. Это охватывает большинство списков рассылки.

19.8.4 Настройка рикошетов

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

Если установлен bounce_message_text, его содержимое, которое не раскрывается, включается в сообщение по умолчанию сразу после «This message was created automatically by mail delivery software». Например:

bounce_message_text = If you don’t understand it, please ask your \
                      postmaster for help.

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

Каждый элемент текста, который считывается из файла, раскрывается, и здесь можно использовать две переменные расширения: $bounce_recipient устанавливается получателю сообщения об ошибке во время его создания, а $return_size_limit содержит значение параметра return_size_limit, округленное до целого числа. Элементы должны появляться в файле в следующем порядке:

Состояние по умолчанию (bounce_message_file не установлен) эквивалентно следующему файлу, в котором шестой элемент пуст. Строка Subject: и строка, начинающаяся с «A message», были разделены, чтобы поместиться на странице:

Subject: Mail delivery failed
  ${if eq{$sender_address}{Sbounce_recipient}
  {: returning message to sender}}
****
This message was created automatically by mail delivery software (Exim).

A message
${if eq{$sender_address}{$bounce_recipient}{that you sent }{sent by

  <$sender address>

}}could not be delivered to all of its recipients.
This is a permanent error. The following address(es) failed:
****
The following text was generated during the delivery attempt(s):
****
------ This is a copy of the message, including all the headers. ------
****
------ The body of the message is $message _size characters long; only
------ the first $return_size limit or so are included here.
****

19.8.5 Настройка предупреждающих сообщений

Текст сообщений с предупреждением о задержке (отправляемых в результате опции delay_warning) можно настроить аналогично сообщениям о возврате. Вы можете установить для warn_message_file имя файла шаблона, который в данном случае содержит только три текстовых раздела:

Во время расширения этого файла $warn_message_delay устанавливается на время задержки в одной из форм <n> minutes или <n> hours, а $warn_message_recipients содержит список получателей предупреждающего сообщения. Их может быть больше одного, если несколько адресов получателей имеют разные настройки error_to на роутерах, которые их обработали. Состояние по умолчанию эквивалентно файлу:

Subject: Warning: message $message_id_delayed $warn_message_delay
****
This message was created automatically by mail delivery software (Exim).

A message
${if eq{$sender_address}{$warn_message_recipients}{that you sent }
{sent by

  <$sender_address>

}}has not been delivered to all of its recipients after
more than $warn_message delay on the queue on $primary_hostname.

The message identifier is:     $message_id
The subject of the message is: $h_subject
The date of the message is:    $h_date

The following address(es) have not yet been delivered:
****
No action is required on your part. Delivery attempts will continue for
some time, and this warning may be repeated at intervals if the message
remains undelivered. Eventually the mail delivery software will give up,
and when that happens, the message will be returned to you.

19.9 Прочие элементы управления

Этот раздел содержит краткие описания некоторых второстепенных опций, которые не заслуживают отдельного раздела, но могут представлять общий интерес. Справочное руководство Exim описывает дополнительные, миноритарные опции.

max_username_length (integer, default = 0)

Недостаток некоторых операционных систем состоит в том, что они усекают аргумент getpwnam() (функция, которая считывает информацию об имени пользователя) до восьми символов вместо того, чтобы возвращать «no such user» для более длинных имен. Если для этой опции установлено значение больше нуля, любая попытка вызвать getpwnam() с аргументом, который длиннее его значения, будет вести себя так, как если бы getpwnam() не удалось выполнить.

received_headers_max (integer, default = 30)

Когда сообщение должно быть доставлено, подсчитывается количество заголовков Received:. Если оно больше этого параметра, предполагается, что возникла почтовая петля, доставка прерывается и генерируется сообщение об ошибке.

smtp_banner (string, default = built-in)

Эта строка, которая раскрывается при каждом использовании, выводится как начальный положительный ответ на SMTP-соединение. Значение по умолчанию:

smtp_banner = $primary_hostname ESMTP Exim $version_number $tod_full

Неспособность расширить строку приводит к тому, что Exim записывает в свой журнал паники и немедленно завершает работу. Если вы хотите создать многострочный ответ на начальное SMTP-соединение, используйте \n в строке в соответствующих точках, но не в конце. Обратите внимание, что код 220 не включен в эту строку. Exim добавляет его автоматически (несколько раз в случае многострочного ответа).

smtp_receive_timeout (time, default = 5m)

Это устанавливает значение тайм-аута для приема SMTP. Если строка ввода (команда SMTP или строка данных) не получена в течение этого времени, SMTP-соединение разрывается и сообщение игнорируется. Для ввода, отличного от SMTP, время ожидания приема контролируется параметром receive_timeout.

Глава 20
Интерфейс командной строки для Exim

Всякий раз, когда Exim вызывается, ему передаются опции и аргументы, определяющие, что вызывающий хочет сделать. Поскольку таким образом вы можете вызывать Exim из оболочки, это называется интерфейсом командной строки (command-line interface). На практике, большинство вызовов Exim исходят непосредственно из других программ, таких как MUA, и не включают реальную «командную строку». Однако варианты и аргументы те же.

Многие параметры командной строки совместимы с Sendmail, поэтому Exim может быть заменой, но есть дополнительные параметры, специфичные для Exim. Некоторые опции могут быть использованы только тогда, когда Exim вызывается привилегированным пользователем, и они отмечены далее.

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

Управление режимом ввода
Варианты запуска процессов для получения входящих сообщений
Дополнительные данные сообщения
Варианты предоставления информации, которая будет включена во входящее сообщение, отправленное локально
Немедленный контроль доставки
Параметры для управления доставкой локально отправленного сообщения сразу по прибытии, возможно, в зависимости от типа адреса получателя.
Маршрутизация ошибок
Параметры для управления тем, как сообщается об ошибках в локально отправленном сообщении.
Процессы запуска очереди
Параметры запуска обработчиков очередей и выбора сообщений, которые они обрабатывают
Переопределение конфигурации
Варианты переопределения обычного файла конфигурации
Отслеживание Exim
Опции для проверки сообщений в очереди
Управление сообщениями
Варианты принудительной доставки и других действий с сообщениями
Тестирование
Параметры для тестирования обработки адресов, файлов фильтров, расширения строк и правил повтора.
Отладка
Опции для отладки Exim и его настройки
Внутреннее
Опции, которые полезны только тогда, когда один экземпляр Exim вызывает другой
Прочее
Несколько странностей
Совместимость с Sendmail
Опции, которые распознаются, потому что они используются Sendmail, но которые не делают ничего полезного в Exim

В этой главе мы обсудим варианты по функциональным группам. Вы можете найти полный список опций в алфавитном порядке в справочном руководстве.

20.1 Управление режимом ввода

Четыре взаимоисключающих параметра определяют способ получения сообщений.

20.1.1 Запуск процесса демона

Если указан параметр -bd, запускается процесс-демон для получения сообщений от удаленных хостов через соединения TCP/IP. Обычно он прослушивает порт SMTP (25), но с помощью опции -oX можно указать другой порт. Например:

exim -bd -oX 1225

запускает демона, который прослушивает порт 1225. Это может быть полезно для некоторых нестандартных приложений, а также для тестирования Exim как демона, не нарушая работающую почтовую службу. Подробное обсуждение демона было дано ранее (11.7). Только пользователи с правами администратора могут запускать процессы демона.

20.1.2 Интерактивный прием SMTP

Если указано -bs, запускается процесс приема, который читает SMTP-команды на своем стандартном вводе и записывает ответы на свой стандартный вывод. Эта опция может использоваться любым локальным процессом; однако, если какие-либо сообщения отправляются во время сеанса SMTP, отправители, указанные в командах MAIL, игнорируются, если вызывающий не является доверенным. Параметр -bs также используется для запуска процессов приема из inetd для получения почты от удаленных хостов в качестве альтернативы использованию демона. Необходимая запись в /etc/inetd.conf должна быть следующей:

smtp stream tcp nowait /usr/exim/bin/exim in.exim -bs

Exim может определить разницу между двумя вариантами использования -bs, проверив стандартный ввод. Если есть связанный IP-адрес, вход должен быть сокетом, и процесс должен быть запущен как inetd. В этом случае учитываются адреса отправителей из команд MAIL.

Опцию -os можно использовать для установки времени ожидания для получения SMTP-сообщения; это переопределяет параметр конфигурации smtp_receive_timeout.

20.1.3 Пакетный прием SMTP

Если указано -bS, запускается процесс приема, который читает SMTP-команды на своем стандартном вводе, но не записывает никаких ответов. Это так называемый «пакетный SMTP», который на самом деле представляет собой еще один способ вставки сообщений в неинтерактивном формате. Это обычно используется для сообщений, полученных другими транспортными механизмами, такими как UUCP, или для сообщений, которые были временно сохранены в файлах. Опять же, отправители, указанные в командах MAIL, игнорируются, если вызывающий не является доверенным. Более подробная информация об обработке всех форм SMTP приведена в главе 13.

20.1.4 Прием не-SMTP

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

exim theodora@byzantium.example
<message>
.

где <message> содержит все необходимые строки заголовков RFC 2822, хотя Exim добавляет определенные заголовки, если они отсутствуют. По умолчанию сообщение завершается либо концом файла (о чем можно сигнализировать с терминала, набрав CTRL-D), либо строкой, содержащей одну точку, как показано здесь. Вторая форма завершения отключена, если присутствует -i или -oi, и ее следует использовать всякий раз, когда сообщения неизвестного содержания отправляются с помощью этого средства. В противном случае строка в сообщении, состоящая из одной точки, приводит к усечению сообщения.

Параметр -t предоставляет альтернативный способ указания получателей конверта сообщения. Если он присутствует, подразумевается -bm, но получатели берутся из строк заголовка To:, Cc: и Bcc:, а не из аргументов команды. Затем все строки заголовка Bcc: удаляются из сообщения. Например:

exim -t
From: caesar@rome.example
To: theodora@byzantium.example
Bec: cleopatra@cairo.example
...

отправляет сообщение для доставки на адреса theodora@byzantium.example и cleopatra@cairo.example; ни одна из копий не будет содержать строку Bcc:. Это единственное обстоятельство, при котором Exim удаляет строки Bcc:. Если они присутствуют в сообщениях, полученных через другие интерфейсы, они остаются нетронутыми.

Если адреса предоставляются в качестве аргументов команды при использовании -t, есть две возможности: их можно добавить или вычесть из списка адресов, полученного из строк заголовка. По умолчанию Exim следует поведению, задокументированному для многих версий Sendmail; он вычитает их из списка. Кроме того, если один из других адресов впоследствии генерирует один из адресов аргументов в результате псевдонима или переадресации, он также отбрасывается. Например:

exim -t brutus@rome.example
From: caesar@rome.example
To: anthony@rome.example
Cc: senate-list@rome.example
...

отправляет сообщение, которое не доставляется на адрес brutus@rome.example, даже если этот адрес указан в расширении senate-list. Конечно, это работает только в том случае, если псевдонимы развернуты на одном и том же хосте; если сообщение отправляется на другой хост с неповрежденным адресом senate-list@rome.example, исключение теряется. Поэтому эта функция кажется мало полезной.

На практике ряд версий Sendmail не соответствует документации. Вместо этого они добавляют адреса аргументов в список получателей. Exim можно заставить вести себя таким образом, установив:

extract_addresses_remove_arguments = false

в его конфигурационном файле. Когда это сделано, адреса аргументов, которые также не отображаются в заголовках To: или Cc:, ведут себя как дополнительные получатели Bcc:.

Exim ожидает, что сообщения, отправленные с использованием -bm или -t, будут содержать строки, заканчивающиеся одним символом перевода строки, согласно обычному соглашению Unix, и большинство пользовательских агентов, использующих интерфейс, соответствуют этому использованию. Однако есть некоторые программы, которые предоставляют строки, заканчивающиеся двумя символами, возвратом каретки и переводом строки (CRLF), как если бы это было в сеансе SMTP. Чтобы справиться с этими индивидуалистическими случаями, Exim поддерживает опцию -dropcr. Когда это установлено, любой символ возврата каретки, который непосредственно предшествует переводу строки во вводе, отбрасывается.

Опцию -or можно использовать для установки тайм-аута для получения не-SMTP-сообщения; это переопределяет параметр конфигурации receive_timeout. Если тайм-аут не установлен, Exim вечно ожидает данных на стандартном вводе.

20.1.5 Обзор вариантов приема

Опции, используемые для управления тем, как работают процессы приема, приведены в таблице 20-1.

Табл. 20-1: Параметры режима ввода
Опция Значение
-bd Начать прослушивание демона
-bs SMTP на стандартный ввод и стандартный вывод, из локального процесса или через inetd
-bS Пакетный SMTP из локального процесса
-bm Сообщение на стандартный ввод, получатели в качестве аргументов
-t Сообщение при стандартном вводе, получатели из строк заголовка
-droper Удаление символов возврата каретки
-or Установить тайм-аут без SMTP
-os Установить время ожидания SMTP

20.2 Дополнительные данные сообщения

Несколько вариантов предоставляют дополнительные данные для включения в сообщение, полученное от локального процесса (то есть не через TCP/IP).

20.2.1 Адрес отправителя

Параметр -f указывает адрес отправителя, чтобы переопределить адрес, вычисленный на основе регистрационного имени вызывающего абонента, но только в том случае, если вызывающий абонент является доверенным пользователем. Например, на хосте, чей почтовый домен по умолчанию — elysium.example, если процесс root подчиняется:

exim -f zeus@olympus.example apollo@olympus.example

отправитель конверта сообщения установлен на zeus@olympus.example вместо root@elysium.example, поскольку root всегда является доверенным пользователем. Параметр -f по умолчанию игнорируется для ненадежных пользователей, но параметр untrusted_set_sender можно использовать для ослабления этого ограничения (19.3.1).

Даже если этот параметр не установлен, ненадежным вызывающим сторонам всегда разрешается использовать одну специальную форму -f. Вызов формы:

exim -f '<>' apollo@olympus.example

указывает отправителя пустого конверта для сообщения[6]. Отправители пустых конвертов используются как способ идентификации сообщений, которые никогда не должны приводить к возврату сообщений. Это использование предписано в RFC для самих сообщений о возврате, а также было принято для других типов сообщений, таких как предупреждения о задержке доставки. Если ненадежный пользователь вызывает Exim таким образом, опция -f учитывается в том смысле, что отправитель конверта очищается, но если local_from_check не установлен в false, все еще происходит сравнение реального отправителя с содержимым заголовка From:, и при необходимости добавляется строка заголовка Sender:. Этого не происходит, если вызывающему абоненту доверяют.

Если в командной строке присутствует параметр -f, он переопределяет любую информацию об отправителе, полученную из исходной строки From в начале сообщения.

  1. Угловые скобки <> заключены в кавычки, чтобы сделать это валидной командной строкой оболочки.

20.2.2 Имя отправителя

Когда Exim создает заголовок Sender: или заголовок From: (что он и делает, если они отсутствуют) для локального отправителя, он считывает системную информацию о пароле, чтобы получить реальное имя вызывающего абонента из так называемого поля «gecos», ведущего в строки вида:

Sender: The Boss <zeus@olympus.example>

Часть этого имени пользователя (The Boss) может быть переопределена с помощью параметра командной строки -F, например:

exim -F 'The Big Cheese' apollo@olympus.example

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

20.2.3 Информация об удаленном хосте

Существует ряд параметров, начинающихся с -oM, которые доверенные пользователи могут использовать при отправке сообщения локально для установки значений, которые обычно получаются из входящего SMTP-соединения. Тогда сообщение имеет характеристики того, что было получено от удаленного хоста. Эти параметры следующие:

Если какая-либо из этих опций используется ненадежным вызывающим абонентом или для ввода SMTP через TCP/IP, они игнорируются, за исключением того, что ненадежному вызывающему абоненту разрешается использовать их в сочетании с параметрами -bh, -bf и -bF для для тестирования проверок хоста и файлов фильтрации.

Помимо тестирования, опции -oM полезны при отправке почты, полученной от удаленных хостов, по какому-либо протоколу, отличному от SMTP. Предположим, пакет почты был получен UUCP от хоста ftileting.example, чей IP-адрес равен 192.168.23.45, и сохранен в пакетном формате SMTP в файле с именем /etc/uucp/received. Это может быть передано в Exim доверенным пользователем, выполнившим команду:

exim -bS -oMa 192.168.23.45 -oMs fleeting.example \
  -oMr uucp < /etc/uucp/received

Записи журнала и строка заголовка Received:, которая добавляется к каждому сообщению, будут отображать значения, предоставленные опциями -oM.

20.3 Немедленный контроль доставки

Эти параметры управляют тем, что происходит с сообщением сразу после его получения; в частности, запущен ли для него процесс доставки или нет. Они редко нужны, кроме как для тестирования.

Опция -odb применяется ко всем режимам, в которых Exim принимает входящие сообщения, включая слушающего демона. Он запрашивает «фоновую» доставку таких сообщений, что означает, что принимающий процесс автоматически запускает процесс доставки для каждого полученного сообщения, но Exim не ждет завершения таких процессов (они продолжают работать «в фоновом режиме»). Это действие по умолчанию, если ни одна из опций -od не указана.

Опции -odf и -odi, которые являются синонимами[7], запрашивают «передний план» (синхронную) доставку, когда Exim принял локально сгенерированное сообщение[8]. Для каждого сообщения, полученного на стандартном вводе, Exim создает новый процесс доставки, но ждет его завершения, прежде чем продолжить. В результате первоначальный процесс приема не завершается до тех пор, пока не завершится попытка доставки.

Опция -odq применяется ко всем режимам, в которых Exim принимает входящие сообщения, включая слушающего демона. Он указывает, что процесс приема не должен автоматически запускать попытку доставки для каждого полученного сообщения. Сообщения помещаются в очередь и остаются там до тех пор, пока их не обнаружит следующий процесс запуска очереди. Параметр конфигурации queue_on1y имеет тот же эффект.

Напротив, если установлен параметр -odqs, адреса получателей обрабатываются, а локальные доставки осуществляются обычным образом. Однако, если требуются какие-либо доставки SMTP, в настоящее время они не выполняются. Такие сообщения остаются в очереди до тех пор, пока их не обнаружит следующий процесс запуска очереди. Так как была выполнена маршрутизация, Exim знает, какие сообщения ожидают каких хостов, поэтому, если есть несколько сообщений, предназначенных для одного и того же хоста, они отправляются в одном SMTP-соединении. Параметр queue_smtp_domains имеет тот же эффект для определенных доменов. См. также параметр -qq (20.5.4).

  1. -odf совместим со Smail 3; -odi совместим с Sendmail.

  2. Если указано для процесса-демона, они аналогичны -odb.

20.4 Маршрутизация ошибок

Если Exim обнаруживает ошибку при получении не-SMTP-сообщения (например, неправильно сформированный адрес получателя), он может сообщить о проблеме, либо написав сообщение в стандартный файл ошибок, либо послав почтовое сообщение отправителю. Какое из этих двух действий он выполнит, управляется следующими опциями:

Ошибки обрабатываются особым образом при пакетном SMTP-входе (13.5).

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

20.5 Процессы обработчика очереди

Процессы обработчика очереди обычно периодически запускаются демоном или заданием cron, если вы не используете демона. При необходимости они также могут быть запущены пользователем с правами администратора вручную. Например, команда:

exim -q

создает один процесс обработчика очереди, который сканирует очередь один раз. Это команда, которую демон выдает всякий раз, когда приходит время запустить обработчик очереди.

20.5.1 Только первоначальные доставки

Обработчик очереди может быть ограничен теми сообщениями, которые ранее не пробовались, путем добавления буквы i (для «первоначальной доставки» (initial delivery)) после -q. Это полезно только в особых конфигурациях, когда сообщения помещаются в очередь без попытки автоматической доставки (например, при установке параметра queue_on1y в значение true).

20.5.2 Переопределение времени повторных попыток и зависание

Обычный обработчик очереди обрабатывает только размороженные сообщения и адреса, время повторных попыток которых истекло. Чтобы изменить это, можно добавить дополнительные буквы к -q. Если после -q следует один символ f, попытки доставки принудительны для всех адресов (независимо от того, достигли ли они своего времени повтора), но замороженные сообщения по-прежнему пропускаются. Однако, если за -q следует ff, замороженные сообщения автоматически размораживаются и включаются в обработку. Таким образом:

exim -qff

гарантирует, что попытка доставки делается для каждого адреса в каждом сообщении.

20.5.3 Только локальные доставки

Обработчик очереди может быть ограничен локальными доставками только путем добавления буквы l после f или ff, если они есть. Таким образом:

exim -qfl

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

20.5.4 Двухпроходная обработка для удаленных адресов

При обычном выполнении очереди каждое сообщение обрабатывается только один раз. Если несколько сообщений имеют удаленные адреса, которые направляются на один и тот же хост, и ни одно из них ранее не обрабатывалось, каждое из них отправляется в отдельном SMTP-подключении. Это обстоятельство довольно распространено в некоторых конфигурациях, таких как хост, который периодически подключается к Интернету (12.12.2).

Для лучшей производительности желательно, чтобы Exim знал, что у него есть несколько сообщений для одного и того же хоста, чтобы их можно было отправить в одном SMTP-соединении. Если какой-либо из параметров -q указан с дополнительным q (например, -qqff), результирующий запуск очереди выполняется в два этапа. На первом этапе маршрутизируются удаленные адреса, но транспортировка не выполняется. База данных, которая запоминает, какие сообщения ожидают определенных хостов, обновляется, как если бы доставка на эти хосты была отложена. Когда это завершено, происходит второе обычное сканирование очереди, а также нормальная маршрутизация и доставка. Сообщения, направляемые на один и тот же хост, доставляются по одному SMTP-соединению из-за подсказок, которые были настроены во время первого сканирования очереди.

20.5.5 Периодические запуски очереди

В большинстве установок процессы обработчика очередей должны запускаться через регулярные промежутки времени. Вы можете запросить, чтобы демон Exim выполнил эту работу, указав после -q значение времени. Например:

exim -q20m

создает демон, который запускает процесс запуска очереди каждые 20 минут (и больше ничего не делает). Эта форма параметра -q обычно сочетается с -bd для запуска одного демона, который прослушивает входящие SMTP, а также периодически запускает обработчики очередей. Например:

exim -bd -q30m

На практике команда, которая запускает демон такого типа, обычно является стандартной командой, которая появляется в сценариях загрузки операционной системы, которые ссылаются на /usr/sbin/sendmail или /usr/lib/sendmail. Обычно такие сценарии могут вместо этого запускать Exim без необходимости модификации, при условии, что путь Sendmail является символической ссылкой на двоичный файл Exim.

Вы можете, если хотите, использовать значение времени с любым из вариантов -q, обсуждавшихся до сих пор, например:

exim -qff4h

инициирует попытку доставки каждого адреса в очереди каждые четыре часа.

20.5.6 Обработка определенных сообщений

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

Если после -q (или -qf и т. д.) указать идентификатор сообщения, как в следующем примере, все сообщения, идентификаторы которых лексически меньше, будут пропущены:

exim -qf 0t5C6f-0000c8-00

Поскольку идентификаторы сообщений начинаются со времени прибытия, пропускаются все сообщения, прибывшие до 0t5C6f-0000c8-00. Если задан второй идентификатор сообщения, сообщения, идентификаторы которых больше его, пропускаются. Однако очередь по-прежнему обрабатывается в произвольном порядке.

20.5.7 Обработка конкретных адресов

Процессу исполнителя очереди можно дать указание обрабатывать только те сообщения, отправители или получатели которых соответствуют определенному шаблону. Параметры -S и -R задают шаблоны для отправителя и получателя соответственно. Если указаны и -S, и -R, оба должны быть удовлетворены. В случае -R сообщение выбирается до тех пор, пока совпадает хотя бы один из его недоставленных получателей. Например:

exim -R zalamea.example

запускает процесс доставки любого сообщения с недоставленным адресом, содержащим zalamea.example. Это простая текстовая проверка; строка может быть найдена в локальной части или в домене (или в обоих, если она содержит @).

Если вы хотите использовать более сложный шаблон, вы можете указать, что введенная вами строка является регулярным выражением, указав после -R или -S букву r. Например:

exim -Rr '(major|minor)\.zalamea\.example$'

выбирает сообщения, которые содержат недоставленный адрес, оканчивающийся на major.zalamea.example или minor.zalamea.example.

Как только сообщение выбрано для доставки, запускается обычный процесс доставки, и обрабатываются все получатели, а не только те, которые соответствуют -R. Для первого выбранного сообщения Exim отменяет любую информацию о повторных попытках и инициирует попытку доставки для каждого недоставленного адреса. Если за -S или -R следует f или ff, форсирование применяется ко всем выбранным сообщениям; в случае с ff также включаются замороженные сообщения.

Параметр -R позволяет легко инициировать доставку всех сообщений в заданный домен после того, как хост некоторое время был недоступен.

20.5.8 Сводка опций обработчика очереди

Опции для процессов обработчика очередей приведены в таблице 20-2.

Табл. 20-2: Параметры обработчика очереди
Опция Значение
-q Обычный обработчик очереди
-gf Запустить очередь с принудительной доставкой
-qff Принудительная доставка и замороженные сообщения
-ql Только локальная доставка
-qfl Принудительные местные доставки
-qffl То же, что и -qfl, но включает замороженные сообщения
-R Выбор получателя, литеральная строка
-Rf То же, с форсированием
-Rff То же, с форсированием и замороженными сообщениями
-Rr Выбор по получателю, регулярное выражение
-Rrf То же, с форсированием
-Rrff То же, с форсированием и замороженными сообщениями
-S Выбор отправителя, литеральная строка
-Sf То же, с форсированием
-Sff То же, с форсированием и замороженными сообщениями
-Sr Выбор отправителя, регулярное выражение
-Srf То же, с форсированием
-Srff То же, с форсированием и замороженными сообщениями

Любая из опций -q... может быть указана как -qq..., чтобы запустить двухэтапную очередь, и за любой из них может также следовать время, чтобы настроить демон, который периодически повторяет запуск очереди с той же опцией.

20.6 Переопределение конфигурации

Имя файла конфигурации времени выполнения Exim определяется в конфигурации времени сборки и встроено в двоичный файл. Это необходимо, потому что Exim обычно является программой setuid с привилегиями root, которую может вызывать любой процесс. Разрешение на использование произвольных конфигураций времени выполнения было бы огромным риском для безопасности. Однако иногда бывает полезно иметь возможность использовать альтернативный файл конфигурации или изменять содержимое стандартного файла либо для тестирования, либо для какой-либо специальной цели.

Имя файла конфигурации среды выполнения можно изменить с помощью параметра -C:

exim -C /etc/exim/alt.config ...

Если вызывающая программа не является пользователем root или пользователем Exim и имя файла отличается от встроенного имени, Exim немедленно навсегда отказывается от своей привилегии root и запускается от имени вызывающего пользователя.

Файл конфигурации среды выполнения может содержать определения макросов (4.3.5). Их значения можно переопределить с помощью опции -D, например:

exim -DLOG_SELECTOR=+filter ...

но опять же, если вызывающая сторона не является пользователем root или пользователем Exim, Exim отказывается от своей привилегии root при использовании этой опции. Опция -D может повторяться в командной строке до десяти раз.

20.7 Просмотр очереди Exim

Пользователи с правами администратора могут использовать следующий набор опций для проверки содержимого очереди Exim. Если у вас есть доступ к серверу X Window, альтернативным способом просмотра этой информации является запуск монитора Exim (eximon). Однако все, что может сделать eximon, можно сделать и из командной строки.

Параметр -bpc выводит одно число, которое представляет собой количество сообщений в очереди. Опция -bp выводит полный список. Каждое сообщение отображается, как в этом примере:

25m  2.9K  Ot5C6f-0000c8-00 <caesar@rome.example>
           brutus@rome.example
           ...

В первой строке отображается время нахождения сообщения в очереди (в данном случае 25 минут), размер (2,9 КБ), локальный идентификатор и отправитель конверта. Для рикошетов, у которых нет отправителя, здесь отображается <>. Остальные строки содержат получателей конвертов, по одному в каждой строке. Те, кому сообщение уже было доставлено, отмечены буквой D; в случае расширения адреса за счет псевдонимов или переадресации это происходит только после завершения доставки всем его дочерним элементам.

Если вместо -bp используется -bpu, отображаются только недоставленные адреса. Если используется -bpa, добавляются доставленные адреса, созданные из исходных адресов. Если используется -bpr, выходные данные не сортируются в хронологическом порядке поступления сообщений. Это может ускорить работу, если в очереди много сообщений, и особенно полезно, если вывод будет подвергаться постобработке без сортировки. Вы также можете использовать -bpra и -bpru, которые действуют как -bpa и -bpu, но опять же без сортировки.

20.8 Управление сообщениями

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

20.8.1 Операции со списком сообщений

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

exim -M 123H3N-0003mY-00 Ot5C6f-0000c8-00

-M создает процесс доставки для каждого сообщения по очереди, предварительно размораживая его, если это необходимо. Во время доставки время повтора переопределяется, а такие параметры, как queue_domains и hold_domains, игнорируются. Другими словами, Exim выполняет попытку доставки для каждого недоставленного получателя. Это часто называют «принудительной доставкой сообщения» (forcing message delivery).

-Mf и -Mt сообщения о замораживании и размораживании соответственно. Когда -Mt был применен к сообщению, условие manually_thawed становится истинным в фильтре Exim.

-Mg и -Mrm оба приводят к тому, что сообщения игнорируются. Разница между ними в том, что -Mg (g означает "отказаться") отправляет каждый адрес с ошибкой «доставка отменена администратором» (delivery cancelled by administrator) и генерирует сообщение о возврате отправителю, тогда как -Mrm просто удаляет сообщения из очереди без отправки рикошетов.

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

20.8.2 Проверка сообщения в очереди

Содержимое файлов спула сообщения может быть проверено пользователем с правами администратора; опции -Mvb, -Mvh и -Mvl, за которыми следует идентификатор сообщения, выводят тело (файл -D), заголовок (файл -H) или файл журнала сообщений соответственно. Например:

exim -Mvl 123H3N-0003mY-00

показывает журнал сообщений для сообщения 123H3N-0003mY-00.

20.8.3 Изменение сообщения в очереди

Параметры, описанные в этом разделе, позволяют администратору изменять сообщение в очереди при условии, что оно не находится в процессе доставки. Все они требуют идентификатор сообщения в качестве первого аргумента; некоторые из них также нуждаются в дополнительных данных.

Получателей сообщения можно изменить с помощью -Mar, -Mmd и -Mmad. Первый из них добавляет дополнительного получателя. Например:

exim -Mar 123H3N-0003mY-00 extra@xyz.example

добавляет получателя extra@xyz.example в сообщение 123H3N-0003mY-00. Невозможно удалить получателей, но Exim можно заставить делать вид, что он доставил им сообщение. Команда:

exim -Mmad 123H3N-0003mY-00

помечает все адреса получателей как доставленные, тогда как:

exim -Mmd 123H3N-0003mY-00 godot@waiting.example

помечает только адрес godot@waiting.example как доставленный. Если вам нужно перенаправить сообщение одному или нескольким новым получателям, возможно, потому, что исходные адреса известны как недействительные, безопасный способ сделать это следующим образом:

Вы также можете изменить отправителя конверта сообщения, используя опцию -Mes. Например:

exim -Mes 123H3N-0003mY-00 newsender@new.domain.example

Чтобы полностью удалить отправителя (то есть, чтобы сообщение выглядело как рикошет), новый отправитель может быть указан как <>.

20.8.4 Обзор опций управления сообщениями

Варианты управления сообщениями приведены в таблице 20-3.

Таблица 20-3: Опции управления сообщениями
Опция Значение
-M Принудительная доставка
-Мар Добавить получателя
-Mes Изменить отправителя
-Mf Заморозить
-Mg Сдаться (рикошет)
-Mmad Отметить все как доставленные
-Mmd Отметить как доставленные
-Mrm Удалить сообщение (без рикошета)
-Mt Разморозить
-Mvb Просмотр тела сообщения
-Mvh Просмотр заголовка сообщения
-Mvl Просмотр журнала сообщений

20.9 Варианты тестирования

Есть ряд опций, которые помогут вам протестировать Exim и его конфигурацию, или выяснить, почему он сделал то, что сделал. Иногда также могут быть полезны параметры из следующего раздела (20.10).

20.9.1 Проверка параметров конфигурации

Если вы хотите быть уверены, что бинарный файл Exim можно использовать и что он может успешно прочитать свой файл конфигурации, запустите:

exim -bV

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

Вы также можете проверить, что он прочитал из файла конфигурации. Если -bP задан без аргументов, это приводит к тому, что значения всех основных опций конфигурации Exim'а будут записаны в стандартный вывод. Однако, если какой-либо из настроек параметра предшествует слово hide, их значения отображаются только для пользователей с правами администратора. Вы должны использовать скрытие, когда вы помещаете конфиденциальную информацию, такую как пароли для доступа к базам данных, в файл конфигурации.

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

exim -bP qualify_domain hold_domains

Имя конфигурационного файла можно запросить следующим образом:

exim -bP configure_file

Параметры конфигурации для отдельных драйверов можно получить, указав одно из слов router, transport или authenticator, за которым следует имя соответствующего экземпляра драйвера. Например:

exim -bP transport local_delivery

Сначала выводятся общие параметры драйвера, а затем частные параметры драйвера. Полный список всех драйверов определенного типа с настройками их параметров можно получить, используя routers, transports или authenticators. Например:

exim -bP authenticators

Наконец, список имен драйверов определенного типа можно получить, используя одно из слов router_list, transport_list или authenticator_list. Например:

exim -bP router_list

может вывести:

dnslookup
system_aliases
cancelled_users
real_localuser
userforward
localuser

20.9.2 Тестирование маршрутизации

В большинстве распространенных случаев нет необходимости посылать сообщение, чтобы узнать, как Exim будет маршрутизировать конкретный адрес; опция -bt сделает это за вас. Например:

$ exim -bt ph10@exim.example
ph10@exim.example
  deliver to ph10@exim.example
  router = dnslookup, transport = remote_smtp
  host ppsw.exim.example  [10.111.8.38]   MX=7
  host ppsw.exim.example  [10.111.8.40]   MX=7

Это говорит вам о том, что роутер dnslookup обрабатывал адрес, перенаправляя его на транспорт remote_smtp с заданным списком хостов. Для получения дополнительной информации о том, как был достигнут этот результат, вы можете установить параметры отладки (20.10).

Опции -bv и -bvs позволяют вам проверить, что будет делать Exim при проверке адреса получателя или отправителя, соответственно, в отличие от обработки адреса для доставки. Если вы не использовали параметры, которые заставляют роутеры вести себя по-разному при проверке (например, только проверка), результат будет таким же, как и для -bt. Существует разница между -bv и -bvs только в том случае, если вы установили проверку отправителя или проверку получателя на роутере, чтобы провести различие между этими двумя случаями.

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

Precedence: list

Настроить Exim для этого очень просто, используя роутер такого типа:

list_to_server:
  driver = manualroute
  condition = ${if eq {$h_precedence:}{list}{yes}{no}}
  transport = remote_smtp
  route_list = * server.name.example

Однако, когда вы используете -bt для проверки адресов, этот роутер никогда не запускается, потому что условие никогда не совпадает. См. описание -N (20.10.1) для альтернативного подхода к тестированию, который может быть здесь более полезным.

20.9.3 Проверка входящих соединений

Если вы настроили политики проверки и отклонения для использования при получении почты с других хостов, отследить, как именно будут применяться проверки, иногда может быть довольно сложно. Опция -bh поможет вам. Вы указываете IP-адрес, и Exim запускает фальшивую SMTP-сессию, как если бы он получил соединение с этого адреса. Пока он это делает, он выводит комментарии о проверках, которые он применяет, чтобы вы могли видеть, что происходит. Вы можете пройти весь диалог SMTP, если хотите; если вы хотите проверить элементы управления релеем, вы должны пройти как минимум до команд RCPT[1].

Ничего не записывается в лог-файлы, и никакие данные не записываются в каталог спулинга Exim. Все это полностью фальшивка и предназначена исключительно для тестирования. Вот расшифровка части сеанса тестирования с вкраплениями комментариев:

$ exim -bh 192.203.178.4

Эта команда запускает поддельный сеанс SMTP, как если бы было получено соединение с адреса 192.203.178.4.

**** SMTP testing session as if from host 192.203.178.4
**** but without any ident (RFC 1413) callback.
**** This is not for real!

Exim напоминает вам, что все это выдумка. Обратные вызовы RFC 1413 не могут быть выполнены, потому что нет реального соединения TCP/IP.

>>> host in host_lookup? yes (end of list)

Exim проверяет, соответствует ли вызывающий хост чему-либо в опции host_lookup. Ответ «yes», но то, что совпало, — это конец списка. Как это может быть? Последний элемент в host_lookup должен быть отрицательным элементом (начиная с восклицательного знака). В этом случае достижение конца списка дает «yes», а не «no».

>>> looking up host name for 192.203.178.4
>>> IP address lookup yielded dul.crynwr.com

Поскольку IP-адрес совпадает с host_lookup, Exim выполнил обратный поиск DNS, чтобы найти имя хоста, и он нашел dul.crynwr.com. Это тестовый хост, который гарантированно находится в MAPS DUL (списке пользователей удаленного доступа), чтобы люди могли протестировать свои конфигурации.

>>> host in host_reject_connection? no (option unset)

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

220 libra.test.example ESMTP Exim 4.10 ...

Exim завершил предварительное тестирование, и это первоначальный ответ, который он пошлет удаленному хосту. Теперь вам нужно сыграть роль клиента, набрав SMTP-команды, первой из которых должна быть HELO или EHLO:

helo dul.crynwr.com
250 libra.test.example Hello dul.crynwr.com [192.203.178.4]

После успешного HELO (или EHLO) вы можете попробовать передать сообщение Exim'у, используя команды MAIL, RCPT и DATA. Однако любые «принятые» сообщения отбрасываются. Во время транзакции сообщений Exim выводит комментарии о проверках, которые он выполняет для адресов, и дает соответствующие ответы. Вы можете завершить сеанс тестирования с помощью команды QUIT или выйти из него с помощью CTRL-C.

  1. Если вас интересует только то, принят или отклонен конкретный адрес получателя, вы можете использовать служебную команду exim_checkaccess, которая представляет собой «упакованную» версию теста -bh (21.8).

20.9.4 Правила повторения тестирования

Вы можете проверить, какое правило повтора будет использоваться для конкретного адреса, с помощью параметра -brt, за которым должен следовать хотя бы один аргумент. Exim выводит применимое правило повтора. Например:

$ exim -brt bach.comp.example
Retry rule: *.comp.example  F,2h,15m; F,4d,30m;

Аргументом может быть полный адрес электронной почты или просто доменное имя. Другое доменное имя может быть указано как необязательный второй аргумент; если для первого аргумента не найдено правило повтора, пробуется второй. Это связано с поведением Exim при поиске правил повтора для удаленных хостов. Если правило, соответствующее хосту, не найдено, ищется правило, соответствующее почтовому домену.

Также может быть задан третий необязательный аргумент, имя конкретной ошибки доставки. Итак, следующее:

exim -brt host.comp.example comp.example timeout_connect

задает вопрос: «Какое правило повтора будет использовано, если время ожидания соединения с хостом host.comp.example истекло при попытке доставить сообщение получателю в домене comp.example

20.9.5 Тестирование правил перезаписи

Опция -brw для проверки правил перезаписи адресов описана в разделе 15.8.

20.9.6 Тестирование файлов фильтров

Опции -bf и -bF для тестирования пользовательских и системных фильтров описаны в разделе 10.5.

20.9.7 Расширение тестовой строки

Опция -be для проверки расширений строк описана в разделе 17.16.

20.10 Опции для отладки

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

Пользователи с правами администратора могут запросить более подробную информацию, установив -d. Когда эта опция задается сама по себе, выводится большое количество стандартных данных отладки. Его можно уменьшить или увеличить, чтобы включить некоторую более редко необходимую информацию, добавив после -d строку, состоящую из имен, которым предшествуют символы плюс или минус. Они добавляют или удаляют наборы отладочных данных соответственно. Например, -d+filter добавляет отладку фильтра, тогда как -d-all+filter выбирает только отладку фильтра. Доступные категории отладки показаны в таблице 20-4.

Таблица 20-4: Селекторы отладки
Селектор Показать отладочную информацию для
acl Интерпретации ACL
auth Аутентификаторы
deliver Общая логика доставки
dns Поиск DNS (см. также резолвер)
dnsbl Код черного списка DNS
exec Аргументы для вызовов execv()
expand Детали расширения строки
filter Обработка фильтра
hints_lookup Подсказки при поиске данных
host_lookup Все типы преобразования имени в IP-адрес
ident Поиск по RFC 1413 (ident)
interface Списки локальных интерфейсов
lists Сопоставление элементов в списках
load Проверка загрузки системы
local_scan Отладка в функции local_scan()
lookup Общий поисковый код и все поисковые запросы
memory Обработка памяти
process_info Информация о настройке для журнала процесса
queue_run Запуск очереди
pid Добавление pid (идентификатора процесса) в строки отладки
receive Общая логика приема сообщений
resolver Отладочный вывод DNS-резолвера
retry Повтор обработки
rewrite Переписывание адреса
route Маршрутизация адресов
timestamp Строки отладки с меткой времени
tls Логика TLS
transport Транспорты
uid Изменения uid/gid и поиск uid/gid
verify Логика проверки адреса
all Все вышеперечисленное, а также -v вывод

К сожалению, резолвер DNS записывает отладочный вывод в стандартный вывод, а не в стандартный поток ошибок.

Значение по умолчанию (-d без аргумента) опускает expand, filter, interface, load, local_scan, memory, pid, timestamp и resolver. Однако, если демон запускается с включенной отладкой, pid принудительно устанавливается, чтобы можно было различать разные подпроцессы.

Вывод отладки разработан в первую очередь для того, чтобы помочь автору Exim отследить проблемы, но большая его часть должна быть понятна большинству администраторов. В частности, если вы включите отладку вместе с -bt или при доставке сообщения, вы сможете отслеживать поток управления через различные роутеры по мере обработки адреса. Если опция debug_print установлена на каком-либо роутере или транспорте, она выводит вывод всякий раз, когда выбрана любая отладка или если используется -v.

20.10.1 Подавление доставки

При обсуждении -bt и -bv (20.9.2) было указано, что их нельзя использовать для проверки какой-либо обработки адресов, включающей содержимое сообщения. Один из способов проверить это — использовать параметр -N. Это параметр отладки, запрещающий доставку сообщения на транспортном уровне. Это подразумевает -v, но при необходимости могут быть запрошены более высокие уровни отладочного вывода. Exim выполняет множество действий по доставке, но на самом деле не передает сообщение. Вместо этого он ведет себя так, как если бы он успешно это сделал. Однако он не вносит никаких обновлений в базу данных повторных попыток, а записи журнала о доставке помечаются знаком *> вместо =>.

После использования -N в сообщении оно никогда не может быть доставлено в обычном режиме. Если первоначальная доставка отложена, последующие попытки доставки выполняются автоматически с ключом -N.

Поскольку -N отбрасывает почту, только root и пользователь Exim могут использовать его в сочетании с -bd, -q, -R или -M (то есть «доставлять» произвольные сообщения таким образом). Любой другой пользователь может использовать -N только при вводе входящего сообщения.

20.11 Завершение опций

Есть специальный вариант, имя которого состоит из двух дефисов. Его единственная цель — завершить параметры и, следовательно, заставить последующие элементы командной строки рассматриваться как аргументы, а не как параметры, даже если они начинаются с дефиса. Возможно (хотя и маловероятно), что локальная часть адреса электронной почты начинается с дефиса; чтобы послать сообщение на такой адрес, вам нужно вызвать Exim следующим образом:

exim -- -oddname@wherever.example

20.12 Параметры встроенного Perl

Опции -pd и -ps, управляющие способом инициализации встроенного интерпретатора Perl, описаны в разделе 17.14.2.

20.13 Совместимость с Sendmail

Многие из опций командной строки Exim напрямую совместимы с Sendmail, так что Exim может быть установлен в качестве замены. Однако, поскольку дизайн Exim отличается, некоторые параметры Sendmail не имеют значения или работают в Exim по-другому. Sendmail также имеет ряд устаревших опций и синонимов, которые все еще используются некоторыми старыми MUA.

Sendmail интерпретирует вызов с опцией -bi как запрос на перестроение своего файла псевдонимов, и часто есть команда, называемая newaliases, чье действие является чем-то вроде «перестроить базу данных для файла псевдонимов почты».

Exim не имеет концепции файла псевдонимов; вы можете настроить столько псевдонимов роутеров, сколько захотите, хотя на практике чаще всего используется только один. «Пересборка» может иметь значение, если вы используете файл DBM или cdb для псевдонимов, но не в случае использования NIS или базы данных, такой как MySQL.

Сценарии, запускаемые при загрузке системы (или в другое время), могут вызывать newaliases или /usr/sbin/sendmail с параметром -bi. Exim ничего не делает, если вызывается с -bi, если только вы не укажете:

bi_command = /the/path/to/some/command

в этом случае он запускает данную команду под uid и gid вызывающей стороны Exim. Значение bi_command — это просто имя команды без аргументов. Если требуется аргумент, он должен быть задан опцией -oA в командной строке.

20.14 Вызов Exim под разными именами

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

mailq
Это имя предполагает опцию -bp, которая заставляет Exim отображать содержимое очереди.
rsmtp
Это имя предполагает опцию -bS, которая заставляет Exim читать пакетный SMTP из стандартного ввода (это для совместимости со Smail).
rmail
Это имя предполагает опции -i и -oee; первая отключает распознавание одной точки в качестве признака конца сообщения, а вторая изменяет обработку ошибок, обнаруженных при вводе. Имя rmail используется некоторыми системами UUCP.
runq
Это имя предполагает -q и вызывает запуск одной очереди (это для совместимости со Smail).

Ни одно из этих альтернативных имен не задается стандартными сценариями установки. Если вы хотите их использовать, вы должны сами создать символические ссылки.

Глава 21
Администрирование Exim

После того, как Exim запущен и работает, есть несколько вещей, которые необходимо делать регулярно, чтобы убедиться, что он продолжает обрабатывать вашу почту так, как вы этого хотите. То, сколько регулярного внимания ему требуется, во многом зависит от характера вашей установки и объема почты, с которой вы работаете.

Одна вещь, которую вы, возможно, захотите сделать, это посмотреть, что Exim на самом деле делает в процессе или что он только что сделал. Вы можете проверить процессы Exim с помощью утилиты exiwhat, и вы можете напрямую прочитать файлы журнала или использовать монитор Exim для отображения непрерывного основного журнала. Утилитный сценарий под названием exigrep предоставляет упакованный способ извлечения записей журнала для сообщений, соответствующих заданному шаблону.

Файлы журнала могут стать очень большими; обычно они «зацикливаются» на регулярной (обычно ежедневной) основе, чтобы журналы за предыдущие дни можно было сжать. Некоторые операционные системы имеют стандартные процедуры для циклических файлов журнала; для тех, кто этого не делает, предоставляется служебный скрипт под названием exicyclog.

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

21.1 Файлы журнала

Exim записывает три разных журнала, называемых main (основной), reject (отклонений) и panic (паники) журналами.

В дополнение к этим трем лог-файлам Exim обычно записывает лог-файл для каждого сообщения, которое он обрабатывает. Имена этих журналов сообщений являются идентификаторами сообщений, и они хранятся в подкаталоге msglog каталога очереди. Содержимое журнала сообщений является копией записей основного журнала о доставке рассматриваемого сообщения, хотя повторяющиеся сообщения «retry time not reached» (время повторной попытки не достигнуто) опущены. Эти файлы написаны, чтобы упростить администратору поиск истории того, что произошло с конкретным сообщением. Если не задан параметр save_message_logs, журнал сообщений удаляется, когда сообщение, на которое он ссылается, завершено.

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

  1. Например, вы можете настроить задание cron, которое отправит вам письмо, если оно найдет непустой журнал паники.

21.2 Управление назначением журнала

Журналы Exim могут быть записаны в локальные файлы, в syslog или в оба. Однако следует отметить, что многие реализации syslog используют UDP в качестве транспорта. Следовательно, они ненадежны в том смысле, что не гарантируется поступление сообщений на хост журнала, а также не обязательно сохраняется порядок сообщений[2].

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

log_file_path = /var/log/$primary_hostname/exim_%slog

Однако, как правило, желательно, чтобы место назначения журнала было включено в двоичный файл, а не устанавливалось во время выполнения, потому что тогда настройка доступна с самого начала выполнения Exim'а. В противном случае, если есть что-то, что он хочет зарегистрировать до того, как он прочитает файл конфигурации (например, ошибку в файле конфигурации), он не будет использовать нужный вам путь и, возможно, вообще не сможет войти в журнал.

Значение log_file_path представляет собой список, разделенный двоеточиями, в настоящее время ограниченный не более чем двумя элементами[3]. Если элементом является syslog, то используется syslog; в противном случае элемент должен быть либо абсолютным путем, содержащим %s в точке, где должны быть вставлены main, reject или panic, либо быть пустым, подразумевая использование пути по умолчанию (это log/%slog в каталоге спула). Если ничего не указано, используется путь по умолчанию. Вот несколько примеров возможных настроек:

log_file_path=/usr/log/exim_%s
log_file_path=syslog
log_file_path=:syslog
log_file_path=syslog : /usr/log/exim_%s

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

  1. Также сообщалось, что для больших файлов журналов (десятки мегабайт) вам может потребоваться настроить syslog, чтобы он не синхронизировал файл при каждой записи; в Linux было замечено, что syslog занимает более 90% процессорного времени.

  2. Разделитель для большинства списков в Exim может быть изменен с двоеточия на другой символ, но эта опция является исключением. Необходимо использовать двоеточие.

21.2.1 Регистрация в журнале syslog

Использование syslog не меняет то, что Exim регистрирует, или формат его сообщений. В syslog записываются те же строки, что и в файлы журналов. Однако, если вы установите syslog_timestamp в false, Exim не будет добавлять отметку времени к строкам журнала, которые он записывает в syslog.

Для syslog установлено значение LOG_MAIL, а имя программы — exim. В системах, которые разрешают это (все, кроме ULTRIX), устанавливается флаг LOG_PID, чтобы вызов syslog добавлял pid, а также время и имя хоста в каждую строку. Три потока журналов сопоставляются с приоритетами syslog следующим образом:

Многие строки журнала записываются как в основной, так и в журнал отклонений, поэтому будут дубликаты, если они направляются системным журналом в одно и то же место.

Строки журнала Exim иногда могут быть очень длинными, а некоторые из его записей журнала отклонения содержат несколько строк, когда включаются заголовки. Чтобы справиться с обоими этими случаями, записи, записываемые в syslog, разбиваются на отдельные вызовы syslog при каждой внутренней новой строке, а также после максимум 1000 символов. Чтобы упростить их сборку позже, каждый компонент разделенной записи начинается со строки вида [<n>/<m>] или [<n>\<m>], где <n> — номер компонента. и <m> — общее количество компонентов в записи. Разделитель / , когда строка разделена, потому что она слишком длинная; если он разделен из-за внутренней новой строки, разделителем является \.

Например, если бы ограничение по длине было 70 вместо 1000, следующее было бы результатом типичного сообщения об отклонении в основной журнал (LOG_INFO), причем каждой строке дополнительно предшествовали время, имя хоста и pid, добавленные системным журналом:

[1/3] 1999-09-16 16:09:43 11RdAL-0006pc-00 rejected from [127.0.0.1] (ph10):
[2/3] syntax error in 'From' header when scanning for sender: missing or ma
[3/3] lformed local part in "<>" (envelope sender is <ph10@cam.example>)

Эта же ошибка может привести к тому, что в журнал отклонений (LOG_NOTICE) будут записаны следующие строки:

[1/14] 1999-09-16 16:09:43 11RdAL-0006pc-00 rejected from [127.0.0.1] (ph10):
[2/14]  syntax error in 'From' header when scanning for sender: missing or ma
[3\14] lformed local part in "<>" (envelope sender is <ph10@cam.example>)
[4\14] Recipients: ph10@some.domain.cam.example
[5\14] P Received: from [127.0.0.1] (ident=ph10)
[6\14]        by xxxxx.cam.example with smtp (Exim 3.22 #27)
[7\14]        id 11RdAL-0006pc-00
[8\14]        for ph10@cam.example; Mon, 16 Apr 2001 16:09:43 +0100
[9\14] F From: <>
[10\14]   Subject: this is a test header
[11\14]   X-something: this is another header
[12\14] I Message-ID: <E11RdAL-0006pc-00@xxxxx.cam.example>
[13\14] B Bcc:
[14/14]   Date: Mon, 16 Apr 2001 16:09:43 +0100

Строки журнала, которые не являются слишком длинными и не содержат новых строк, записываются в системный журнал без изменений, например:

1999-09-16 16:09:47 SMTP connection from [127.0.0.1] closed by QUIT

Время, добавляемое syslog, обычно такое же, как временные метки Exim'а (хотя и в другом формате и без года), поэтому установка syslog_timestamp в false уменьшает количество записываемых данных без большой потери информации.

21.2.2 Уменьшение или увеличение того, что журналируется

Установив глобальную опцию log_selector, вы можете отключить некоторые журналы Exim'а по умолчанию, или вы можете запросить дополнительные журналы. Значение log_selector состоит из имен, которым предшествуют символы плюс или минус, как в следующем примере:

log_selector = +arguments -retry_defer

Аргументы обрабатываются слева направо, при этом положительные аргументы увеличиваются, а отрицательные уменьшают объем журналирования. Доступные имена показаны в таблице 21-1, а выбор по умолчанию отмечен звездочками.

Более подробная информация о каждом из этих пунктов приведена ниже:

Табл. 21-1: Дополнительные элементы журналирования
Имя Логируемая информация
address_rewrite Перезапись адреса
all_parents Все родители в строках =>
arguments Аргументы командной строки
*connection_reject Отказы в подключении
*delay_delivery Задержка немедленной доставки (сообщение поставлено в очередь)
delivery_size Добавить S=<nnn> в => строки
*dnslist_defer Откладывает поиск по списку DNS (он же RBL)
*etrn Команды ETRN
incoming_interface Входящий интерфейс в строках <=
incoming_port Входящий порт в строках <=
*lost_incoming_connection Как указано (включая тайм-ауты)
*queue_run Запускает и заканчивает работу очереди
received_recipients Получатели в строках <=
received_sender Отправитель в строках <=
*retry_defer Сообщения «retry time not reached» (время повторной попытки не достигнуто)
sender_on_delivery Добавить отправителя в строки =>
*size_reject Отклонить из-за слишком большого сообщения
*skip_delivery «message is frozen» (сообщение заморожено), «spool file is locked» (файл спула заблокирован)
smtp_confirmation Подтверждение SMTP в строках <=
smtp_connection Соединения SMTP
smtp_protocol_error Ошибки протокола SMTP
smtp_syntax_error Синтаксические ошибки SMTP
subject Содержанимое Subject: в строках <=
*tls_cipher Набор шифров TLS в строках <=
tls_peerdn Одноранговый DN TLS в строках <=
all Все вышеперечисленное
address_rewrite
Это приводит к тому, что все перезаписи адресов регистрируются в журнале для облегчения отладки правил перезаписи. Это относится как к глобальным перезаписям, так и к перезаписям в транспорте.
all_parents
Обычно в строках доставки регистрируются только исходный и конечный адреса; с этим селектором в скобках между ними указываются промежуточные родители.
arguments
Установка этой опции заставляет Exim записывать опции и аргументы, с которыми он был вызван, в главный журнал. Это функция отладки, добавленная для того, чтобы упростить поиск аргументов, с которыми определенные MUA вызывают MTA. Регистрация не происходит, если Exim отказался от привилегий root, потому что он был вызван с опциями -C или -D. Это средство не может регистрировать нераспознанные аргументы, поскольку аргументы проверяются перед чтением файла конфигурации. Единственный способ зарегистрировать такие случаи — это вставить скрипт между вызывающей программой и Exim'ом[4].
connection_reject
Запись в журнал записывается всякий раз, когда входящее SMTP-соединение по какой-либо причине отклоняется.
delay_delivery
Запись в журнал записывается всякий раз, когда процесс доставки не запускается для входящего сообщения из-за слишком высокой нагрузки или получения слишком большого количества сообщений по одному соединению. Ведение журнала не происходит, если процесс доставки не запущен, поскольку задано значение queue_only или был использован параметр -odq.
delivery_size
Для каждой доставки размер доставленного сообщения добавляется в строку => с тегом S=.
dnslist_defer
Запись в журнал записывается, если попытка поиска хоста в черном списке DNS приводит к временной ошибке.
etrn
Каждая легальная полученная команда ETRN регистрируется перед запуском ACL, чтобы определить, действительно ли она принята. Недопустимая команда ETRN или команда, полученная в транзакции сообщения, не регистрируется этим селектором (см. smtp_syntax_error и smtp_protocol_error).
incoming_interface
Интерфейс, на который было получено сообщение, добавляется в строку <= в виде IP-адреса в квадратных скобках с тегом I=, за которым следует двоеточие и номер порта.
incoming_port
Номер удаленного порта, с которого было получено сообщение, добавляется в записи журнала и строки заголовка Received: после IP-адреса в квадратных скобках и отделяется от него двоеточием. Это реализуется путем изменения значения, помещенного в переменные $sender_fullhost и $sender_rcvhost. Запись номера удаленного порта стала более важной с расширением использования NAT (см. RFC 2505).
lost_incoming_connection
Строка журнала записывается, когда входящее SMTP-соединение неожиданно обрывается.
queue_run
Начало и конец каждого запуска очереди регистрируются.
received_recipients
Получатели сообщения перечислены в главном журнале сразу после получения сообщения. Список появляется в конце строки журнала, которая записывается при получении сообщения, перед которым стоит слово «for». Адреса перечислены после того, как они были квалифицированы, но до какой-либо перезаписи.
received_sender
Незаписанный первоначальный отправитель сообщения добавляется в конец строки журнала, в которой фиксируется прибытие сообщения, после слова «from» (перед получателями, если также установлено поле received_recipients).
retry_defer
Строка журнала записывается, если доставка отложена из-за того, что время повторной попытки еще не достигнуто. Однако это сообщение «retry time not reached» (время повторной попытки не достигнуто) всегда исключается из отдельных журналов сообщений после первой попытки доставки.
sender_on_delivery
Адрес отправителя сообщения добавляется к каждой строке доставки и возврата, отмеченной F= (означает «from»).
size_reject
Строка журнала записывается всякий раз, когда сообщение отклоняется из-за того, что оно слишком большое.
skip_delivery
Строка журнала записывается всякий раз, когда сообщение пропускается во время выполнения очереди, потому что оно заморожено или потому что его уже доставляет другой процесс.
smtp_confirmation
Ответ на последнюю точку в диалоге SMTP для исходящих сообщений добавляется в строки журнала доставки в виде C="<text>". Некоторые MTA (включая Exim) возвращают в этом ответе идентифицирующую строку.
smtp_connection
Это включает более подробное ведение журнала входящих SMTP-соединений. Не применяется к пакетному SMTP, но применяется к SMTP-подключениям от локальных процессов, использующих параметр -bs, включая входящие подключения с использованием inetd. Строка журнала записывается всякий раз, когда соединение устанавливается или закрывается. Если соединение разрывается в середине сообщения, всегда записывается строка журнала, но в противном случае ничего не записывается в начале и конце SMTP-соединений, если этот селектор не установлен.
smtp_protocol_error
Строка журнала записывается для каждой обнаруженной ошибки протокола SMTP.
smtp_syntax_error
Строка журнала записывается для каждой обнаруженной синтаксической ошибки SMTP. Нераспознанная команда рассматривается как синтаксическая ошибка. Для внешнего соединения указывается идентификатор хоста; для внутреннего соединения с использованием -bs указывается идентификатор отправителя (обычно вызывающего пользователя).
subject
Тема сообщения добавляется в строку журнала прибытия, перед которой ставится T= (T означает «topic», поскольку S уже используется для «size»).
tls_cipher
Когда сообщение отправляется или принимается через зашифрованное соединение, используемый набор шифров добавляется в строку журнала, перед которой ставится X=.
tls_peerdn
Когда сообщение отправляется или принимается по зашифрованному соединению и удаленный хост предоставляет сертификат, в строку журнала добавляется одноранговый DN, которому предшествует DN=.
  1. Пример такого скрипта, называемый logargs.sh, находится в каталоге утилит Exim.

21.2.3 Непечатаемые символы в строках журнала

Есть еще один последний параметр, влияющий на ведение журнала. Когда Exim включает данные из сообщения в запись журнала, он заботится о том, чтобы непечатаемые символы были экранированы, чтобы не испортить формат журнала. Например, если сообщение содержит следующие строки в заголовке:

Subject: This subject covers
  more than one line

и селектор subject установлен, текст, который записывается в журнал:

T="This subject covers\n more than one line"

Символы, значения которых превышают 127 (так называемые «8-битные» или «top-bit» символы), по умолчанию печатаются с использованием восьмеричных escape-последовательностей. Однако, если вы установите для print_topbitchars значение true, эти символы считаются печатаемыми и отправляются в журнал без изменений.

21.3 Формат основных записей журнала

Каждая запись в главном журнале представляет собой одну строку текста. Некоторые строки довольно длинные, но это сделано для того, чтобы упростить разбор строк в программах, которые анализируют данные. Каждая строка начинается с временной метки вида:

2000-06-30 01:07:31

Часовой пояс, используемый для временных меток Exim, обсуждается в разделе 19.5. Строки журнала, которые относятся к получению или доставке сообщений, имеют двухсимвольный «флаг» после метки времени, чтобы их было легко идентифицировать. Флаги:

<=    for an arrival
=>    for a successful delivery
==    for deferment of delivery till later
**    for a delivery failure

Кроме того, -> и *> используются для некоторых специальных видов доставки, как описано далее в этой главе.

21.3.1 Регистрация приема сообщений

Прибытие сообщения, которое не получено через TCP/IP, регистрируется строкой формы, показанной в этом примере, которая разделена здесь на несколько строк, чтобы уместиться на странице:

2000-06-30 00:11:51 137nTL-0005br-00 <= holly@dwarf.example U=holly P=local S=811 id=Pine.SOL.3.96.1000630001852.21797A-100000@dwarf.example

Адрес, который сразу следует за символом <=, является адресом отправителя конверта (после применения всех правил перезаписи), а поле U записывает логин процесса, который вызвал Exim для отправки сообщения.

Когда сообщение приходит с другого хоста, в поле U записывается идентификатор RFC 1413 пользователя, отправившего сообщение, если оно было получено, а поле H идентифицирует хост-отправитель:

2000-06-30 08:57:53 137CW1-0005MB-00 <= kryten@dwarf.example H=mailer.dwarf.example [192.168.123.123] U=exim P=smtp S=5678 id=20000630091558.B12616@dwarf.example

Число, указанное в квадратных скобках, является IP-адресом хоста клиента. Если в поле H есть только одно имя, как показано здесь, Exim подтвердил, что оно соответствует IP-адресу. Если имя указано в круглых скобках, это имя было указано удаленным хостом в кавычках в команде EHLO или HELO и не проверено[5].

Если проверка дает имя, отличное от имени, указанного для EHLO или HELO, проверенное имя отображается первым, а за ним в скобках следует имя EHLO или HELO. В этом примере клиент не указал свое полное имя:

H=mm272.lucy.example (mm272) [192.168.215.229]

Неправильно настроенные хосты (и подделыватели писем) иногда помещают IP-адрес с квадратными скобками или без них в команду EHLO или HELO, что приводит к появлению записей в журнале, содержащих такие выдержки:

H=(10.21.32.43) [192.168.8.34]
H=((10.21.32.43]) [192.168.8.34]

Такие записи могут сбивать с толку. Можно полагаться только на конечный адрес в квадратных скобках.

Для всех сообщений поле P указывает протокол, используемый для получения сообщения. Для этого параметра установлено значение asmtp для сообщений, полученных от хостов, которые аутентифицировали себя с помощью команды SMTP AUTH. В этом случае имя используемого аутентификатора регистрируется с A в качестве имени поля. Если аутентифицированная идентификация была установлена опцией server_set_id аутентификатора, это также регистрируется, отделяясь двоеточием от имени аутентификатора. Например:

A=fixed_plain:ph10

Если вы используете версию Exim, которая поддерживает зашифрованные передачи, шифр, который использовался для входящего сообщения, регистрируется с X в качестве имени поля. Например:

X=TLSv1:DES-CBC3-SHA:168

Если вы этого не хотите, удалите tls_cipher из селектора журнала. По умолчанию ничего не регистрируется, когда Exim запрашивает сертификат у клиента, но если вы добавите tls_peerdn в селектор журнала, Distinguished Name зарегистрируется с DN в качестве имени поля.

Размер полученного сообщения указывается в байтах в поле S. Когда сообщение доставлено, заголовки могут быть удалены или добавлены. Это означает, что размер доставленных копий сообщения может отличаться от значения, зарегистрированного при получении (и действительно может отличаться друг от друга).

В поле ID записывается содержимое любой существующей строки заголовка Message-ID:. Если сообщение не содержит такой строки, ничего не регистрируется, но Exim добавляет ее перед доставкой сообщения.

Сообщение об ошибке доставки (рикошет) отображается с адресом отправителя <>, и если это локально сгенерированное сообщение, за ним обычно следует поле R, которое является ссылкой на локальную идентификацию сообщения, вызвавшего сообщение об ошибке. Например:

2000-06-30 00:49:27 137o3j-0005mU-00 <= <> R=137o3e-0005mO-00 U=root P=local S=1239

фиксирует приход сообщения о возврате, спровоцированного сообщением с идентификатором 137o3e-0005mO-00.

Вы можете запросить добавление дополнительных данных в строку журнала приема сообщений, установив определенные селекторы журнала, как описано ранее.

  1. Поиск имени хоста клиента по его IP-адресу происходит только в том случае, если конфигурация Exim требует имя, чтобы проверить его по списку хостов, или если хост соответствует опции host_lookup.

21.3.2 Регистрация доставок

Вот примеры строк журнала доставки для локальной и удаленной доставки соответственно:

1995-10-31 08:59:13 0tACW1-0005MB-00 => marv <marv@hitch.example> R=localuser T=local_delivery
1995-10-31 09:00:10 0tACW1-0005MB-00 => monk@holistic.example R=dnslookup T=remote_smtp H=holistic.example [192.168.234.234]

Поля R и T определяют роутер и транспорт, которые использовались для доставки. Поле H идентифицирует удаленный хост.

Для локальной доставки поле, следующее сразу за =>, представляет собой либо просто локальную часть, либо имя файла, либо команду конвейера. За ним следует исходный адрес в угловых скобках, как в первой строке этого примера. Если (в результате псевдонима или переадресации) между первоначальным и конечным адресом существуют промежуточные адреса, последний из них указывается в скобках после конечного адреса. Однако селектор журнала all_parents может быть установлен таким образом, чтобы регистрировались все промежуточные адреса.

Генерация ответного сообщения файлом фильтра регистрируется как «delivery» (доставка) адресату, перед которым ставится >. Элементы R и T записывают роутер и транспорт. Например:

2000-06-30 09:42:35 137wNf-0000ng-00 => >hermione@hws.thaum.example <harry@hws.thaum.example R=userforward T=address_reply

показывает, что у пользователя harry есть файл фильтра, который использовал команду reply для создания сообщения на адрес hermione@hws.thaum.example. Рядом в журнале, часто непосредственно перед такой строкой, вы найдете запись о приходе сгенерированного сообщения.

Если теневой транспорт запускается после успешной локальной доставки, в строку журнала для успешной доставки добавляется элемент в конце формы:

ST=shadow transport name

Если теневой транспорт не удается, сообщение об ошибке помещается в круглые скобки.

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

Когда в одну доставку включено более одного адреса (например, в одной транзакции используются две команды SMTP RCPT), второй и последующие адреса помечаются флагом -> вместо =>, чтобы программы сбора статистики могли различать доставлены копии и доставлены адреса. Если два или более сообщения доставляются по одному SMTP-соединению, звездочка следует за IP-адресом в строках журнала для второго и последующих сообщений.

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

1998-12-10 00:50:49 OznuKe-0001UB-00 => discarded <low.club@trick4.bridge.example> R=userforward

написано, чтобы записать, почему для этого адреса не регистрируются доставки. Если системный фильтр отбрасывает все доставки сообщения, строка журнала будет следующей:

1999-12-14 00:30:42 OznuKe-0001UB-00 => discarded (system_filter)

Наконец, когда используется параметр отладки -N для предотвращения фактической доставки, записи журнала помечаются знаком *> вместо => или ->.

21.3.3 Отложенные доставки

При отсрочке доставки в журнал записывается строка следующего вида:

2002-12-19 16:20:23 16aiQz-0002Q5-00 == marvine@@endrest.example R=dnslookup T=smtp defer (146): Connection refused

В случае удаленной доставки ошибка возникает для последнего испробованного IP-адреса. Сведения об отдельных сбоях SMTP также записываются в журнал, поэтому этой строке будет предшествовать следующая строка:

2002-12-19 16:20:23 16aiQz-0002Q5-00 Failed to connect to mail1.endrest.example [192.168.239.239]: Connection refused

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

21.3.4 Ошибки доставки

Если доставка не удалась из-за того, что адрес не может быть маршрутизирован, в журнал записывается строка следующего вида:

1995-12-19 16:20:23 0tRiQz-0002Q5-00 ** jim@trek99.example <jim@trek99.example>: unknown mail domain

Если доставка не удалась во время транспортировки, отображаются роутер и транспорт, а также включается ответ от удаленного хоста, как в этом примере:

2002-07-11 07:14:17 17SXDU-000189-00 ** ace400@pb.example R=dnslookup T=remote_smtp: SMTP error from remote mailer after RCPT TO:<ace400@pb.example>: host pbmail3.py.example [192.168.63.111]: 553 5.3.0 <ace400@pb.example>... Addressee unknown The log lines for all forms of delivery failure are flagged with **.

Строки журнала для всех форм сбоев доставки отмечены **.

21.3.5 Завершение сообщения

Строка вида:

1995-10-31 09:00:11 OtACW1-0005MB-00 Completed

записывается в основной журнал, когда сообщение должно быть удалено из спула в конце его обработки. Это гарантирует, что никакие дальнейшие записи журнала для этого сообщения не будут записываться.

21.3.6 Другие записи журнала

Время от времени в журнал записываются различные другие типы записей. Большинство должно быть самоочевидным. Одна из них, которая иногда вызывает беспокойство, — «Spool file is locked» (файл спула заблокирован). Обычно это не ошибка; это означает, что попытка доставить сообщение не может быть продолжена, потому что какой-то другой процесс Exim уже работает над сообщением. Эта запись в журнале может быть довольно распространенной, если процессы обработчика очередей запускаются с частыми интервалами. Его можно подавить, отключив селектор журнала skip_delivery.

Однако, если вы видите, что эта строка журнала повторяется для одного и того же сообщения в течение неоправданного времени, может возникнуть проблема. Вы можете использовать утилиту exiwhat, чтобы узнать, что пытается сделать процесс Exim, работающий с сообщением.

21.3.7 Сводка полей в строках журнала

Сводка идентификаторов полей, которые используются в строках журнала, показана в таблице 21-2.

Таблица 21-2: Поля в строках журнала
Флаг поля Информация
A Имя аутентификатора (и необязательный идентификатор)
C Подтверждение SMTP при доставке
DN DN из сертификата партнера
F Адрес отправителя (в строках доставки)
H Имя хоста и IP-адрес
id Идентификатор сообщения для входящего сообщения
P Протокол для входящего сообщения
R В строках <=: ссылка на локальный отказ; в строках =>: имя роутера
S Размер сообщения
ST Имя теневого транспорта
T В строках <=: тема сообщения (topic); в строках =>: имя транспорта
U Локальный пользователь или идентификатор RFC 1413
X Набор шифров TLS

21.4 Файлы журнала циклов

Если вы используете локальные файлы, а не системный журнал (syslog) для записи журналов Exim (наиболее распространенная конфигурация), вы обычно должны периодически «ротировать» основной журнал и журнал отклонений. Процесс ротации состоит из переименования текущего файла журнала и удаления предыдущих, которые являются слишком старыми. Большинство сайтов делают это, настраивая задание cron, которое запускается один раз в день, обычно в полночь, так что каждый файл содержит журнал за один день[6].

Процесс доставки Exim открывает основной журнал, когда ему впервые нужно записать в него, и он держит файл открытым на случай, если потребуются последующие записи (например, если для одного и того же сообщения выполняется несколько разных доставок). Однако удаленная доставка по SMTP может занять много времени, и это означает, что файл может оставаться открытым и использоваться еще долго после того, как он был переименован. Чтобы избежать этого, Exim проверяет главный файл журнала по имени перед повторным использованием открытого файла. Если файл не существует, или если его индексный дескриптор изменился (то есть, несмотря на то же имя, на самом деле это другой файл), Exim закрывает старый файл и вместо него открывает новый. Таким образом, старый лог-файл может оставаться открытым некоторое время, но никакие процессы Exim не должны писать в него после того, как он был переименован.

Некоторые операционные системы имеют стандартные скрипты для ротирования журналов, которые, конечно же, можно использовать. Для тех, кто этого не делает, в составе дистрибутива Exim есть утилита под названием exicyclog. Она циклически повторяет как основной журнал, так и файлы журнала отклонения. Вы можете запустить ее из crontab root в форме:

1 0 * * * su exim -c /usr/exim/bin/exicyclog

В этом примере скрипт запускается в 00:01 каждый день. Сценарий не нужно запускать от имени пользователя root, поскольку файлы журналов принадлежат пользователю Exim.

Если файл основного журнал (или журнала отклонений) не существует, сценарий ничего не делает (для этого набора файлов). В противном случае при каждом запуске exicyclog файлы «перетасовываются» по одному: mainlog становится mainlog.01, предыдущий mainlog.01 становится mainlog.02 и так далее, вплоть до предела, который устанавливается в скрипте при его сборке (значение по умолчанию равно 10). Все старые файлы, за исключением вчерашнего журнала (mainlog.01), автоматически сжимаются для экономии места на диске[7].

  1. Конечно, вы не можете гарантировать, что переименование произойдет ровно в полночь, и вы не можете синхронизироваться с какими-либо процессами Exim, которые могут находиться в процессе записи в журнал, поэтому на практике в «неправильном» файле обычно будет несколько строк журнала.

  2. В этом описании мы использовали имя файла по умолчанию, mainlog, но сценарий прекрасно работает с любыми именами файлов журналов, которые вы выберете.

21.5 Извлечение информации из лог-файлов

В дистрибутив Exim входят два Perl-скрипта для извлечения информации из основных лог-файлов. Они предоставляют довольно простые возможности, но вы можете модифицировать их или написать свои собственные, если вам нужны дополнительные функции.

21.5.1 Утилита exigrep

Утилита exigrep извлекает из одного или нескольких файлов журналов все записи, относящиеся к любому сообщению, записи которого содержат хотя бы одну запись, соответствующую заданному шаблону. Например:

exigrep 'H=orange\.csi\.example' /var/spool/exim/log/mainlog

выбирает не только строки, содержащие строку H=orange.csi.example, но и все остальные строки для сообщений, имеющих совпадающую запись. Таким образом, на выходе будет полный набор строк журнала для всех сообщений, связанных с этим конкретным хостом. Записи сортируются таким образом, что все записи для каждого сообщения печатаются вместе, а между записями каждого сообщения есть пустая строка.

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

exigrep [-l] <pattern> [<log file>] [<log file>] ...

где флаг -l означает «литерал», то есть рассматривает все символы в шаблоне как самостоятельные. В противном случае шаблон должен быть регулярным выражением Perl. Файлы журнала могут быть сжаты или несжаты; те, которые сжаты, передаются через утилиту zcat по мере их чтения[8]. Если имена файлов не указаны в командной строке, читается стандартный ввод.

  1. Это предполагает, что местоположение zcat было известно во время сборки Exim.

21.5.2 Утилита eximstats

Сценарий Perl под названием eximstats поставляется с дистрибутивом Exim. Он извлекает статистику из лог-файлов Exim. Первоначально eximstats был задуман просто как демонстрация того, как это можно сделать, но он нашел свое применение в регулярном использовании, и со временем его переработали и существенно расширили. eximstats по умолчанию выдает большой объем информации, но есть варианты подавления различных ее частей; они приведены в таблице 21-3.

Таблица 21-3: Опции Eximstat
Параметр Эффект
-bydomain Рейтинговые таблицы по вышестоящим доменам
-byemail Рейтинговые таблицы по адресам электронной почты
-byhost Рейтинговые таблицы по хостам (по умолчанию)
-h0 Подавить гистограммы
-h<n> Интервал управления гистограммой
-help Показать справочную информацию
-html Вывод в формате HTML
-ne Подавить вывод ошибок
-nr Подавить вывод для релеев
-nr/pattern>/ То же самое, для тех, которые соответствуют шаблону
-nt Подавить вывод для доставки транспортом
-q0 Подавить вывод времени в очереди
-q<n1,n2...> Управление интервалами времени в очереди
-t<n> Установить длину рейтинговых таблиц
-tnl Исключить локальную информацию из рейтинговых таблиц
-t_remote_users Включить нелокальные локальные части

По умолчанию выходные данные представляют собой обычный текст, но параметр -htm1 приводит к тому, что он записывается в формате HTML. Краткое описание вывода и основных опций приведено здесь. Более подробную информацию можно найти в руководстве Exim.

Следуя любым параметрам, аргументы скрипта представляют собой список файлов, которые должны быть файлами основного журнала. Например:

eximstats -nr -ne /var/spool/exim/log/mainlog.01

eximstats извлекает информацию о количестве и объеме сообщений, полученных от различных хостов или доставленных на них. Информация сортируется как по количеству сообщений, так и по объему, и в стандартном выводе перечислены 50 ведущих хостов в каждой категории. Для сообщений, доставленных и полученных локально, аналогичная статистика создается для каждого пользователя.

Выходные данные также включают общее количество и статистику ошибок доставки, а также гистограммы, показывающие количество полученных и доставленных сообщений в каждый час дня. Доставка с более чем одним адресом в конверте (например, транзакция SMTP с более чем одной командой RCPT) считается одной доставкой.

Хотя обычно сообщается о большем количестве доставленных, чем полученных сообщений (поскольку сообщения могут иметь несколько получателей), eximstats может сообщать о большем количестве полученных сообщений, чем доставленных, даже если спул пуст в начале и в конце рассматриваемого периода. Если входящее сообщение не содержит действительных получателей, для него не записывается доставка. Отчет об ошибке обрабатывается как отдельное сообщение.

eximstats выводит общую сводку, содержащую объем и количество полученных и доставленных сообщений, а также количество хостов, задействованных в каждом случае. Он также выводит количество сообщений, которые были задержаны (то есть не доставлены полностью с первой попытки), и количество сообщений, в которых хотя бы один адрес не был доставлен. Вот пример этого начального вывода:

Exim statistics from 1999-01-21 00:11:08 to 1999-01-22 00:10:46

Grand total summary
-------------------

                                            At least one address
  TOTAL        Volume   Messages   Hosts    Delayed       Failed
  Received     153MB      16520    2341    53  0.3%    104  0.6%
  Delivered    182MB      23197    1513

Остальная часть вывода находится в разделах, которые можно независимо отключить или изменить с помощью различных параметров. Сначала идет сводка по доставке транспортом:

Deliveries by transport
-----------------------

                   Volume   Messages
  **bypassed**        960          1
  :blackhole:        27KB          4
  address_file     1665KB        425
  address_pipe     2134KB        417
  address_reply      4368          3
  local_delivery    135MB      16141
  remote_smtp        43MB       6206

**bypassed** записывается, когда сообщение направляется в /dev/null, что Exim распознает как особый случай. :blackhole: записывает использование специальной функции файлов псевдонимов :blackhost:. Эту часть вывода можно подавить, установив параметр -nt.

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

Messages received per hour (each dot is 27 messages)
----------------------------------------------------

00-01    342 ............
01-02    249 .........
02-03    206 .......
03-04    154 .....
04-05    134 ....
05-06    160 .....
06-07    141 .....
07-08    245 .........
08-09    562 ....................
09-10   1208 ..............................................
10-11   1228 ...............................................
11-12   1300 ..................................................
12-13   1242 ................................................
13-14   1070 .........................................
14-15   1320 .................................................
15-16   1335 ................................................
16-17   1281 ..............................................
17-18   1226 .....................................
18-19    890 ...............................
19-20    597 ......................
20-21    452 ................
21-22    448 ................
22-23    467 .................
23-24    463 .................

Deliveries per hour (each dot is 47 deliveries)
-----------------------------------------------

00-01    411 ........
01-02    266 .....
02-03    236 .....
03-04    189 ....
04-05    139 ..
05-06    208 ....
06-07    164 ...
07-08    263 .....
08-09    985 .......................
09-10   1801 ..........................................
10-11   1780 .........................................
11-12   1916 ............................................
12-13   1624 ......................................
13-14   1607 ......................................
14-15   2087 ................................................
15-16   2373 ......................................................
16-17   1764 .........................................
17-18   1299 .............................
18-19   1118 .........................
19-20    693 ................
20-21    579 ..............
21-22    524 .............
22-23    634 ...............
23-24    537 .............

По умолчанию временной интервал равен одному часу. Если указано -h0, гистограммы подавляются; если -h сопровождается числом, значение дает количество делений в час, поэтому -h2 устанавливает интервал в 30 минут. Значение по умолчанию эквивалентно -h1.

Далее идет анализ времени нахождения в очереди, сначала по всем сообщениям:

Time spent on the queue: all messages
-------------------------------------

Under   1m    16271  98.5%   98.5%
        5m      168   1.0%   99.5%
       15m       30   0.2%   99.7%
       30m       19   0.1%   99.8%
        1h       10   0.1%   99.9%
        3h       10   0.1%   99.9%
        6h        4   0.0%  100.0%
       12h        3   0.0%  100.0%
        1d        1   0.0%  100.0%
Over    1d        1   0.0%  100.0%

а затем по сообщениям, у которых была хотя бы одна удаленная доставка:

Time spent on the queue: messages with at least one remote delivery
-------------------------------------------------------------------

Under   1m     5073  95.6%   95.6%
        5m      167   3.1%   98.7%
       15m       27   0.5%   99.2%
       30m       12   0.2%   99.5%
        1h       10   0.2%   99.7%
        3h       10   0.2%   99.8%
        6h        4   0.1%   99.9%
       12h        3   0.1%  100.0%
Over    1d        1   0.0%  100.0%

Эти конкретные статистические данные были записаны в хороший день. Этот вывод можно подавить опцией -q0. Кроме того, за -q может следовать список временных интервалов для этого анализа. Значения разделены запятыми и указаны в секундах, но могут включать арифметические множители, поэтому, например, вы можете установить 3*60, чтобы указать 3 минуты. Такая настройка, как:

-q60,5*60,10*60

заставляет eximstats давать количество сообщений, которые оставались в очереди менее одной минуты, менее 5 минут, менее 10 минут и более 10 минут.

Если не указан параметр -nr, следует список всех сообщений, которые были ретранслированы через локальный хост, начиная с этого:

Relayed messages
----------------

    5 (rosemary) [192.168.182.138] jcbreb@cus.example
      => green.gra.example [192.168.8.57] r5j4m@herm.gra.example
    ...
Total: 1949 (plus 0 unshown)

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

Иногда вы хотите знать только об определенных релеях. Вы можете выборочно опустить информацию о релее, указав регулярное выражение после -nr, например:

eximstats '-nr/busy\.host\.name/' /var/spool/exim/log/mainlog.01

Шаблон сопоставляется со строкой следующей формы (здесь разделенной на две строки):

H=<host> [<ip address>] A=<sender address> => H=<host> A=<recipient address>

например:

H=in.host [10.2.3.4] A=from@some.where => H=out.host A=to@else.where

Имя хоста-отправителя отображается в скобках, если оно не было проверено на соответствие IP-адресу. Почтовые адреса берутся из конверта, а не из заголовков. Релеи, подавленные этим механизмом, вносят свой вклад в «непоказанный» счет в итоговой общей строке.

Следующая часть вывода состоит из «рейтинговых таблиц», начиная с:

Top 50 sending hosts by message count
-------------------------------------

 323   3834KB   purple.csi.example
 314   5710KB   maroon.csi.example
 170   1853KB   local
  29     97KB   mta1.c1.example
  28   3653KB   pml20.acad.abc.example
  28    725KB   hutmail.com.example
  ...

в котором перечислены хосты, от которых было получено наибольшее количество сообщений. За ним следуют:

Вы можете изменить число 50 с помощью опции -t; например, -t10 перечисляет только 10 лучших в каждой категории. Если вы установите -t0, эта часть вывода подавляется; если вы установите -tnl, информация о локальных отправителях и получателях будет скрыта.

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

Последний раздел вывода eximstats — это список ошибок доставки:

List of errors
--------------

    1 " peter"@cus.example R=unknownuser T=unknownuser pipe:
      return message generated

    1 1996@southwest.cim.example R=dnslookup T=smtp: SMTP error
      from remote mailer after RCPT TO: <1996@southwest.cim.example>:
      host tuert.southwest.cim.example [192.168.9.19]:
      550 <1996@southwest.cim.example>...
      User unknown

    ...

Errors encountered: 118
-----------------------

Число в начале каждого элемента — это количество одинаковых ошибок. Этот вывод можно подавить, указав -ne.

21.6 Наблюдение за тем, что делает Exim

Есть два аспекта наблюдения за тем, что Exim на самом деле делает в любой момент времени. Файлы сообщений в его каталоге спулинга содержат работу, которую он взял на себя. Они представляют его активность в относительно длительном временном масштабе, тогда как текущий набор процессов Exim — это то, что он фактически делает в данный момент. Предусмотрены условия для просмотра обоих этих видов деятельности.

21.6.1 Утилита exiqsumm

Один из способов наблюдать за тем, что делает Exim, — просмотреть список сообщений, которые он обрабатывает. Эту информацию предоставляет параметр командной строки -bp и его варианты (20.7). Существует также короткий служебный скрипт exiqsumm, который выполняет постобработку вывода -bp для предоставления сводки. Следующая команда:

exim -bp | exiqsumm

производит выходные строки этой формы:

3   2322   74m   66m   wek.example

Это означает, что три сообщения в очереди имеют недоставленные адреса в домене wek.example. Их общий размер составляет 2322 байта; самый старый стоял в очереди 74 минуты, а самый новый — 66 минут.

21.6.2 Утилита exinext

Если вы хотите знать, когда Exim попытается в следующий раз выполнить конкретную доставку, в которой возникла временная ошибка, вы можете использовать утилиту exinext, которая в основном представляет собой Perl-скрипт, для извлечения информации из базы данных повторных попыток Exim. Имея почтовый домен или полный адрес, он ищет хосты для домена и выводит любую информацию о повторных попытках, которая у него может быть. В настоящее время информация о повторных попытках получается путем запуска базовой утилиты exim_dumpdb (описанной в справочном руководстве) и постобработки вывода. exinext не особенно эффективен, но и не ожидается, что он будет запускаться очень часто. Вот пример его использования:

$ exinext piglet@milne.fict.example
kanga.milne.fict.example:192.168.8.1 error 146: Connection refused
  first failed: 21-Feb-1996 14:57:34
  last tried:   21-Feb-1996 14:57:34
  next try at:  21-Feb-1996 15:02:34
roo.milne.fict.example:192.168.8.3 error 146: Connection refused
  first failed: 20-Jan-1996 13:12:08
  last tried:   21-Feb-1996 11:42:03
  next try at:  21-Feb-1996 19:42:03
  past final cutoff time

Фраза «past final cutoff time» (прошедшее окончательное время отсечки) означает, что ошибка возникает дольше, чем максимальное время, указанное в соответствующем правиле повторных попыток. Вы можете предоставить exinext локальную часть без домена, чтобы получить информацию о повторных попытках локальной доставки, которая временно не удалась. Идентификатор сообщения может быть задан для получения информации о повторной попытке, относящейся к конкретному сообщению. Это происходит только тогда, когда при попытке доставить сообщение на удаленный хост возникает ошибка, связанная с сообщением (12.2.2).

21.6.3 Запрос процессов Exim

Вы можете, конечно, использовать команду Unix ps для получения списка процессов Exim. Обычно это комбинируется с grep для формирования такой команды, как:

ps -ef | grep exim

Вывод может содержать такие строки:

exim   295     1   0   Jul 01 ?     0:05 /usr/sbin/sendmail -bd -q15m
exim  4240   295   0 10:09:40 ?     0:00 /usr/sbin/sendmail -bd -q15m
exim  4342   295   0 10:10:59 ?     0:00 /usr/exim/bin/exim -q
exim  4345  4342   0 10:10:59 ?     0:00 /usr/exim/bin/exim -q
exim  4376   295   0 10:11:13 ?     0:00 /usr/sbin/sendmail -bd -q15m

Все эти процессы работают под пользователем Exim, и ни один из них не имеет связанного с ним терминала (именно это означают вопросительные знаки). Вы можете сделать вывод, что процесс 295 является процессом демона, поскольку его родителем является процесс номер 1, процесс init, который становится родителем всех процессов демона. Кроме того, он был запущен несколько дней назад, поэтому время его начала указано в виде даты, а не времени.

Процессы 4240, 4342 и 4376 являются потомками демона. Первый и третий должны быть процессами приема для входящих SMTP-соединений, потому что показанная команда совпадает с командой демона. Это означает, что он разветвил новые процессы, но не выполнил новую команду. Однако процесс 4342, хотя он также является ответвлением от демона, выполняет другую команду (а именно, процесс запуска очереди). Он создал процесс 4345 для доставки сообщения.

Эта информация от ps довольно ограничена. Например, вы не можете сказать, с каких удаленных хостов приходят сообщения или какие сообщения доставляет обработчик очереди. Метод, принятый некоторыми программами для предоставления информации об их действиях, заключается в изменении значений переменных-аргументов, с которыми они вызываются, так что выходные данные ps изменяются по мере выполнения программы. К сожалению, этот метод работает не во всех операционных системах, поэтому Exim его не использует.

Вместо этого процессы Exim реагируют на сигнал SIGUSR1, записывая строку текста, описывающую, что они делают, в файл exim-process.info в каталоге спулинга Exim. Это средство упаковано для использования с помощью служебного сценария exiwhat. Вы должны запустить эту команду от имени пользователя root, чтобы она имела право посылать сигнал процессам, работающим под любым идентификатором uid[9].

Первое, что делает exiwhat — очищает файл с информацией о процессе. Затем он находит все процессы Exim и посылает каждому сигнал SIGUSR1. Сценарий ждет одну секунду, чтобы позволить процессам отреагировать, а затем копирует файл в стандартный вывод. Это может выглядеть так:

 295 daemon: -q15m, listening on port 25
4240 handling incoming connection from [192.168.243.242]
4342 running queue: waiting for 0tAycK-0002ij-00 (4345)
4345 delivering 0tAycK-0002ij-00 to mail.ref.example [192.168.42.42] (editor@ref .example)
4375 handling incoming connection from [192.168.234.111]

Число в начале каждой строки вывода является номером процесса. Четвертая строка здесь разделена, чтобы поместиться на странице.

В некоторых операционных системах есть команда для идентификации всех процессов, выполняющих определенную программу, и отправки им сигнала. В Linux и FreeBSD это killall; на Solaris это pkill.

Exim использует команду множественного уничтожения, когда это возможно. Для других систем комбинация ps и egrep используется для поиска всех процессов Exim. К сожалению, команда ps различается в разных операционных системах. Используются не только разные параметры, но и формат вывода. Если вам кажется, что exiwhat не работает, проверьте первые несколько строк сценария оболочки, которые должны содержать что-то вроде этого:

multikill_cmd=pkill
multikill_arg='exim( |$|-)'

ps_cmd=/bin/ps
ps_arg=-e
egrep arg=' exim( |$|-)'

signal=-USR1

Если multikill_cmd не пустой, он указывает команду множественного уничтожения, а multikill_arg является ее аргументом. В противном случае используются следующие три настройки. Первая из них задает путь к команде ps, а вторая является аргументом для ps. Третья — аргумент для egrep, чтобы он извлек список процессов Exim из вывода ps. В обоих случаях окончательная настройка определяет сигнал, который необходимо отправить. Фактические значения могут отличаться в зависимости от используемой операционной системы.

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

  1. Это отличается от перезапуска демона с использованием SIGHUP, который может быть выполнен либо как root, либо как exim.

21.7 Монитор Exim

Монитор Exim — это приложение X Window, которое постоянно отображает информацию о том, что делает Exim. Пользователь с правами администратора может выполнять определенные операции с сообщениями из этого графического интерфейса; однако все эти средства также доступны из командной строки, и, действительно, сам монитор использует интерфейс командной строки для выполнения этих операций.

21.7.1 Запуск монитора

Монитор запускается запуском скрипта eximon. Это сценарий оболочки, который устанавливает ряд переменных среды, а затем запускает двоичный файл с именем eximon.bin. Переменные среды — это способ настройки монитора, например, указание размера его окна. Их значения по умолчанию указываются при сборке Exim. Однако, даже если вы используете предварительно скомпилированную версию Exim, параметры, встроенные в сценарий eximon во время компиляции, могут быть переопределены для конкретного вызова путем установки переменных окружения с теми же именами, которым предшествует EXIMON_. Например, команда оболочки, такая как:

EXIMON_LOG_DEPTH=400 eximon

(в оболочке, совместимой с Bourne) запускает eximon с приоритетной настройкой параметра LOG_DEPTH.

Если EXIMON_LOG_FILE_PATH установлен в среде, он переопределяет конфигурацию файла журнала Exim. Это позволяет записывать данные журнала eximon в системный журнал при условии, что сообщения MAIL.INFO системного журнала syslog направляются в файл на локальном хосте. В противном случае, если для записи данных журнала используется только syslog, монитор Exim не может обеспечить отображение хвоста журнала.

Ресурсы X можно использовать для изменения внешнего вида окна обычным способом. Например, настройка ресурса вида:

Eximon*background: grey94

изменяет цвет фона на светло-серый, а не на белый. На ленточных диаграммах линии данных и опорные линии отображаются черным цветом. Это означает, что опорные линии не видны поверх данных. Однако их цвет можно изменить, установив ресурс под названием «highlight» (странное имя, но именно его использует виджет ленточной диаграммы Athena). Например, вы можете настроить более светлые контрольные линии на ленточных диаграммах, выполнив следующую команду:

xrdb -merge <<End
Eximon*highlight: grey50
End

Чтобы видеть содержимое сообщений в спуле и работать с ними, eximon должен быть запущен либо как root, либо пользователем, который является членом группы Exim.

Окно монитора разделено на три части, как показано на рисунке 21-1, который является фактическим снимком экрана, сделанным в работающей системе. Однако почтовые адреса, имена хостов и IP-адреса скрыты крестиком из соображений конфиденциальности.

Рисунок 21-1: Скриншот монитора

Первая часть содержит одну или несколько ленточных диаграмм и две кнопки действий, вторая — «хвост» основного лог-файла, а третья — отображение очереди сообщений, ожидающих доставки, с еще двумя кнопками действий.

21.7.2 Ленточные диаграммы

Первая ленточная диаграмма — это количество сообщений в очереди. Остальные ленточные диаграммы определяются в сценарии конфигурации совпадениями регулярных выражений в записях файла журнала, что позволяет отображать, например, количество сообщений, доставленных на определенные хосты или с использованием определенных транспортов. Предоставленные значения по умолчанию отображают количество полученных и доставленных сообщений, а также локальных и SMTP-доставок. Период по умолчанию между обновлениями ленточной диаграммы составляет одну минуту.

Дисплеи ленточных диаграмм автоматически изменяют масштаб по мере изменения отображаемого значения. На каждом графике всегда 10 горизонтальных линий; строка заголовка указывает значение каждого деления, когда оно больше единицы. Например, x2 означает, что каждое деление представляет значение 2.

Также можно иметь ленточную диаграмму, показывающую процент заполнения определенного раздела диска, что полезно, когда локальные почтовые ящики ограничены одним разделом. Это зависит от наличия функции statvfs() или ее эквивалента в операционной системе. Большинство, но не все версии Unix, которые поддерживают Exim, имеют это. Для этой конкретной ленточной диаграммы верхняя часть диаграммы всегда соответствует 100%, а масштаб задан как x10%. Вы можете запустить eximon с этой дополнительной лентой командой вида:

EXIMON_SIZE_STRIPCHART=/var/mail eximon

предполагая, что вы используете Bourne-совместимую оболочку. В этом примере отслеживается размер раздела, содержащего каталог /var/mail. Если вы собираете Exim из исходников, вы можете указать в конфигурации времени сборки, что это должно быть по умолчанию. Имя диаграммы является последним компонентом пути, но вы можете изменить его, установив EXIMON_SIZE_STRIPCHART_NAME, если хотите.

21.7.3 Кнопки основных действий

Под диаграммами находится кнопка действия для выхода из монитора. Рядом с ней находится еще одна кнопка с пометкой Size. Они размещены здесь таким образом, что при сжатии окна до минимального размера остаются видимыми только ленточная диаграмма количества очередей и эти две кнопки. Кнопка Size — это переключатель, который заставляет окно переключаться между максимальным и минимальным размером. При максимальном расширении, если окно не видно полностью там, где оно сейчас находится, оно перемещается обратно туда, где оно было в последний раз, когда оно было в полном размере. Старое положение запоминается; в следующий раз, когда окно уменьшается до минимума, оно перемещается назад.

Идея состоит в том, что вы можете сохранить уменьшенное окно, показывающее только одну или две ленточные диаграммы в удобном месте на экране, легко развернуть его, чтобы показать полное окно, когда это необходимо, и так же легко вернуть его к тому, что было. Эта функция скопирована из того, что оконный менеджер twm делает для действия f.ful1zoom. Минимальный размер окна можно изменить, задав переменные окружения EXIMON_MIN_HEIGHT и EXIMON_MIN_WIDTH при запуске монитора.

21.7.4 Отображение журнала

Вторая часть окна представляет собой область, в которой отображается несколько последних строк основного журнала. Это недоступно, когда единственным местом назначения для регистрации данных является syslog, если только строки syslog не направляются в локальный файл, имя которого передается eximon через переменную окружения EXIMON_LOG_FILE_PATH.

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

Объем сохраняемого журнала зависит от параметра EXIMON_LOG_BUFFER, который определяет объем используемой памяти. Когда он заполнен, более ранние 50% данных отбрасываются; это намного эффективнее, чем выбрасывать его построчно. Подокно также имеет горизонтальную полосу прокрутки для доступа к концам длинных строк журнала. Это единственный способ горизонтальной прокрутки; клавиши со стрелками вправо и влево недоступны. Текст можно вырезать из этой части окна с помощью мыши обычным способом. Размер этого подокна управляется EXIMON_LOG_DEPTH.

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

Нажатие CTRL-R или CTRL-S открывает окно, в котором можно ввести искомый текст. Имеются кнопки для выбора прямого или обратного поиска, для выполнения поиска и для отмены. Если нажата кнопка Search, происходит поиск, и окно остается, чтобы можно было выполнять дальнейшие поиски. Если нажата клавиша Return (или Enter), выполняется однократный поиск, и окно закрывается. При нажатии CTRL-C поиск отменяется[10].

  1. Средство поиска реализовано с использованием возможностей текстового виджета Athena. По умолчанию это всплывающее окно, содержащее параметры «search» и «replace». Чтобы подавить нежелательную часть «replace» для eximon, вместе с Exim распространяется модифицированная версия виджета TextPop. Однако компоновщики в BSD и HP-UX, похоже, не в состоянии обрабатывать предоставленную извне версию TextPop, когда остальные части текстового виджета поступают из стандартных библиотек. Таким образом, в этих системах eximon должен быть встроен со стандартным виджетом, который приводит к появлению этих нежелательных элементов во всплывающем окне поиска.

21.7.5 Отображение очереди

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

Глубина этого подокна управляется EXIMON_QUEUE_DEPTH, а частота, с которой оно обновляется, управляется EXIMON_QUEUE_INTERVAL; значение по умолчанию — 5 минут, так как сканирование очереди довольно дорогое удовольствие. Однако прямо над дисплеем находится кнопка действия Update, которую можно использовать для принудительного обновления отображения очереди в любое время.

Если хост некоторое время не работает, для него может накапливаться ожидающая почта, и это может затруднить работу с другими сообщениями в очереди. Чтобы помочь в этой ситуации, рядом с Update есть кнопка Hide. При нажатии открывается диалоговое окно «Hide addresses ending with» (cкрыть адреса, заканчивающиеся на). Если вы наберете здесь что-либо и нажмете Return (или Enter), текст будет добавлен в цепочку таких текстов, и если каждый недоставленный адрес в сообщении совпадает хотя бы с одним из текстов, сообщение не будет отображаться.

Если есть адрес, который не соответствует ни одному из текстов, все адреса отображаются как обычно. Сопоставление происходит на концах адресов, поэтому, например, foo.com.example указывает все адреса в этом домене, тогда как xxx@foo.com.example указывает только один конкретный адрес. Когда любое скрытие настроено, отображается кнопка Unhide. При нажатии отменяет все скрытия. Кроме того, чтобы гарантировать, что скрытые сообщения не будут забыты, запрос на скрытие автоматически отменяется через час.

Пока отображается диалоговое окно, вы не можете нажимать какие-либо кнопки или делать что-либо еще в окне монитора. По этой причине, если вы хотите вырезать текст из отображения очереди для использования в диалоговом окне, вы должны сделать вырезание до того, как нажмете кнопку Hide.

Отображение очереди содержит для каждого нескрытого сообщения в очереди время, в течение которого оно находилось в очереди, размер сообщения, ID сообщения, отправителя сообщения и первого недоставленного получателя — все в одной строке. Если это сообщение об ошибке доставки, отправитель отображается как <>. Если имеется более одного получателя, которому сообщение еще не доставлено, последующие получатели перечислены в дополнительных строках, вплоть до заданного максимального числа, после которого отображается многоточие. Получатели, которые уже получили сообщение, не отображаются. Если сообщение заморожено, слева отображается звездочка.

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

21.7.6 Меню очереди

Если удерживать клавишу Shift и щелкать левой кнопкой мыши, когда указатель мыши находится над текстом любого сообщения в окне очереди, всплывает меню действий и выделяется первая строка отображения очереди для сообщения. Это не влияет на выделенный текст. Если вы хотите использовать какое-либо другое событие для всплывающего меню, вы можете установить EXIMON MENU EVENT в среде перед запуском монитора. Значение, установленное в этом параметре, является стандартным описанием события X. Например, чтобы запустить eximon с помощью Ctrl, а не Shift, вы можете использовать:

EXIMON_MENU_EVENT='Ctr11<Btn1Down>>' eximon

Пример меню eximon показан на рис. 21-2.

Рисунок 21-2: Меню монитора

Заголовок меню представляет собой идентификатор сообщения и содержит пункты, которые действуют следующим образом:

Журнал сообщений
Содержимое журнала сообщений для сообщения отображается в новом текстовом окне.
Заголовки
Информация из буферного файла, содержащая информацию о конверте и строки заголовка, отображается в новом текстовом окне.
Тело
Содержимое буферного файла, содержащего тело сообщения, отображается в новом текстовом окне. По умолчанию существует ограничение в 20 КБ на объем отображаемых данных. Это можно изменить, установив EXIMON_BODY_MAX.
Доставить сообщение
Вызов Exim делается с использованием опции -M для запроса доставки сообщения. Это вызывает автоматическое размораживание, если сообщение заморожено. Также установлена опция -v. Вывод Exim отображается в новом текстовом окне. Доставка выполняется в отдельном процессе, чтобы не задерживать монитор во время доставки.
Заморозить сообщение
Exim вызывается с помощью опции -Mf, чтобы запросить замораживание сообщения.
Разморозить сообщение
Exim вызывается с помощью опции -Mt, чтобы запросить разморозку сообщения.
Отказ от msg
Exim вызывается с помощью опции -Mg, чтобы запросить, чтобы Exim прекратил попытки доставить сообщение. Для всех оставшихся недоставленных адресов создается отчет об ошибке доставки.
Удалить сообщение
Exim вызывается с помощью опции -Mrm, чтобы запросить удаление сообщения из системы без создания каких-либо отчетов об ошибках.
Добавить получателя
Отображается диалоговое окно, в котором можно ввести адрес получателя. Нажатие Return (или Enter) вызывает вызов Exim с использованием опции -Mar, которая запрашивает, чтобы к сообщению был добавлен дополнительный получатель. Однако, если поле ввода пусто, никаких действий не предпринимается.
Отметка доставке
Отображается диалоговое окно, в котором можно ввести адрес получателя. Нажатие Return (или Enter) вызывает вызов Exim с использованием опции -Mmd, которая помечает данный адрес получателя как уже доставленный. Однако, если поле ввода пусто, никаких действий не предпринимается.
Отметка о доставке всех
Вызов Exim делается с использованием опции -Mmad, чтобы пометить все адреса получателей как уже доставленные.
Изменить отправителя
Отображается диалоговое окно, инициализированное текущим адресом отправителя, которое вы можете редактировать. Нажатие Return (или Enter) вызывает вызов Exim с использованием опции -Mes, которая заменяет адрес отправителя. Однако, если поле ввода пусто, никаких действий не предпринимается.

В тех случаях, когда делается вызов Exim, если вызов приводит к какому-либо выводу из Exim (в частности, если команда завершается ошибкой), отображается окно, содержащее команду и вывод. Когда доставка принудительна, всегда есть вывод из-за использования опции -v. В любом текстовом окне, отображаемом в результате действия меню, доступны обычные средства вырезания и вставки, а поиск можно выполнять с помощью CTRL-R и CTRL-S, как описано ранее для окна хвоста журнала.

Результаты действия обычно очевидны из журналов и экранов очереди. Последний автоматически обновляется для таких действий, как замораживание и оттаивание, если не было установлено EXIMON_ACTION_QUEUE_UPDATE=no. В этом случае необходимо использовать кнопку Update, чтобы принудительно обновить дисплей после замораживания или оттаивания.

21.8 Проверка доступа к релею

Опция командной строки -bh позволяет вам запустить фальшивую сессию SMTP с выводом отладки, чтобы проверить, что делает Exim, когда он применяет элементы управления политикой к входящей SMTP-почте. Однако не все достаточно знакомы с протоколом SMTP, чтобы в полной мере использовать -bh, и иногда просто хочется ответить на вопрос Есть ли у этого адреса доступ? не утруждая себя никакими дополнительными подробностями.

Утилита exim_checkaccess представляет собой «упакованную» версию -bh. Она принимает два аргумента, IP-адрес и адрес электронной почты, как в следующем примере:

exim_checkaccess 10.9.8.7 A.User@a.domain.example

Утилита запускает вызов Exim с опцией -bh, чтобы проверить, будет ли данный адрес электронной почты принят в команде RCPT в соединении TCP/IP с хоста с заданным IP-адресом. На выходе утилиты либо слово «accepted», либо ответ об ошибке SMTP, как в этом примере:

Rejected:
  550 Relay not permitted

При запуске этого теста утилита использует <> в качестве адреса отправителя конверта для команды MAIL, но вы можете изменить это, указав дополнительные параметры. Они передаются непосредственно команде Exim. Например, чтобы указать, что тест должен запускаться с адресом отправителя himself@there.example, вы можете использовать эту команду:

exim_checkaccess 10.9.8.7 A.User@a.domain.example \
                 -f himself@there.example

21.9 Сохранение псевдонимов и других файлов

Хотя Exim использует только один файл конфигурации времени выполнения, это обычно относится к другим файлам, содержащим различные типы данных (например, списки псевдонимов). Если вы обновите любой из этих файлов, на которые есть ссылки, процессы Exim немедленно подхватят новое содержимое; вам не нужно предпринимать специальных действий. Однако если вы обновите саму конфигурацию среды выполнения или любые файлы, включенные в .include, вы должны отправить сигнал SIGHUP процессу демона. Для этого вам нужно быть пользователем root или exim, используя такую команду:

kill -HUP 'cat /var/spool/exim/exim-daemon.pid'

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

21.9.1 Ведение файлов DBM

Если вы используете файлы DBM для псевдонимов или любых других данных, вам необходимо перестроить их из исходных файлов, если вы хотите изменить их содержимое. Для этого предусмотрена вспомогательная программа под названием exim_dbmbuild. Она читает входной файл в формате файла псевдонимов и записывает базу данных DBM, используя имена псевдонимов в нижнем регистре в качестве ключей, а остальную информацию в качестве данных. Использование нижнего регистра можно предотвратить, вызвав программу с параметром -nolc.

Завершающий ноль включен как часть ключевой строки. Это ожидается для типа поиска dbm. Однако, если задана опция -nozero, exim_dbmbuild создает файлы без завершающих нулей и в строках ключей, и в строках данных. С такими файлами можно использовать тип поиска dbmnz.

Программа требует два аргумента: имя входного файла (которое может быть одним дефисом для обозначения стандартного ввода) и имя выходной базы данных. Он создает базу данных под временным именем, а затем переименовывает файлы, если все идет хорошо. Если используется родной интерфейс Berkeley DB (распространенный в свободных операционных системах), два имени файла должны быть разными, поскольку в этом режиме функции Berkeley DB создают один выходной файл, используя точно заданное имя. Например:

exim_dbmbuild /etc/aliases /etc/aliases.db

читает системный файл псевдонимов и создает его версию DBM в /etc/aliases.db.

В системах, использующих подпрограммы ndbm (в основном проприетарные версии Unix), базы данных DBM состоят из двух файлов с суффиксами .dir и .pag. В этой среде суффиксы добавляются ко второму аргументу exim_dbmbuild, поэтому он может быть таким же, как и первый.

Программа выводит предупреждение, если встречает повторяющийся ключ. По умолчанию используется только первый из набора дубликатов; это делает его совместимым с поиском 1search. Параметр с именем -lastdup заставляет использовать вместо него последний. Существует также параметр -nowarn, который не позволяет ему отображать повторяющиеся ключи в стандартном потоке ошибок. Если встречаются какие-либо дубликаты, код возврата равен 1, если только не используется -noduperr. Для других ошибок (когда фактически новый файл не был создан) код возврата равен 2.

21.10 Ведение базы данных подсказок

Exim использует файлы DBM для хранения данных для своих баз данных подсказок. При нормальных обстоятельствах вам не нужно сильно беспокоиться об этом, за исключением одного: их нужно периодически «приводить в порядок». В файлах накапливается устаревшая информация; если их не убирать, их размер продолжает увеличиваться. Обычно это происходит довольно постепенно, так что часто бывает достаточно еженедельной уборки, хотя некоторые сайты предпочитают делать это ежедневно. Для выполнения этой работы предоставляется служебная программа exim_tidydb.

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

Также есть утилита для сброса содержимого базы данных подсказок и утилита для внесения изменений. Однако вряд ли вы захотите использовать какую-либо из них, поэтому здесь они не описываются. В справочном руководстве есть подробности, если они вам нужны.

Вспомогательная программа exim_tidydb требует два аргумента. Первый определяет имя каталога спула Exim, а второй — имя базы данных, с которой должен работать exim_tidydb. Эти имена следующие:

retry:
База данных повторных попыток.
wait-<transport -name>:
Базы данных информации о сообщениях, ожидающих удаленных хостов, использующих определенные транспорты. Обычно существует только один удаленный транспорт, поэтому существует только одна такая база данных с таким именем, как wait-remote_smtp.
misc:
База данных, содержащая различную информацию, которую Exim должен помнить, например, список текущих соединений с хостами, которые ограничены одним исходящим соединением за раз с помощью опции сериализации хостов транспорта smtp.

Если exim_tidydb запускается без параметров, он удаляет из базы данных все записи старше 30 дней. Например:

exim_tidydb /var/spool/exim retry

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

exim_tidydb -t 7d /var/spool/exim retry

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

Утилита exim_tidydb выводит комментарии на стандартный вывод всякий раз, когда она удаляет информацию из базы данных. Однако этот вывод обычно не нужен, и его можно отбросить. Обычно вы должны периодически запускать эту утилиту для всех баз данных, но в спокойное время дня, так как она требует, чтобы база данных была заблокирована (и, следовательно, недоступна для Exim), пока она выполняет свою работу. Ее можно запустить от имени пользователя Exim. Например, если у вас есть только один удаленный транспорт с именем remote_smtp, подойдут следующие команды:

exim_tidydb /var/spool/exim retry            >/dev/null
exim_tidydb /var/spool/exim wait-remote_smtp >/dev/null
exim_tidydb /var/spool/exim misc             >/dev/null

Вы можете поместить эти команды в файл (например, с именем /usr/exim/bin/tidy_alldb) и запускать его ежедневно с помощью корневой записи crontab, такой как:

10 3 * * * su exim -c /usr/exim/bin/tidy_alldb

Вы должны убедиться, что exim_tidydb можно найти, либо используя абсолютный путь, либо установив PATH в скрипте. Кроме того, вы можете использовать запись crontab для exim вместо root, и, конечно же, вы можете поместить отдельные команды прямо в crontab, если хотите.

21.11 Обслуживание почтового ящика

Время от времени возникают случаи, когда вы хотите предотвратить доставку любых новых сообщений в почтовый ящик пользователя, потому что вы хотите выполнить некоторые действия по обслуживанию или исследовать проблему. Вы можете изменить конфигурационный файл Exim, чтобы отложить доставку этому пользователю, но тогда вам придется впоследствии изменить его заново, и в любом случае это не помешает агентам пользователя модифицировать почтовый ящик. Лучший подход — заблокировать почтовый ящик[11].

Утилита exim_lock блокирует файл почтового ящика, используя тот же алгоритм, что и Exim. Это предотвращает модификацию почтового ящика Exim'ом или пользовательским агентом. Утилита требует имя файла в качестве первого аргумента. Если блокировка прошла успешно, второй аргумент запускается как команда. Если второго аргумента нет, используется значение переменной среды SHELL; если она не установлена или пуста, запускается /bin/sh. По завершении команды почтовый ящик разблокируется и работа утилиты завершается. Например:

exim_lock /var/spool/mail/spqr

запускает интерактивную оболочку, пока файл заблокирован, тогда как:

exim_lock -q /var/spool/mail/spqr <<End
some commands
End

запускает определенную неинтерактивную последовательность команд, пока файл заблокирован. Параметр -q («quiet») подавляет вывод проверки. Одна команда может быть запущена, пока файл заблокирован такой командой, как:

exim_lock -q /var/spool/mail/spqr "cp /var/spool/mail/spqr /some/where"

Обратите внимание, что если указана команда, она должна полностью содержаться во втором аргументе; отсюда и кавычки.

Без -q записывается некоторый вывод проверки. Более подробную проверку процесса блокировки можно запросить с помощью -v. Есть также некоторые параметры, похожие на параметры транспорта appendfile для управления способом блокировки почтового ящика. Если вы не внесли изменения в параметры блокировки файла appendfile, вам не нужно указывать какие-либо из этих параметров. Для получения подробной информации см. справочное руководство.

  1. Блокировка работает, конечно, только если почтовый ящик представляет собой один файл. Если вы используете многофайловые почтовые ящики, необходимо найти другой подход.

Глава 22
Сборка и установка Exim

До сих пор мы говорили только о том, как использовать Exim, предполагая, что он уже установлен в вашей системе. Мы еще не рассмотрели процесс установки. Есть три возможности:

В этой главе описывается процесс сборки и установки Exim из исходного дистрибутива.

22.1 Предпосылки

Вам нужно установить работающий компилятор ANSI/ISO C, прежде чем вы сможете собрать Exim. Если у вас нет компилятора, проконсультируйтесь с вашим поставщиком или рассмотрите возможность установки gcc, компилятора GNU C. Вы можете найти информацию о gcc на http://www fsf.org/order/ftp.html. Если вы хотите собрать монитор Exim, библиотеки X Window и заголовочные файлы также должны быть доступны.

Вам понадобится gunzip (или bunzip) и tar для распаковки исходников. Процесс сборки предполагает наличие стандартных инструментов Unix, таких как make и sed. Вам не нужен Perl для сборки или запуска Exim, но так как некоторые из связанных с ним утилит являются Perl-скриптами, неплохо убедиться, что он также установлен.

Наконец, даже если вы не используете поиск DBM в своей конфигурации, Exim требует библиотеки DBM, потому что он использует файлы DBM для хранения своих баз данных подсказок. Лицензионные версии Unix обычно содержат библиотеку функций DBM, работающих через интерфейс ndbm. Бесплатные операционные системы различаются тем, что они содержат в стандартной комплектации. В некоторых старых версиях Linux вообще нет библиотеки DBM по умолчанию, и разные дистрибьюторы решили включать разные библиотеки. Однако более поздние выпуски всех свободных операционных систем, по-видимому, стандартизированы на библиотеке Berkeley DB.

Исходный пакет Berkeley DB достиг версии 1.85, после чего был заменен релизом 2, а затем релизами 3 и 4. Более старые версии больше не поддерживаются. Вы можете найти информацию о Berkeley DB на http://vww.sleepycat.com.

22.2 Получение и распаковка исходников

Ссылка «Availability» на домашней странице Exim (http://www.exim.org) ведет на страницу, содержащую список сайтов, с которых можно загрузить исходный код. Домашняя страница также содержит информацию о статусе различных версий. Вы можете использовать свой браузер для загрузки дистрибутива в соответствующий каталог, такой как /usr/source/exim. Дистрибутив представляет собой один сжатый файл tar, например:

/usr/source/exim/exim-4.10.tar.gz

Перейдите в этот каталог и распакуйте Exim с помощью gunzip и tar[1]:

$ ed /usr/source/exim
$ gunzip exim-4.10.tar.gz
$ tar -xf exim-4.10.tar

Теперь у вас должен быть каталог с именем exim-4.10, который является деревом файлов дистрибутива. Вы можете удалить файл tar, чтобы сэкономить место, а затем перейти в каталог дистрибутива:

$ rm exim-4.10.tar
$ cd exim-4.10

Вы должны увидеть следующие файлы:

CHANGES         информация об изменениях
LICENCE         Стандартная общественная лицензия GNU
Makefile        makefile верхнего уровня
NOTICE          условия использования Exim
README          список файлов, каталогов и простых инструкций по сборке

Также могут присутствовать другие файлы, имена которых начинаются с README. Должны присутствовать следующие подкаталоги:

Local           пустой; вы помещаете сюда локальную конфигурацию сборки
os              файлы для конкретной ОС
doc             файлы документации
exim_monitor    исходные файлы для Exim monitor
scripts         скрипты скрипты используемые в процессе сборки
src             исходные файлы для Exim и некоторых утилит
util            независимые утилиты

Каталог doc содержит копию справочного руководства в виде обычного текстового файла с именем spec.txt. Это предусмотрено больше для удобного поиска, чем для последовательного чтения. Вы можете загрузить копии руководства в других форматах с ftp-сайтов; для экономии места они не включены в исходный дистрибутив. PostScript или PDF лучше всего подходят, если вы хотите сделать печатную копию, тогда как HTML и Texinfo являются индексируемыми форматами для чтения в Интернете[2]. Каталог doc также содержит информацию об изменениях и новых дополнениях к Exim. Полный список параметров времени выполнения и времени сборки можно найти в файле doc/OptionLists.txt.

Большинство утилит находятся в каталоге src и собираются с помощью бинарного файла Exim; те, которые распространяются в каталоге util, являются примерами сценариев, которые не зависят от какой-либо конфигурации времени компиляции.

  1. Дистрибутив доступен в формате bzip2, а также в формате gzip; первый значительно меньше по размеру и, следовательно, быстрее загружается. Окончательное расширение файла — .bz2 вместо .gz, и вы распаковываете его с помощью bunzip вместо gunzip.

  2. Далее в этой главе будет больше информации о документации Texinfo (22.8).

22.3 Конфигурация для сборки

Конфигурация, которую вы установили для сборки Exim, помещается в каталог с именем Local, который изначально пуст. Для самого Exim должен быть файл с именем Local/Makefile, а для монитора Exim файл с именем Local/eximon.conf. Вам никогда не потребуется изменять исходные файлы дистрибутива. Если что-то в вашей операционной системе требует других настроек, не изменяйте настройки по умолчанию; вместо этого вставьте переопределяющий параметр в Local/Makefile. Если вы поступите таким образом, вы сможете скопировать содержимое локального каталога и повторно использовать его, когда придет время для сборки следующего выпуска.

22.3.1 Содержимое Local/Makefile

Содержимое Local/Makefile представляет собой ряд настроек, таких как:

BIN_DIRECTORY=/usr/exim/bin

Настройки могут быть в любом порядке, и вы можете вставлять строки комментариев, начинающиеся с #, если хотите. Шаблон для Local/Makefile находится в src/EDITME. Он содержит примеры настроек и комментарии, описывающие, для чего они нужны. Один из способов создать ваш Local/Makefile — скопировать шаблон, а затем отредактировать копию:

$ cp src/EDITME Local/Makefile
$ vi Local/Makefile

Однако вы можете, конечно, создать Local/Makefile с нуля. Он может содержать несколько различных типов настроек:

Обязательные
Это настройки, без которых Exim не будет собираться.
Драйверы
Эти параметры определяют, какие драйверы включены в двоичный файл.
Модули
Эти параметры определяют, какие дополнительные модули (например, типы поиска) включаются в двоичный файл.
Рекомендуемые
Есть некоторые значения, которые можно установить либо во время сборки, либо в конфигурации времени выполнения. Рекомендуется установить их здесь, если это возможно.
Опциональные
Эти настройки являются просто вопросом выбора.
Система
Эти настройки зависят от конфигурации вашей операционной системы.

Содержимое Local/Makefile комбинируется с файлами из каталога OS, чтобы получить настройки, которые будут использоваться для сборки Exim следующим образом:

Некоторые настройки параметров предназначены для использования в особых случаях и редко требуются. Следующие разделы кратко охватывают те из них, которые требуются чаще всего. Файлы src/EDITME и OS/Makefile-Defaults содержат более подробную информацию в виде расширенных комментариев.

22.3.2 Обязательные настройки Local/Makefile

Есть только три параметра, которые вы должны включить в Local/Makefile. Они определяют каталог, в который будет установлен Exim, местонахождение его файла конфигурации времени выполнения и идентификатор пользователя Exim. Например:

BIN_DIRECTORY=/usr/exim/bin
CONFIGURE_FILE=/usr/exim/configure
EXIM_USER=exim

EXIM_USER определяет uid и gid для пользователя Exim (то есть идентификатор, под которым запускается Exim, когда ему не нужны привилегии суперпользователя). Если вы определяете пользователя численно, вам также необходимо указать EXIM_GROUP. Однако, если вы соберете Exim только с этими настройками, это будет не очень полезно, потому что полученный бинарник не содержит драйверов и кода для каких-либо типов поиска. Он сможет принимать сообщения, но не доставлять их.

22.3.3 Выбор драйвера в Local/Makefile

На практике большинство людей используют настройки по умолчанию в src/EDITME, когда дело доходит до выбора, какие роутеры и транспорты включить. Вот они:

ROUTER_ACCEPT=yes
ROUTER_DNSLOOKUP=yes
ROUTER_IPLITERAL=yes
ROUTER_MANUALROUTE=yes
ROUTER_QUERYPROGRAM=yes
ROUTER_REDIRECT=yes

TRANSPORT_APPENDFILE=yes
TRANSPORT_AUTOREPLY=yes
TRANSPORT_PIPE=yes
TRANSPORT_SMTP=yes

Если вы хотите включить поддержку проверки подлинности SMTP, вы должны добавить один или оба из следующих параметров:

AUTH_CRAM_MD5=yes
AUTH_PLAINTEXT=yes

22.3.4 Выбор модуля в Local/Makefile

Настройки по умолчанию для типов поиска:

LOOKUP_DBM=yes
LOOKUP_LSEARCH=yes

которые вызывают включение кода для типов поиска lsearch и dbm. Другие возможности заключаются в следующем:

LOOKUP_CDB=yes
LOOKUP_DNSDB=yes
LOOKUP_DSBARCH=yes
LOOKUP_LDAP=yes
LOOKUP_MYSQL=yes
LOOKUP_NIS=yes
LOOKUP_NISPLUS=yes
LOOKUP_ORACLE=yes
LOOKUP_PGSQL=yes
LOOKUP_WHOSON=yes

Помимо LOOKUP_DNSDB, вы должны устанавливать их только в том случае, если в вашей системе установлено соответствующее программное обеспечение. Обычно также необходимо указать, где можно найти соответствующую библиотеку и включаемые файлы. Например, если вы хотите включить поддержку MySQL, вы можете использовать:

LOOKUP_MYSQL=yes
LOOKUP_INCLUDE=-I /usr/local/mysqli/include
LOOKUP_LIBS=-L/usr/local/lib -lmysqliclient

22.3.5 Рекомендуемые настройки Local/Makefile

Несколько важных параметров можно указать либо во время сборки, либо во время выполнения. Рекомендуется установить их во время сборки, если их значения фиксированы, по двум причинам:

Если это так опасно, то почему эти настройки можно менять во время выполнения? Есть две причины:

Ниже приведены примеры параметров, относящихся к этой рекомендуемой категории.

LOG_FILE_PATH=/var/log/exim_%slog
SPOOL_DIRECTORY=/var/spool/exim
SPOOL_MODE=0640

Вам вообще не нужно устанавливать LOG_FILE_PATH, если вас устраивает значение по умолчанию, которое заключается в использовании подкаталога каталога спула, что эквивалентно в этом примере:

LOG_FILE_PATH=/var/spool/exim/log/%slog

Вам не нужно устанавливать SPOOL_MODE, если вас устраивает значение по умолчанию 0600. Установка его на 0640 позволяет членам группы Exim читать файлы спула, что необходимо для запуска монитора Exim.

22.3.6 Возможный минимальный локальный/Makefile

Вот пример минимального файла Local/Makefile, который включает рекомендуемые настройки, а также драйверы и поисковые запросы по умолчанию:

BIN_DIRECTORY=/usr/exim/bin
CONFIGURE_FILE=/usr/exim/configure
EXIM_USER=exim

LOOKUP_DBM=yes
LOOKUP_LSEARCH=yes

ROUTER_ACCEPT=yes
ROUTER_DNSLOOKUP=yes
ROUTER_IPLITERAL=yes
ROUTER_MANUALROUTE=yes
ROUTER_QUERYPROGRAM=yes
ROUTER_REDIRECT=yes

SPOOL_DIRECTORY=/var/spool/exim
SPOOL_MODE=0640

TRANSPORT_APPENDFILE=yes
TRANSPORT_AUTOREPLY=yes
TRANSPORT_PIPE=yes
TRANSPORT_SMTP=yes

Сборка Exim с этим файлом производит пригодный для использования двоичный файл, который может осуществлять прямую доставку почты.

22.3.7 Системные настройки Local/Makefile

Компилятор C называется либо cc, либо gcc, а иногда как-то еще, и разные компиляторы принимают разные настройки параметров. Системные настройки позволяют указать имя вашего компилятора и его параметры, как в следующем примере:

CC=cc
CFLAGS=-Otax -4

В некоторых операционных системах необходимо указать дополнительные библиотеки. Например, Solaris хранит функции, связанные с сокетами, в отдельной библиотеке. Makefiles, зависящий от ОС, используют LIBS для этих настроек. Например, файл Solaris содержит:

LIBS=-lsocket -lnsl -lkstat

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

Настройки в LIBS и EXTRALIBS используются для каждого создаваемого двоичного файла, который включает в себя некоторые утилиты. Если вы хотите ограничить использование определенных библиотек только бинарным файлом Exim или только бинарным файлом eximon, вы можете использовать EXTRALIBS_EXIM и EXTRALIBS_EXIMON соответственно.

Также обычно требуются настройки для библиотеки DBM:

USE_DB=yes
DBMLIB=-1db

Значения по умолчанию для этих параметров берутся из системных make-файлов в каталоге ОС, поэтому в большинстве случаев вам не нужно устанавливать их в Local/Makefile.

Если вы хотите, чтобы Exim использовал реальное время для своих временных меток, вы можете установить TIMEZONE_DEFAULT в Local/Makefile, например:

TIMEZONE_DEFAULT=EST

Это обеспечивает значение по умолчанию для параметра timezone (19.5). Если он не включен, используется значение переменной окружения TZ во время сборки Exim.

22.3.8 Дополнительные настройки в Local/Makefile

Остальные настройки в Local/Makefile являются просто вопросом выбора. Например:

EXICYCLOG_MAX=28

указывает, что утилита exicyclog должна хранить не более 28 старых файлов журнала (по умолчанию 10). Комментарии в src/EDITME объясняют, что делают настройки в каждом случае.

22.3.9 Конфигурация для сборки монитора Exim

Монитор Exim создается вместе с Exim, и если вы хотите это сделать, вы должны настроить для него подходящую конфигурацию. Обязательны только две вещи:

Шаблон с комментариями для Local/eximon.conf находится в файле exim_monitor/EDITME. В этом случае обязательных настроек нет, поэтому файл может быть совсем пустым, хотя он должен существовать. Вы можете настроить пустой файл с помощью команды:

touch Local/eximon.conf

Доступные настройки позволяют изменить размер окна и внешний вид некоторых данных. Описания каждой настройки отображаются в виде комментариев в exim_monitor/EDITME.

22.3.10 Сборка Exim для нескольких систем

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

До сих пор мы говорили об одном файле Local/Makefile, содержащем все локальные настройки. Фактически вы можете предоставить отдельные файлы для каждой операционной системы, каждой аппаратной архитектуры и каждой комбинации операционной системы и архитектуры, если хотите. Это необязательные файлы, к которым обращаются в дополнение к Local/Makefile, только если они существуют. Полный список всех возможных файлов выглядит следующим образом:

Local/Makefile
Local/Makefile-ostype
Local/Makefile-archtype
Local/Makefile-ostype-archtype

где ostype — тип операционной системы (например, Linux), а archtype — тип аппаратной архитектуры (например, i386). Файлы используются именно в таком порядке. Другими словами, настройки в Local/Makefile применяются ко всем случаям, но могут быть переопределены настройками в Local/Makefile-ostype, которые, в свою очередь, могут быть переопределены двумя другими файлами. Таким образом, один набор файлов может содержать правильные настройки для всех различных случаев с минимальным повторением.

Аналогичная схема используется для монитора Exim, где имена файлов следующие:

Local/eximon.conf
Local/eximon.conf-ostype
Local/eximon.conf-archtype
Local/eximon.conf-ostype-archtype

Значения, которые используются для ostype и archtype, получены из скриптов с именами scripts/os-type и scripts/arch-type соответственно. Если установлены какие-либо переменные среды EXIM_OSTYPE или EXIM_ARCHTYPE, вместо них используются их значения, тем самым обеспечивая средства принудительной установки определенных параметров. В противном случае сценарии пытаются найти подходящие значения, запуская команду uname. Если это не удается, проверяются переменные оболочки OSTYPE и ARCHTYPE. Затем применяется ряд специальных преобразований для получения стандартных имен, ожидаемых Exim. Вы можете запустить эти сценарии прямо из оболочки, чтобы узнать, какие значения будут использоваться в вашей системе.

Makefile верхнего уровня корректно справляется с пересборкой Exim, если любой из конфигурационных файлов редактируется. Однако, если дополнительный файл конфигурации удален, необходимо выполнить команду touch связанного необязательного файла (т. е. Local/Makefile или /Local/eximon.conf) перед перестроением.

22.4 Процесс сборки

После того, как вы создали соответствующие файлы конфигурации в локальном каталоге, вы можете запустить процесс сборки с помощью одной команды:

$ make

Первое, что она делает, это создает «каталог сборки» с именем build-ostype-archtype (например, build-SunOS5-5.8-sparc). В этот каталог устанавливаются ссылки на исходные файлы, сюда же записываются все файлы, которые создаются при сборке. Такой подход означает, что вы можете собрать Exim для разных операционных систем и разных архитектур из одного и того же набора общих исходных файлов, если хотите.

Если make запускается впервые, он вызывает сценарий, который создает makefile внутри каталога сборки, используя файлы конфигурации из каталога Local[3]. Новый makefile передается другому экземпляру make, который выполняет основную работу. Во-первых, он собирает заголовочный файл с именем config.h, используя значения из Local/Makefile, а затем собирает ряд служебных сценариев. Далее он компилирует и линкует бинарники для монитора Exim (если он настроен), ряд утилит и, наконец, сам Exim. Если все пойдет хорошо, последняя строка вывода на вашем экране должна быть:

>>> exim binary built

Если у вас возникли проблемы при сборке Exim, проверьте наличие комментариев в файле README, касающихся вашей операционной системы, а также загляните в FAQ, где описаны некоторые распространенные проблемы. Он доступен на любом FTP-сайте, а также доступен в Интернете по адресу http://vww.exim.org.

  1. Команду make makefile можно использовать для принудительной пересборки makefile в каталоге сборки, если в этом когда-либо возникнет необходимость. Если вы вносите изменения в Local/Makefile, он автоматически перестраивается при следующем запуске make.

22.5 Установка Exim

Команда:

$ make install

запускает скрипт scripts/exim_install, который копирует бинарные файлы и скрипты в каталог, имя которого указано в BIN_DIRECTORY в Local/Makefile.

Файлы копируются только в том случае, если они новее любых версий, уже находящихся в каталоге. Старые версии служебных программ переименовываются путем добавления к их именам суффикса .O. Однако сам бинарный файл Exim обрабатывается по-другому. Он устанавливается под именем, которое включает номер версии и номер компиляции, например, exim-4.10-1. Затем сценарий размещает символическую ссылку exim, указывающую на двоичный файл. Если вы обновляете предыдущую версию Exim, сценарий позаботится о том, чтобы имя exim никогда не отсутствовало в каталоге (что видно другим процессам). Это означает, что вы можете безопасно установить новую версию Exim на работающую систему.

Вы должны иметь root, когда запускаете эту команду, потому что для большинства конфигураций основной бинарный файл Exim должен принадлежать пользователю root и иметь установленный бит setuid. Таким образом, сценарий установки устанавливает root, поскольку владелец основного двоичного файла делает его setuid. Если вы хотите увидеть, что будет делать скрипт перед его реальным запуском, запустите его из каталога сборки с параметром -n (для которого привилегии root не нужны):

$ (cd build-SunOS5-5.5.1-sparc; ../scripts/exim_install -n)

Параметр -n заставляет сценарий выводить список команд, которым он будет подчиняться, фактически не подчиняясь ни одной из них.

Если файл конфигурации времени выполнения, как определено в CONFIGURE_FILE в Local/Makefile, не существует, сценарий установки копирует туда файл конфигурации по умолчанию src/configure.default. Если файл конфигурации среды выполнения уже существует, он остается нетронутым.

Конфигурация по умолчанию использует имя локального хоста в качестве единственного локального домена и настроена на локальную доставку в общий каталог /var/mail под локальным пользователем. Поддерживаются псевдонимы в файлах /etc/aliases и .forward в домашних каталогах пользователей. Удаленные домены маршрутизируются с использованием DNS с доставкой по SMTP. Существует ACL, который принимает входящую почту SMTP только для локального домена; ретрансляция с других хостов заблокирована.

Вам не нужно создавать каталог spool при установке Exim. Когда он запускается, Exim создает каталог спула, если он не существует. Подкаталоги автоматически создаются в каталоге буфера по мере необходимости.

Если вы устанавливаете Exim в системе, в которой запущен какой-то другой MTA, установка файлов с помощью команды make install сама по себе не заставит Exim заменить другой MTA. Как только вы зайдете так далеко, и все будет готово к работе, но его еще нужно «включить», прежде чем он начнет обрабатывать вашу почту. Прежде чем сделать этот последний шаг, рекомендуется провести небольшое тестирование.

22.6 Тестирование перед включением Exim

Когда все файлы на месте, вы можете запускать различные тесты, в том числе напрямую передавать сообщения в Exim и доставлять их. Затем вы можете проверить файлы журнала или запустить монитор, если хотите. Единственное, что вы не можете сделать во время работы другого MTA, — это запустить демон на стандартном порту SMTP, но если вы хотите протестировать демона, можно использовать альтернативный порт.

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

exim -bV

Если в конфигурационном файле есть какие-либо ошибки, Exim выводит сообщения об ошибках, которые также записываются в журнал паники. В противном случае он просто выводит номер версии и дату сборки. Тесты маршрутизации можно выполнить с помощью опции тестирования адресов. Например:

exim -v -bt user@your.domain

проверяет, распознается ли локальный почтовый ящик, и:

exim -v -bt user@somewhere.else.example

распознается ли удаленный почтовый ящик. Затем попробуйте использовать Exim для доставки почты, как локально, так и удаленно. Это можно сделать, передавая сообщения непосредственно в Exim, минуя пользовательский агент. Например:

exim postmaster@your.domain
From: user@your.domain
To: postmaster@your.domain
Subject: Testing Exim

This is a test message.
...

Если вы столкнулись с проблемами, просмотрите лог-файлы Exim, чтобы увидеть, есть ли там какая-либо соответствующая информация, и используйте опцию -bp, чтобы увидеть, находится ли сообщение все еще в очереди Exim. Другим источником информации является работающий Exim с включенной опцией отладки -d (для этого вам нужно быть администратором). Помимо прочего, будет показана последовательность роутеров, обрабатывающих адрес. Если сообщение застряло в спуле Exim, вы можете форсировать доставку с включенной отладкой командой вида:

exim -d -M 13A918-0000iT-00

Одна конкретная проблема, которая обнаружилась на некоторых сайтах, — это сбой локальной доставки в один общий каталог почтового ящика, для которого не установлен «sticky bit» (9.4.1). По умолчанию Exim пытается создать файл блокировки перед записью в файл почтового ящика, и если он не может создать файл блокировки, доставка откладывается. Чтобы обойти эту проблему, либо установите «sticky bit» в каталоге, либо установите определенную группу для локальной доставки и разрешите этой группе создавать файлы в каталоге (см. комментарии о транспорте local_delivery в файле конфигурации по умолчанию). Для дальнейшего обсуждения вопросов блокировки см. раздел 9.4.3.

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

exim -bd -oX 1225

а потом с помощью telnet подключиться к порту 1225[4]. Однако, если вы хотите протестировать элементы управления политикой для входящей почты, лучше использовать параметр -bh, поскольку он позволяет имитировать входящее соединение с любого IP-адреса, с которого вы захотите.

Новую версию в системе, в которой уже работает Exim, проще всего протестировать, создав двоичный файл с другим параметром CONFIGURE_FILE. В конфигурации времени выполнения все другие имена файлов и каталогов, которые использует Exim, могут быть изменены, чтобы полностью исключить их из производственной версии.

  1. Часто бывает полезно добавить к такой команде -d, чтобы включить отладку; при этом демон тестирования остается подключенным к терминалу, так что вы можете легко убить его с помощью CTRL-C.

22.7 Включение Exim

Обычное имя пути, которое используется для вызова MTA в Unix-подобных системах, — это либо /usr/sbin/sendmail, либо /usr/lib/sendmail. В некоторых случаях существуют оба пути, обычно указывающие на один и тот же файл. Пользовательские агенты используют одно из этих имен для отправки сообщений, и обычно есть ссылка из одного из сценариев загрузки системы, который запускает слушающего демона.

Процесс «включения Exim» состоит в изменении этих путей так, чтобы они ссылались на Exim, а не на предыдущий MTA. Обычно это делается путем переименования существующего файла и установки символической ссылки. Вы должны быть root, чтобы сделать это. Также рекомендуется удалить бит setuid из предыдущего MTA и/или сделать его недоступным. Например:

$ mv /usr/sbin/sendmail /usr/sbin/sendmail.old
$ chmod 0600 /usr/sbin/sendmail.old
$ ln -s /usr/exim/bin/exim /usr/sbin/sendmail

Как только это сделано, любая программа, которая вызывает /usr/sbin/sendmail, фактически будет вызывать Exim.

Некоторые операционные системы представили альтернативные способы переключения MTA. Например, если вы используете FreeBSD, вам нужно отредактировать файл /etc/mail/mailer.conf вместо создания символической ссылки, как только что было описано. Типичный пример содержимого этого файла для запуска Exim выглядит следующим образом:

sendmail         /usr/exim/bin/exim
send-mail        /usr/exim/bin/exim
mailq            /usr/exim/bin/exim -bp
newaliases       /usr/bin/true

Как только вы установили символическую ссылку или отредактировали /etc/mail/mailer.conf, ваша установка Exim «живая». Проверьте это, отправив сообщение из вашего любимого пользовательского агента.

Есть еще одна вещь, которую нужно сделать после того, как Exim запущен на вашем хосте, и это настроить задания cron. Они вам нужны для циклического просмотра файлов журналов (если только вы не используете syslog) и время от времени приводить в порядок базы данных подсказок (21.4, 21.10).

22.8 Установка документации в формате info

Некоторые операционные системы стандартизировали информационную систему GNU для документации, и если у вас одна из них, вы, вероятно, захотите установить документацию Exim в этом формате. Вы можете организовать это как часть процесса установки Exim, сделав несколько предварительных приготовлений.

Исходник версии info документации не входит в состав дистрибутива Exim, так как не всем он нужен, поэтому вам придется брать его отдельно. На сайте, с которого вы получили Exim, также должен быть файл с таким именем:

exim-texinfo-4.10.tar.gz

Это распаковывается в два файла с именем:

exim-texinfo-4.10/doc/filter.texinfo
exim-texinfo-4.10/doc/spec.texinfo

Номер версии всегда будет заканчиваться нулем, потому что основная документация не обновляется для промежуточных выпусков, где номер версии заканчивается ненулевой цифрой. Скопируйте или переместите их в каталог doc исходного дерева, которое вы используете (у которого может быть более поздний номер версии):

$ mv exim-texinfo-4.10/doc/* exim-4.12/doc

Затем добавьте в ваш Local/Makefile строку вида:

INFO_DIRECTORY=/usr/local/info

чтобы определить расположение файлов info в вашей системе. Как только это будет сделано, выполните команду:

$ make install

Она автоматически создает файлы info из исходников texinfo и устанавливает их в /usr/local/info.

22.9 Обновление до новой версии

После того, как вы получили и распаковали исходный код новой версии, вы должны прочитать файл с именем README.UPDATING. Он содержит информацию об изменениях, которые могут повлиять на работу Exim или которые требуют изменений в конфигурации. Большинство релизов Exim полностью обратно совместимы со своими предшественниками, хотя в релизе 3.00 было несовместимое изменение в конфигурации среды выполнения, а также еще одно серьезное изменение в релизе 4.00.

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

После того, как вы создали новую версию, при условии, что она совместима со старой конфигурацией среды выполнения, вы можете установить ее «на лету», ничего не останавливая. Было только два новых релиза (3.00 и 4.00), в которых это было невозможно. Если это произойдет снова, вы можете быть уверены, что README.UPDATING предупредит вас об этом и подскажет, как действовать дальше. В противном случае просто запустите:

$ make install

Как только это будет сделано, программы, вызывающие MTA, немедленно начнут использовать новую версию вместо старой. Однако процесс-демон будет продолжать запускать старую версию, пока вы не скажете ему перезагрузить себя, отправив ему сигнал HUP.

Приложение A
Раскрытие строки

Это приложение содержит список всех доступных элементов раскрытия, условий и переменных, в алфавитном порядке в каждом случае, с краткими описаниями. Более подробное обсуждение элементов и условий раскрытия можно найти в главе 17.

A.1 Элементы раскрытия

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

$<variable-name> или ${<variable-name>}
Содержимое именованной переменной подставляется. Неизвестное имя переменной вызывает ошибку.
${address:<string>}
Строка раскрывается; затем онф интерпретируется как адрес RFC 2822, и из нее извлекается эффективный адрес.
${base62:<string>}
Раскрытая строка должна полностью состоять из десятичных цифр. Число преобразуется в основание 62 и выводится в виде строки из шести символов, включая ведущие нули.
${domain:<string>}
Строка раскрывается; затем она интерпретируется как адрес RFC 2822, и из нее извлекается домен.
${escape:<string>}
Если раскрытая строка содержит какие-либо непечатаемые символы, они преобразуются в escape-последовательности, начинающиеся с обратной косой черты.
${expand:<string>}
Строка раскрывается дважды.
${extract{<key>}{<string>}}
Подполе, идентифицируемое ключом, извлекается из раскрытой строки. Если подполе не найдено, результатом является пустая строка.
${extract{<key>}{<string1>}{<string2>}{<string3>}}
Подполе, идентифицируемое ключом, извлекается из <string1>. Если подполе найдено, его значение помещается в $value и затем раскрывается <string2>; в противном случае раскрывается <string3>.
${extract{<number>}{<separators>}{<string>}}
Подполе с номером <number> извлекается из раскрытой строки. Если полей недостаточно, ничего не вставляется.
${extract{<number>}{<separators>}{<string1>}{<string2>}{<string3>}}
Подполе с номером <number> извлекается из развернутой <string1> и помещается в $value. Затем раскрывается <string2>. Если полей недостаточно, вместо них раскрывается <string3>.
${hash_<n>_<m>:<string>}
Генерируется текстовый хэш длины <n>, используя символы из первых <m> символов конкатенации строчных букв, прописных букв и цифр. См. также nhash.
Sheader_<header-name>: или $h_<header-name>:
Подставляется содержимое именованного заголовка сообщения. Если такого заголовка нет, то ошибки не возникает и ничего не подставляется.
${if <condition> {<string1>}{<string2>}}
Если <condition> истинно, <string1> раскрывается; в противном случае раскрывается <string2>.
${lc:<string>}
Буквы раскрытой строки переводятся в нижний регистр.
${length_<number>:<string>}
Заменяются начальные <number> символов раскрытой строки.
${local_part:<string>}
Раскрытая строка интерпретируется как адрес RFC 2822, и из нее извлекается локальная часть.
${lookup{<key>} <single-key-lookup-type> {<file>}{<string1>}{<string2>}}
Ключ ищется в данном файле с использованием заданного типа поиска. Если он найден, <string1> дополняется $value, содержащим данные; в противном случае раскрывается <string2>.
${lookup <query-style-lookup-type> {<query>}{<string1>}{<string2>}}
Запрос передается заданному поиску в стиле запроса. В случае успеха <string1> дополняется $value, содержащим данные; в противном случае раскрывается <string2>.
${mask:<IP address>/<bitcount>}
Подставляется IP-адрес, в котором все биты <bitcount>, кроме наиболее значимых, принудительно обнуляются, за которым следует /<bitcount>.
${md5:<string>}
Хэш MDS5 раскрытой строки вставляется в виде 32-значного шестнадцатеричного числа.
${nhash_<n>:<string>}
Строка раскрывается, а затем обрабатывается хэш-функцией, которая возвращает числовое значение в диапазоне от 0 до <n-1>.
${nhash_<n>_<m>:<string>}
Строка раскрывается, а затем обрабатывается хэш-функцией div/mod, которая возвращает два числа, разделенных косой чертой, в диапазонах от 0 до <n-1> и от 0 до <m-1> соответственно.
${perl{<subroutine>}{<arg>}{<arg>}...}
Подпрограмма Perl вызывается с заданными аргументами, максимум до восьми. Аргументы сначала раскрываются.
${quote: <string>}
Строка раскрывается, а затем заменяется в двойных кавычках, если она содержит что-либо, кроме букв, цифр, знаков подчеркивания, точек и дефисов. Любые вхождения двойных кавычек и обратной косой черты экранируются обратной косой чертой.
${quote_<lookup-type>:<string>}
К раскрытой строке применяются правила цитирования, специфичные для поиска.
${readfile{<filename>}{<eol string>}}
Содержимое файла будет вставлено, а все символы новой строки заменены на <eol string>.
${run{<command args>}{<string1>}{<string2>}}
Команда запускается в отдельном процессе. В случае успеха его вывод помещается в $value и раскрывается <string2>. В противном случае раскрывается <string2>.
${xrxquote:<string>}
Обратная косая черта вставляется перед любыми небуквенно-цифровыми символами в раскрытой строке.
${sg{<subject>}{<regex>}{<replacement>}}
Регулярное выражение неоднократно сопоставляется с раскрытой строкой темы, и для каждого совпадения заменяется раскрытая замена. $1, $2 и т. д. могут использоваться в замене для вставки захваченных подстрок.
${substr_<offset>_<length>:<string>}
Подстрока длины <length>, начинающаяся со смещения <offset>, извлекается из раскрытой строки. Отрицательные смещения отсчитываются в обратном порядке от конца строки.
${tr{<subject>}{<string1>}{<string2>}}
Раскрытый <subject> переводится путем замены символов, найденных в <string1>, соответствующими символами в <string2>.
${uc:<string>}
Буквы в раскрытой строке переводятся в верхний регистр.

A.2 Условия раскрытия

Следующие условия доступны для проверки элементом ${if при раскрытии строк:

!<condition>
Если перед любым условием стоит восклицательный знак, результат условия отрицается.
<symbolic operator> {<string1>}{<string2>}

Существует ряд символьных операторов для числовых сравнений. Они есть:

=    равно
==   равно
>    больше
>=   больше или равно
<    меньше
<=   меньше или равно

Две строки должны иметь форму десятичных целых чисел с необязательным знаком, за которыми может следовать одна из букв K или M (в верхнем или нижнем регистре), означающая умножение на 1024 или 1024x1024 соответственно.

crypteq {<string1>}{<string2>}
У условия crypteq есть два аргумента. Первый шифруется и сравнивается со вторым, который уже зашифрован. Это условие включено в бинарный файл Exim, если он поддерживает какие-либо механизмы аутентификации. В противном случае необходимо определить SUPPORT_CRYPTEQ в Local/Makefile, чтобы crypteq был включен в двоичный файл.
def:<variable-name>
За этой формой условия определения должно следовать имя одной из переменных раскрытия (A.3). Условие истинно, если именованная переменная раскрытия не содержит пустой строки. Имя переменной дается без ведущего символа доллара. Если переменная не существует, раскрытие завершается ошибкой.
def:header_<header-name>: или def:h_<header-name>:
Эта форма условия определения верна, если сообщение обрабатывается и в сообщении существует именованный заголовок. Перед header_ или h_ в условии не стоит доллар, и имя заголовка должно заканчиваться двоеточием, если за ним не следует пробел.
eq {<string1>}{<string2>}
Две подстроки сначала раскрываются. Условие истинно, если две результирующие строки идентичны, включая регистр букв.
exists {<filename>}
Подстрока сначала раскрывается, а затем интерпретируется как абсолютный путь. Условие истинно, если указанный файл (или каталог) существует.
first_delivery
Это условие без данных выполняется во время первой попытки доставки сообщения. Это ложно во время любых последующих попыток доставки.
ldapauth{<LDAP query>}}
Это условие выполняется, если пользователь и пароль в запросе LDAP успешно аутентифицированы сервером LDAP.
match {<string1>}{<string2>}
Две подстроки сначала раскрываются. Вторая обрабатывается как регулярное выражение и применяется к первой. Из-за предварительного раскрытия, если регулярное выражение содержит символы доллара или обратной косой черты, они должны быть экранированы обратной косой чертой. Также следует соблюдать осторожность, если регулярное выражение содержит фигурные скобки. Закрывающая фигурная скобка должна быть экранирована, чтобы она не воспринималась как преждевременное завершение <string2>. Нет ничего плохого в том, чтобы избежать открывающих фигурных скобок, но это не является строго необходимым. Условие истинно, если совпадение успешно.
pam {<string1>:<string2>:...}
Pluggable Authentication Module (подключаемый модуль аутентификации) (PAM) инициализируется с именем службы «exim» и именем пользователя, взятым из первого элемента в строке данных, разделенной двоеточиями (т. е. <string1>). Остальные элементы в строке данных передаются в ответ на запросы от функции аутентификации. В простом случае будет только один запрос (для пароля), поэтому данные будут состоять только из двух строк[1].
pwcheck{<user:password>}}
Это условие выполняет аутентификацию пользователя, вызывая демон Cyrus pwcheck.
queue_running
Это условие, не имеющее данных, истинно во время попыток доставки, инициированных процессами обработчика очереди, и ложно в противном случае.
radius{<authentication string>}}
Это условие выполняет аутентификацию пользователя, вызывая сервер Radius.
  1. Подключаемые модули аутентификации (http://ftp.kernel.org/pub/inuxlibs/pam/) — это средство, доступное в последних выпусках Solaris и в некоторых дистрибутивах GNU/Linux.

A.2.1 Сочетание условий

Два или более условий могут быть объединены с помощью and и or:

and {{<cond1>}{<cond2>}...}
Подусловия оцениваются слева направо. Условие истинно, если все подусловия истинны. При наличии нескольких подусловий совпадения значения числовых переменных впоследствии берутся из последнего. При обнаружении ложного подусловия следующие анализируются, но не оцениваются.
or {{<cond1>}{<cond2>}...}
Подусловия оцениваются слева направо. Условие истинно, если истинно хотя бы одно из подусловий. Когда истинное подусловие найдено, следующие анализируются, но не оцениваются. Если имеется несколько подусловий совпадения, значения числовых переменных впоследствии берутся из первого, выполнившего успешное условие.

Обратите внимание, что and и or являются полными условиями сами по себе и предшествуют их спискам подусловий. Каждое подусловие должно быть заключено в фигурные скобки внутри общих фигурных скобок, содержащих список. Не используется повторение if.

A.3 Переменные расширения

Подстановки переменных, доступные для использования в строках расширения, следующие:

$0, $1 и т. д.
Когда раскрытие matches соответствует успешному выполнению, эти переменные содержат захваченные подстроки, идентифицированные регулярным выражением во время последующей обработки строки успеха содержащего элемент раскрытия if. Они также могут быть установлены извне каким-либо другим процессом сопоставления, который предшествует раскрытию строки.
$acl_verify_message
Во время раскрытия модификатора message в операторе ACL после неудачной проверки адреса эта переменная содержит исходное сообщение об ошибке, которое будет переопределено раскрытой строкой.
$address_data
Эта переменная устанавливается с помощью опции address_data в роутерах. Затем значение остается с адресом, пока оно обрабатывается последующими роутерами и, в конечном итоге, транспортом.
$address_file

Когда сообщение направляется в определенный файл в результате псевдонима или переадресации, эта переменная содержит имя файла во время работы транспорта. Например, используя конфигурацию по умолчанию, если у пользователя r2d2 есть файл .forward, содержащий:

/home/r2d2/savemail

затем, когда работает транспорт address_file, $address_file содержит /home/r2d2/savemail. В других случаях переменная пуста.

$address_pipe
Когда сообщение направляется в канал (в результате псевдонимов или переадресации), эта переменная содержит команду канала во время работы транспорта.
$authenticated_id
Когда сервер успешно аутентифицирует клиента, он может быть настроен на сохранение части аутентификационной информации в переменной $authenticated_id. Например, конфигурация аутентификатора user/password может сохранить имя пользователя для использования в роутерах.
$authenticated_sender
Когда клиентский хост аутентифицировал себя, Exim обращает внимание на параметр AUTH= в команде SMTP MAIL. В противном случае он принимает синтаксис, но игнорирует данные. Если данные не являются строкой <>, они устанавливаются как аутентифицированный отправитель сообщения, и значение доступно во время доставки в переменной $authenticated_sender.
$body_linecount
Эта переменная содержит количество строк в теле сообщения.
$bounce_recipient
Это значение устанавливается в качестве адреса получателя рикошета, пока Exim его создает. Оно полезно, если используется настраиваемый текстовый файл сообщений об ошибках.
$caller_gid
Эта переменная содержит идентификатор группы, под которой был запущен процесс, вызвавший Exim. Это не то же самое, что идентификатор группы отправителя сообщения (см. $originator_gid). Если Exim перезапустится, эта переменная в новом воплощении обычно содержит gid Exim.
$caller_uid
Эта переменная содержит идентификатор пользователя, под которым был запущен процесс, вызвавший Exim. Это не то же самое, что идентификатор пользователя отправителя сообщения (см. $originator_uid). Если Exim перезапустится, эта переменная в новом воплощении обычно содержит Exim uid.
$compile_date
Эта переменная содержит дату компиляции бинарного файла Exim.
$compile_number
Процесс сборки Exim ведет подсчет количества компиляций Exim. Это служит для различения разных компиляций одной и той же версии программы.
$dnslist_domain
Когда обнаруживается, что клиентский хост находится в черном списке DNS, доменное имя списка помещается в эту переменную, чтобы его можно было включить в сообщение об отклонении.
$dnslist_text
Когда клиентский хост находится в черном списке DNS, содержимое любой связанной с ним записи TXT помещается в эту переменную.
$dnslist_value
Когда клиентский хост находится в черном списке DNS, IP-адрес из записи ресурса помещается в эту переменную.
$domain

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

Когда происходит удаленная или локальная доставка, если все адреса, которые обрабатываются одновременно, содержат один и тот же домен, он помещается в $domain. В противном случае эта переменная пуста во время работы транспорта. Транспорты должны быть ограничены одновременной обработкой только одного домена, если его значение требуется во время транспортировки. Это значение по умолчанию для локальных транспортов.

В конце доставки, если все отложенные адреса имеют один и тот же домен, он устанавливается в $domain во время раскрытия delay_warning_condition.

Переменная $domain также используется в некоторых других случаях, не связанных с доставкой сообщений:

  • Когда для команды RCPT выполняется ACL, $domain содержит домен адреса получателя.

  • Когда обрабатывается элемент конфигурации перезаписи адреса, $domain содержит доменную часть перезаписываемого адреса; его можно использовать в расширении адреса замены.

  • Всякий раз, когда сканируется список доменов, $domain содержит предметный домен.

  • При расширении параметра smtp_etrn_command $domain содержит полный аргумент команды ETRN.

$domain_data
Когда роутер имеет настройку параметра доменов или ACL использует условие доменов, и домен сопоставляется при поиске файлов, данные, полученные в результате поиска, помещаются в $domain_data. Для роутера значение остается доступным во время работы роутера и любого последующего транспорта. Для ACL значение остается доступным, пока обрабатывается остальная часть инструкции.
$home
Когда для роутера установлена опция check_local_user, домашний каталог пользователя помещается в $home, когда проверка проходит успешно. В частности, это означает, что он устанавливается во время запуска файлов фильтров пользователей. Роутер также может явно установить домашний каталог для использования транспортом; это может быть отменено настройкой самого транспорта.
Shost

Когда транспорт smtp расширяет возможности шифрования с помощью TLS, $host содержит имя хоста, к которому он подключен. Аналогично, при использовании в клиентской части конфигурации аутентификатора $host содержит имя сервера, к которому подключен клиент.

При использовании в транспортном фильтре $host относится к хосту, участвующему в текущем соединении. Когда локальный транспорт запускается в результате настройки роутером списка хостов, $host содержит имя первого хоста.

$host_address
Эта переменная устанавливается на IP-адрес удаленного хоста всякий раз, когда $host устанавливается для удаленного подключения.
$host_lookup_failed
Эта переменная содержит «1», если сообщение пришло с удаленного хоста и была попытка найти имя хоста по его IP-адресу, но попытка не удалась. В противном случае значение переменной равно «О».
$interface_address
Для сообщения, полученного через соединение TCP/IP, эта переменная содержит адрес используемого IP-интерфейса.
$interface_port
Для сообщения, полученного через соединение TCP/IP, эта переменная содержит используемый порт.
$local_part

Когда адрес маршрутизируется или доставляется сам по себе, эта переменная содержит локальную часть. Если распознан префикс или суффикс локальной части, он не включается в значение. Когда несколько адресов доставляются в пакете локальным или удаленным транспортом, $local_part не устанавливается.

Когда сообщение доставляется в pipe, appendfile или autoreply в результате перенаправления, $local_part устанавливается в локальную часть родительского адреса.

Когда ACL выполняется для команды RCPT, $local_part содержит локальную часть адреса получателя.

Когда обрабатывается элемент перезаписи конфигурации, $local_part содержит локальную часть перезаписываемого адреса.

$local_part_data
Если роутер имеет настройку параметра local_parts или ACL использует условие local_parts, а локальная часть сопоставляется при поиске файла, данные, полученные в результате поиска, помещаются в $local_part_data. Для роутера значение остается доступным во время работы роутера и любого последующего транспорта. Для ACL значение остается доступным, пока обрабатывается остальная часть инструкции.
$local_part_prefix
Когда адрес маршрутизируется или доставляется, и распознан определенный префикс для локальной части, он доступен в этой переменной, будучи удаленным из $local_part.
$local_part_suffix
Когда адрес перенаправляется или доставляется и распознается определенный суффикс для локальной части, он доступен в этой переменной, будучи удаленным из $local_part.
$local_scan_data
Эта переменная содержит текст, возвращаемый функцией local_scan() при получении сообщения. Описание local_scan() можно найти в справочном руководстве; она не рассматривается в этой книге.
$localhost_number
Переменая содержит раскрытое значение опции localhost_number. Раскрытие происходит после прочтения основных опций.
$message_age
Эта переменная устанавливается в начале попытки доставки, чтобы содержать количество секунд с момента получения сообщения. Она не меняется во время одной попытки доставки.
$message_body
Эта переменная содержит начальную часть тела сообщения во время его доставки и предназначена в основном для использования в файлах фильтров. Максимальное количество используемых символов тела задается параметром конфигурации message_body_visible; значение по умолчанию — 500. Новые строки преобразуются в пробелы, чтобы облегчить поиск фраз, которые могут быть разделены разрывом строки, а двоичные нули также преобразуются в пробелы.
$message_body_end
Эта переменная содержит последнюю часть тела сообщения во время его доставки. Формат и максимальный размер такие же, как для $message_body.
$message_body_size
Когда сообщение принимается или доставляется, эта переменная содержит размер тела в байтах. Отсчет начинается с символа после пустой строки, отделяющей тело от строк заголовка. Новые строки включены в счет. См. также $message_size.
$message_headers
Эта переменная содержит объединение всех строк заголовка при обработке сообщения. Они разделены символами новой строки.
$message_id
Когда сообщение принимается или доставляется, эта переменная содержит уникальный идентификатор сообщения, который используется Exim для идентификации сообщения.
$message_size
Когда сообщение принимается или доставляется, эта переменная содержит его размер в байтах. В размер включаются те заголовки, которые были получены вместе с сообщением, но не те (например, Envelope-to:), которые добавляются в отдельные доставки. См. также $message_body_size.
от $n0 до $n9
Эти переменные являются счетчиками, которые можно увеличивать с помощью команды add в файлах фильтров.
$original_domain

Когда адрес верхнего уровня обрабатывается для доставки, он содержит то же значение, что и $domain. Однако, если обрабатывается «дочерний» адрес (например, сгенерированный псевдонимом, файлом переадресации или фильтра), эта переменная содержит домен исходного адреса. Это отличается от $parent_domain, когда имеется более одного уровня псевдонима или переадресации. Когда более одного адреса доставляется в пакете локальным или удаленным транспортом, $original_domain не устанавливается.

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

$original_local_part
Это аналог $original_domain и содержит локальную часть исходного адреса верхнего уровня.
$originator_gid
Это значение $caller_gid, которое было установлено при получении сообщения. Для сообщений, полученных через командную строку, это gid пользователя-отправителя. Для сообщений, полученных SMTP через TCP/IP, это gid пользователя Exim.
$originator_uid
Значение $caller_uid, которое было установлено при получении сообщения. Для сообщений, полученных через командную строку, это uid пользователя-отправителя. Для сообщений, полученных SMTP через TCP/IP, это uid пользователя Exim.
$parent_domain
Эта переменная пуста, за исключением случаев, когда обрабатывается «дочерний» адрес (сгенерированный, например, псевдонимом или пересылкой), и в этом случае он содержит домен непосредственно предшествующего родительского адреса.
$parent_local_part
Эта переменная пуста, за исключением случаев, когда обрабатывается «дочерний» адрес (сгенерированный, например, псевдонимом или пересылкой), и в этом случае он содержит локальную часть непосредственно предшествующего родительского адреса.
$pid
Эта переменная содержит идентификатор текущего процесса.
$pipe_addresses
Это не переменная раскрытия, но она упоминается здесь, потому что строка Spipe_addresses обрабатывается специально в спецификации команды для транспорта pipe и в транспортных фильтрах. Ее нельзя использовать в общих строках расширения, и при ее обнаружении возникает ошибка «unknown variable» (неизвестная переменная).
$primary_hostname
Эта переменная содержит значение, установленное в файле конфигурации, или значение, определенное при запуске функции uname().
$qualify_domain
Эта переменная содержит значение, установленное для этой опции в файле конфигурации.
$qualify_recipient
Эта переменная содержит значение, установленное для этой опции в файле конфигурации, или, если оно не установлено, значение $qualify_domain.
$rcpt_count
Когда сообщение принимается SMTP, эта переменная содержит количество полученных команд RCPT и может использоваться в ACL. В других случаях ее значение не определено.
$received_for
Если во входящем сообщении есть только один адрес получателя при построении строки заголовка Received:, эта переменная содержит этот адрес.
$received_protocol
Когда сообщение обрабатывается, эта переменная содержит имя протокола, по которому оно было получено.
$recipients
Эта переменная содержит список получателей конверта для сообщения, но распознается только в файле системного фильтра, чтобы предотвратить доступ к bcc: получателям обычных пользователей. Адреса в заменяющем тексте разделяются запятой и пробелом.
$recipients_count

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

Пока сообщение принимается по SMTP, число увеличивается для каждого принятого получателя. На него можно сослаться в ACL.

$reply_address
Когда сообщение обрабатывается, эта переменная содержит содержимое заголовка Reply-To:, если он существует, или, в противном случае, содержимое заголовка From:.
$return_path
Когда сообщение доставляется, эта переменная содержит обратный путь (то есть поле отправителя, которое будет отправлено как часть конверта). Он не заключен в угловые скобки. Во многих случаях $return_path имеет то же значение, что и $sender_address, но если, например, входящее сообщение в список рассылки было расширено роутером, который указывает другой адрес для рикошетов, $return_path содержит новый адрес ошибки, в то время как $sender_address содержит исходный адрес отправителя, который был получен с сообщением.
$return_size_limit
Переменная содержит значение, установленное в опции return_size_limit, округленное до числа, кратного 1000.
$self_hostname
Когда адрес направляется на предположительно удаленный хост, который оказывается локальным, происходящее контролируется общей опцией self роутера. Одно из его значений вызывает передачу адреса другому роутеру. Когда это происходит, $self_hostname устанавливается на имя локального хоста, с которым столкнулся исходный роутер. В других обстоятельствах его содержимое недействительно.
$sender_address
Когда сообщение обрабатывается, эта переменная содержит адрес отправителя, который был получен в конверте сообщения. Для рикошета значением этой переменной является пустая строка.
$sender_address_domain
Эта переменная содержит доменную часть $sender_address.
$sender_address_local_part
Эта переменная содержит локальную часть $sender_address.
$sender_fullhost
Когда сообщение было получено от удаленного хоста, эта переменная содержит имя хоста и IP-адрес в одной строке, которая всегда заканчивается IP-адресом в квадратных скобках. Формат остальной части строки зависит от того, выдал ли хост SMTP-команду HELO или EHLO, и было ли имя хоста проверено путем поиска его IP-адреса. (Поиск IP-адреса может быть форсирован опцией host_lookup, независимо от проверки.) Простое имя хоста в начале строки является проверенным именем хоста; если его нет, проверка либо не удалась, либо не была запрошена. Имя хоста в скобках является аргументом команды HELO или EHLO. Оно опускается, если оно идентично проверенному имени хоста или IP-адресу хоста в квадратных скобках.
$sender_helo_name
Когда получено сообщение от удаленного хоста, который выдал команду HELO или EHLO, первый элемент в аргументе этой команды помещается в эту переменную. Также устанавливается, используется ли HELO или EHLO, когда сообщение получено локально по протоколу SMTP с помощью параметров -bs или -bS.
$sender_host_address
Когда сообщение получено от удаленного хоста, эта переменная содержит IP-адрес хоста. Для сообщений, отправленных локально, она пуста.
$sender_host_authenticated
Во время доставки сообщения эта переменная содержит имя (не общедоступное имя) драйвера проверки подлинности, который успешно аутентифицировал клиента, от которого было получено сообщение. Пусто, если не было успешной аутентификации.
$sender_host_name
Для сообщения, полученного от удаленного хоста, эта переменная содержит имя хоста, полученное путем поиска его IP-адреса. Если поиск не удался или не был запрошен, эта переменная содержит пустую строку.
$sender_host_port
Для сообщения, полученного от удаленного хоста, эта переменная содержит номер порта, который использовался на удаленном хосте.
$sender_ident
Для сообщения, полученного от удаленного хоста, эта переменная содержит идентификатор, полученный в ответ на запрос RFC 1413. Для локально отправленного сообщения эта переменная содержит логин пользователя, вызвавшего Exim.
$sender_rcvhost
Эта переменная предназначена специально для использования в строках заголовков Received:. Она начинается либо с проверенного имени хоста (полученного в результате обратного поиска DNS), либо, если нет проверенного имени хоста, с IP-адреса в квадратных скобках. После этого может быть текст в скобках. Когда первым элементом является проверенное имя хоста, первым в круглых скобках является IP-адрес в квадратных скобках. Также могут быть элементы вида helo=xxxx, если использовались HELO или EHLO и их аргумент не был идентичен реальному имени хоста или IP-адресу, и ident=xxxx, если доступна строка идентификатора RFC 1413. Если в скобках присутствуют все три элемента, в строку вставляется новая строка и табуляция для улучшения форматирования заголовка Received:.
$smtp_command_argument
Во время выполнения ACL для проверки команд AUTH, EXPN, ETRN или VRFY, эта переменная содержит аргумент для команды SMTP.
от $sn0 до $sn9
Эти переменные являются копиями значений аккумуляторов от $n0 до $n9, которые были текущими в конце файла системного фильтра. Это позволяет файлу системного фильтра задавать значения, которые можно проверить в файлах фильтров пользователей. Например, системный фильтр может установить значение, указывающее, насколько вероятно, что сообщение является нежелательной почтой.
$spool_directory
Эта переменная содержит имя каталога спулинга Exim.
$thisaddress
Эта переменная устанавливается только во время обработки команды foranyaddress в файле фильтра (10.15.7).
$tls_cipher
Для сообщения, полученного от удаленного хоста по зашифрованному SMTP-соединению, этой переменной присваивается значение шифра, который был согласован. В противном случае она пуста.
$tls_peerdn
Когда сообщение получено от удаленного хоста через зашифрованное SMTP-соединение, и Exim настроен на запрос сертификата от клиента, эта переменная устанавливается в значение Distinguished Name сертификата.
$tod_bsdinbox
Эта переменная содержит дату и время суток в формате, необходимом для файлов почтовых ящиков в стиле BSD (например, Thu Oct 17 17:14:09 1995).
$tod_epoch
Время и дата в секундах с начала эпохи Unix.
$tod_full
Эта переменная содержит полную версию даты и времени (например, Wed, 16 Oct 1995 09:51:40 +0100). Часовой пояс всегда указывается как числовое смещение от GMT/UTC.
$tod_log
Эта переменная содержит дату и время в формате, используемом для записи файлов журнала Exim (например, 1995-10-12 15:32:29).
$value
Эта переменная содержит результат поиска расширения, операции извлечения или внешней команды.
$version_number
Эта переменная содержит номер версии Exim.
$warn_message_delay
Эта переменная устанавливается только при создании сообщения, предупреждающего о задержке доставки (19.8.5).
$warn_message_recipients
Эта переменная устанавливается только при создании сообщения, предупреждающего о задержке доставки (19.8.5).

Приложение B
Регулярные выражения

Поддержка регулярных выражений в Exim обеспечивается библиотекой PCRE, которая реализует регулярные выражения, синтаксис и семантика которых такие же, как в Perl[1]. Описание здесь взято из документации PCRE и предназначено в качестве справочного материала. Введение в регулярные выражения см. в статье Mastering Regular Expressions (Освоение регулярных выражений) Джеффри Фридала (O'Reilly).

Когда вы используете регулярное выражение в конфигурации Exim, вы должны быть немного осторожны с символами обратной косой черты, доллара и фигурных скобок, которые довольно часто появляются в регулярных выражениях, потому что эти символы также интерпретируются Exim особым образом. Обратная косая черта является специальной внутри строк в кавычках, и все четыре символа являются специальными в раскрытой строке. Один из способов настройки таких элементов конфигурации заключается в следующем:

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

^(?>[a-z]+)\d{3}\.enc\.example$

Если вы хотите использовать этот шаблон в опции доменов на роутере, вам нужно будет установить его как:

domains = \N^(?>[a-z]+)\d{3}\.enc\.example$\N

или

domains = ^(?>[a-z]+)\\d\{3\}\\.enc\\.example\$

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

  1. PCRE был реализован и поддерживается тем же автором, что и Exim. Хотя изначально она была написана для поддержки Exim, это автономная библиотека, которая теперь используется во многих других программах. Однако. версия, включенная в исходный код Exim, минимальна. Если вы хотите использовать PCRE в других программах, вам следует получить и установить полный дистрибутив с ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre.

B.1 Тестирование регулярных выражений

Библиотека PCRE поставляется с программой под названием pcretest, которую можно использовать для проверки регулярных выражений, хотя изначально она была написана для проверки самой библиотеки. В состав дистрибутива Exim входит pcretest, но он не устанавливается автоматически. Если вы собрали Exim из исходников, вы найдете pcretest в каталоге сборки.

Когда вы запускаете pcretest, он запрашивает регулярное выражение, которое должно быть указано между разделителями и может сопровождаться флагами. Затем он предлагает сопоставить последовательность строк данных; для каждого из них выводятся результаты совпадения. Например:

$ peretest
PCRE version 3.9 02-Jan-2002

  re> /^abc(\d+)/i
data> aBc123xyz
  0: aBc123
  1: 123
data> xyz
No match

После успешного совпадения строка 0 соответствует всему шаблону, а строки 1, 2 и т. д. — содержимое захваченных подстрок. Для получения более подробной информации о pcretest взгляните на его спецификацию, которая находится в файле doc/pcretest.txt в дистрибутиве Exim.

B.2 Метасимволы

Регулярное выражение — это шаблон, который сопоставляется со строкой слева направо. Большинство символов обозначают сами себя в шаблоне и совпадают с соответствующими символами в строке. В качестве тривиального примера шаблон:

The quick brown fox

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

Существует два разных набора метасимволов: те, которые распознаются в любом месте шаблона, кроме квадратных скобок, и те, которые распознаются в квадратных скобках. За пределами квадратных скобок метасимволы следующие:

\ Общий escape-символ с несколькими вариантами использования
^ Подтверждает начало объекта (или строки в многострочном режиме)
$ Подтверждает конец объекта (или строки в многострочном режиме)
. Соответствует любому символу, кроме символа новой строки (по умолчанию)
[ Начало определения класса символов
| Начало альтернативной ветви
( Начало подшаблона
) Конец подшаблона
? Расширяет значение (
также квантификатор 0 или 1
также минимизатор квантификации
* квантификатор 0 или более
+ квантификатор 1 или более
{ Начальный квантификатор минимума/максимума

Часть шаблона, заключенная в квадратные скобки, называется классом символов. В классе символов единственными метасимволами являются следующие:

\ Общий escape-символ
^ Инвертировать класс, если этот символ первый в классе
- Указывает диапазон символов
] Завершает класс символов

В следующих разделах описывается использование каждого из метасимволов.

B.3 Обратная косая черта

Символ обратной косой черты имеет несколько применений. Во-первых, если за ним следует не буквенно-цифровой символ, это лишает этот символ особого значения. Такое использование обратной косой черты в качестве escape-символа применяется как внутри, так и вне классов символов.

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

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

Второе использование обратной косой черты обеспечивает способ кодирования непечатаемых символов в шаблонах видимым образом. Нет ограничений на появление непечатаемых символов, за исключением двоичного нуля, который завершает шаблон, но когда шаблон подготавливается путем редактирования текста, обычно проще использовать одну из следующих escape-последовательностей, чем двоичный символ, который он представляет:

\a Звуковой сигнал, то есть символ BEL (символ 7)
\cx «Control-x», где x — любой символ
\e Escape (символ 27)
\f Перевод страницы (символ 12)
\n Новая строка или перевод строки (символ 10)
\r Возврат каретки (символ 13)
\t Tab (символ 9)
\xhh Символ с шестнадцатеричным кодом <hh>
\ddd Символ с восьмеричным кодом <ddd>
или обратная ссылка (см. далее в этом приложении)

Точный эффект \cx следующий: если x — строчная буква, она преобразуется в верхний регистр. Затем бит 6 символа (шестнадцатеричный 40) инвертируется. Таким образом, \cz становится шестнадцатеричным 1A, а \c{ становится шестнадцатеричным 3B, а \c; становится шестнадцатеричным 7B.

После \x читаются до двух шестнадцатеричных цифр (буквы могут быть в верхнем или нижнем регистре).

После \0 читаются еще две восьмеричные цифры. В обоих случаях, если цифр меньше двух, используются только те, которые присутствуют. Таким образом, последовательность \0\x\07 задает два двоичных нуля, за которыми следует символ BEL. Убедитесь, что вы указали две цифры после начального нуля, если следующий символ сам является восьмеричной цифрой.

Обработка обратной косой черты, за которой следует цифра, отличная от 0, сложна. Вне класса символов PCRE читает его и любые последующие цифры как десятичное число. Если число меньше 10 или если в выражении было по крайней мере столько же предыдущих захватывающих левых скобок, вся последовательность берется как обратная ссылка (back reference) (B.12).

Внутри класса символов или если десятичное число больше 9, а захваченных подшаблонов не так много, PCRE повторно считывает до трех восьмеричных цифр, следующих за обратной косой чертой, и генерирует один байт из 8 младших значащих битов значения. Любые последующие цифры обозначают сами себя.

Вот несколько примеров различных способов интерпретации числа:

\040 Другой способ написания пробела
\40 То же самое при наличии менее 40 предыдущих подшаблонов захвата
\7 Всегда обратная ссылка
\11 Может быть обратной ссылкой или другим способом записи табуляции
\011 Всегда табуляция
\0113 Табуляция, за которой следует символ 3
\113 Символ с восьмеричным кодом 113 (так как может быть не более 99 обратных ссылок)
\377 Байт, каждый бит которого равен 1
\81 Либо обратная ссылка, либо двоичный ноль, за которым следуют два символа 8 и 1.

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

Все последовательности, определяющие однобайтовое значение, могут использоваться как внутри, так и вне классов символов. Кроме того, внутри класса символов последовательность \b интерпретируется как символ возврата (символ 8). Вне класса символов она имеет другое значение (см. далее в этом приложении).

Третье использование обратной косой черты предназначено для указания общих типов символов:

\d Любая десятичная цифра
\D Любой символ, не являющийся десятичной цифрой
\s Любой пробельный символ
\S Любой символ, не являющийся пробелом
\w Любой символ «слово» (может использоваться в слове)
\W Любой символ «не слово» (не может использоваться в слове)

Каждая пара escape-последовательностей разбивает полный набор символов на два непересекающихся набора. Любой заданный символ соответствует одному и только одному из каждой пары.

Символ «слово» — это любая буква, цифра или символ подчеркивания (то есть любой символ, который может быть частью «слова» Perl).

Эти последовательности типов символов могут появляться как внутри, так и вне классов символов. Каждое из них соответствует одному символу соответствующего типа. Если текущая точка сопоставления находится в конце строки, все они терпят неудачу, потому что нет символа для сопоставления.

Четвертое использование обратной косой черты для некоторых простых утверждений. Утверждение указывает условие, которое должно быть выполнено в определенный момент совпадения, без использования каких-либо символов из строки. Использование подшаблонов для более сложных утверждений описано позже (B.13). Утверждения с обратной косой чертой выглядят следующим образом:

\b Граница слова
\B Не граница слова
\A Начало строки (независимо от многострочного режима)
\Z Конец строки или новая строка в конце (независимо от многострочного режима)
\z Конец строки (независимо от многострочного режима)

Эти утверждения могут не появляться в классах символов (но обратите внимание, что \b имеет другое значение, а именно символ возврата, внутри класса символов).

Граница слова — это позиция в строке, где текущий и предыдущий символы не совпадают одновременно с \w или \W (то есть один соответствует \w, а другой соответствует \W), или начало или конец слова, если первый или последний символ соответствует \w соответственно.

Утверждения \A, \Z и \z отличаются от традиционных циркумфлекса и доллара (B.5) тем, что они совпадают только в самом начале и конце строки строки, независимо от установленных параметров. Разница между \Z и \z заключается в том, что \Z совпадает перед новой строкой, которая является последним символом строки, а также в конце строки, тогда как \z соответствует только в конце.

B.4 Изменение параметров сопоставления

Некоторые детали процесса сопоставления контролируются параметрами, которые можно изменить в самом шаблоне. Синтаксис представляет собой последовательность букв параметров Perl, заключенных между (? и ). Буквы опций следующие:

i Регистронезависимое сопоставление
m «Многострочное» сопоставление (B.5)
s «Однострочное» сопоставление (B.6)
x Игнорировать буквальные пробелы в шаблоне

Например, (?im) задает безрегистровое многострочное сопоставление. Также можно отключить эти параметры, поставив перед буквой дефис; комбинированная установка и снятие, например (?im-sx), также разрешены. Если буква появляется и до, и после дефиса, параметр не установлен.

Объем изменений этих параметров зависит от того, где в шаблоне происходит настройка. Для настроек, которые находятся за пределами какого-либо подшаблона в круглых скобках (определенных далее в этом приложении), эффект длится до конца шаблона или до тех пор, пока параметр не будет изменен явным образом[2]. Например:

a(?i)bc

соответствует abc, aBc, aBC и abC, тогда как:

a(?i)b(?-i)c

соответствует только abc и aBc. Если шаблон содержит несколько альтернатив, изменения опции, сделанные в одной альтернативе, распространяются на последующие ветви. Например:

a(?i)b|c

соответствует ab, aB, c и C, хотя при сопоставлении C первая ветвь отбрасывается до установки параметра. Это связано с тем, что эффекты настроек параметров проявляются во время компиляции. В противном случае было бы очень странное поведение.

Если изменение параметра происходит внутри подшаблона, это изменение ограничивается этим подшаблоном. Например:

(a(?i)b)c

соответствует abc и aBc и никаким другим строкам.

Помимо стандартных опций Perl, в PCRE есть несколько собственных дополнительных. Вот они:

U Обратное жадное/внутреннее сопоставление (B.11) R Рекурсивное сопоставление (B.17)
  1. Это поведение соответствует Perl 5.8 и было изменено в версии 4.0 библиотеки регулярных выражений PCRE. В более ранних версиях изменение «верхнего уровня» применяется ко всему шаблону, независимо от того, где оно встречается в шаблоне.

B.5 Циркумфлекс и доллар

Вне класса символов, в режиме сопоставления по умолчанию, символ циркумфлекса является утверждением, которое истинно только в том случае, если текущая точка сопоставления находится в начале строки темы. Внутри класса символов циркумфлекс имеет совершенно другое значение (B.7).

Циркумфлекс используется в конфигурационных файлах Exim, чтобы указать, что введенная им строка является регулярным выражением, а не литеральной строкой, и интерпретируется как часть этого выражения. Однако, когда строка может быть только регулярным выражением (например, как часть условия совпадения в раскрытии строки), начальный циркумфлекс не нужен.

Если все возможные альтернативы начинаются с циркумфлекса (то есть, если шаблон ограничен совпадением только в начале субъекта), он называется «привязанным» шаблоном. (Есть и другие конструкции, которые могут привести к закреплению шаблона.)

Символ доллара — это утверждение, которое истинно только в том случае, если текущая точка совпадения находится в конце строки темы или непосредственно перед символом новой строки, который является последним символом в строке (по умолчанию). Доллар не обязательно должен быть последним символом шаблона, если задействовано несколько альтернатив, но он должен быть последним элементом в любой ветви, в которой он появляется. Доллар не имеет особого значения в классе символов.

Значения символов циркумфлекса и доллара изменяются, если установлена опция (?m). В Perl это называется «многострочной» опцией. В этом случае циркумфлекс и доллар совпадают сразу после и непосредственно перед внутренним символом новой строки, соответственно, в дополнение к совпадению в начале и конце строки. Например, шаблон ^abc$ соответствует строке def\nabc (где \n представляет новую строку) в многострочном режиме, но не в другом. Следовательно, шаблоны, закрепленные в однострочном режиме, поскольку все ветви начинаются с ^, не закрепляются в многострочном режиме.

Обратите внимание, что последовательности \A, \Z и \z можно использовать для сопоставления начала и конца строки в обоих режимах, и если все ветви шаблона начинаются с \A, шаблон всегда закрепляется.

B.6 Точка (dot, period, full stop)

Вне класса символов точка в шаблоне соответствует любому символу в теме, включая непечатаемый символ, но не символ новой строки (по умолчанию). Если установлена опция (?s), точка также соответствует новой строке. (В Perl это называется «однострочной» опцией.) Обработка точки полностью независима от обработки циркумфлекса и доллара, единственная связь заключается в том, что они оба включают символы новой строки. Точка не имеет особого значения в классе символов.

B.7 Квадратные скобки

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

Класс символов соответствует одному символу в теме; символ должен быть в наборе символов, определенном классом, если только первый символ в классе не является циркумфлексом, и в этом случае подлежащий символ не должен быть в наборе, определенном классом. Если в качестве члена класса действительно требуется циркумфлекс, убедитесь, что он не является первым символом, или экранируйте его обратной косой чертой.

Например, класс символов [aeiou] соответствует любому гласному в нижнем регистре, а [^aeiou] соответствует любому символу, который не является гласным в нижнем регистре. Такое использование циркумфлекса — просто удобная нотация для указания символов, которые есть в классе, путем перечисления тех, которых нет. Класс символов, который начинается с циркумфлекса, не является утверждением: он по-прежнему использует символ из строки объекта и терпит неудачу, если текущий указатель находится в конце строки.

Когда установлено безрегистровое сопоставление, любые буквы в классе представляют их версии как в верхнем, так и в нижнем регистре, поэтому, например, безрегистровое [aeiou] соответствует U так же, как и u, а безрегистровое [^aeiou] не соответствует U, тогда как версия с регистром соответствовала бы.

Символ новой строки никогда не обрабатывается каким-либо особым образом в классах символов, независимо от установки параметров (?s) или (?m). Такой класс, как [^a], всегда соответствует новой строке.

Символ дефиса (минус) может использоваться для указания диапазона символов в классе символов. Например, [d-m] соответствует любой букве от d до m включительно. Если в классе требуется дефис, он должен быть экранирован обратной косой чертой или стоять в позиции, где его нельзя интерпретировать как указание диапазона, обычно как первый или последний символ в классе.

Невозможно использовать символ ] в качестве конечного символа диапазона. Такой шаблон, как [W-]46], интерпретируется как класс из двух символов W и -, за которыми следует буквальная строка 46], поэтому он будет соответствовать W46] или -46]. Однако, если ] экранируется обратной косой чертой, это интерпретируется как конец диапазона, поэтому [W-\]46] интерпретируется как единый класс, содержащий диапазон, за которым следуют два отдельных символа. Восьмеричное или шестнадцатеричное представление ] также может использоваться для завершения диапазона.

Диапазоны работают в последовательности сопоставления ASCII. Их также можно использовать для символов, указанных в числовом виде (например, [\000-\037]). Если диапазон, включающий буквы, используется при заданном сопоставлении без регистра, он соответствует буквам в любом случае. Например, [W-c] эквивалентно [] [\^_`wxyzabc] без учета регистра.

Типы символов \d, \D, \s, \S, \w и \W также могут появляться в классе символов и добавлять в класс символы, которым они соответствуют. Например, [\dABCDEF] соответствует любой шестнадцатеричной цифре.

Интерпретацию неотрицательного класса можно понять, прочитав его со словом «или» между каждым пунктом, тогда как для инвертированного класса подразумевается «и не». Например, [ANZ] соответствует символу «A или N или Z», а [^ANZ] соответствует символу «не A, не N и не Z». Это означает, что класс с отрицанием можно удобно использовать с типами символов верхнего регистра, чтобы указать более ограниченный набор символов, чем соответствующий тип символов нижнего регистра. Например, класс [^\W_] соответствует любой букве или цифре, но не символу подчеркивания, поскольку он соответствует символу, который не является символом слова (то есть это символ слова), а не символом подчеркивания.

Все небуквенно-цифровые символы, кроме \, -, ^ (в начале) и завершающего ], не являются специальными в классах символов, но их экранирование не навредит.

B.8 Классы символов POSIX

Текущие версии Perl поддерживают нотацию POSIX для классов символов, в которой используются имена, заключенные в квадратные скобки [: и :]. PCRE поддерживает это обозначение. Например:

[01[:alpha:]%]

соответствует 0, 1, любому буквенному символу или %. Поддерживаемые имена классов:

alnum Буквы и цифры
alpha Буквы
ascii Символы с кодами 0-127
cntrl Управляющие символы
digit Десятичные цифры (то же, что и \d)
graph Печатные символы, кроме нижнего пробела
lower Буквы нижнего регистра
print Печатные символы, включая пробелы.
punct Печатные символы, кроме букв и цифр.
space Пробелы (то же, что и \s)
upper Заглавные буквы
word Символы «слова» (то же, что и \w)
xdigit Шестнадцатеричные цифры

Имена ascii и word являются расширениями Perl. Еще одним расширением Perl является отрицание, которое обозначается символом ^ после двоеточия. Например:

[12[:^digit:]]

соответствует 1, 2 или любому другому нецифровому символу. PCRE также распознает синтаксис POSIX [.ch.] и [=ch=], где «ch» — «элемент сортировки», но они не поддерживаются, и при их обнаружении выдается ошибка.

B.9 Вертикальная черта

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

gilbert|sullivan

соответствует либо gilbert, либо sullivan. Может появиться любое количество альтернатив, допускается пустая альтернатива (соответствующая пустой строке). Процесс сопоставления пробует каждую альтернативу по очереди, слева направо, и используется первая удачная альтернатива. Если альтернативы находятся внутри подшаблона (определенного в следующем разделе), «успешно» означает совпадение с остальной частью основного шаблона, а также с альтернативой в подшаблоне.

B.10 Подшаблоны

Подшаблоны разделяются круглыми скобками, которые могут быть вложенными. Пометка части шаблона как подшаблона делает две вещи:

Например, если строка the red king сопоставляется со следующим шаблоном:

the ((red|white) (king|queen) )

захваченные подстроки — это red king, red и king, и они пронумерованы 1, 2 и 3 соответственно.

Тот факт, что простые круглые скобки выполняют две функции, не всегда полезен. Часто бывают случаи, когда подшаблон группировки требуется без требования захвата. Если за открывающей скобкой следует ?:, подшаблон не выполняет никакого захвата и не учитывается при вычислении количества любых последующих захватываемых подшаблонов. Например, если строка «the white queen» сопоставляется с шаблоном:

the ((?:red|white) (king|queen))

захваченные подстроки — это white queen и queen, и пронумерованы 1 и 2. Максимальное количество захватываемых подшаблонов — 65 535; количество незахватывающих подшаблонов не ограничено, но максимальная глубина вложенности всех видов заключенных в скобки подшаблонов, включая захватывающие подшаблоны, утверждения и другие типы подшаблонов, равна 200.

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

(?i:saturday|sunday)
(?:(?i)saturday|sunday)

соответствуют точно такому же набору строк. Поскольку альтернативные ветви пробуются слева направо, а параметры не сбрасываются до тех пор, пока не будет достигнут конец подшаблона, установка параметра в одной ветви влияет на последующие ветви, поэтому эти шаблоны соответствуют SUNDAY и Saturday, а также любым другим вариантам регистра.

B.11 Повторение

Повторение определяется кванторами, которые могут следовать за любым из следующих элементов:

Общий квантификатор повторения указывает минимальное и максимальное количество разрешенных совпадений, давая два числа в фигурных скобках, разделенных запятой. Числа должны быть меньше 65 536, а первое должно быть меньше или равно второму. Например:

z{2,4}

соответствует zz, zzz или zzzz. Закрывающая фигурная скобка сама по себе не является специальным символом. Если второе число опущено, а запятая стоит, верхнего предела нет; если второе число и запятая опущены, квантификатор указывает точное количество требуемых совпадений. Таким образом:

[aeiou]{3,}

соответствует как минимум трем последовательным гласным, но может соответствовать большему их количеству, тогда как:

\d{8}

соответствует ровно восьми цифрам. Открывающая фигурная скобка, которая появляется в позиции, где квантификатор не разрешен, или та, которая не соответствует синтаксису квантификатора, воспринимается как литерал. Например, {,6} — это не квантификатор, а буквенная строка из четырех символов.

Разрешен квантификатор {0}, в результате чего выражение ведет себя так, как если бы предыдущий элемент и квантификатор отсутствовали.

Для удобства (и исторической совместимости) три наиболее распространенных квантификатора имеют односимвольные сокращения:

* Эквивалентно {0,}
+ Эквивалентно {1,}
? Эквивалент {0,1}

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

(a?)*

Ранние версии Perl и PCRE для таких шаблонов выдавали ошибку во время компиляции. Однако, поскольку есть случаи, когда это может быть полезно, такие шаблоны теперь принимаются, но если какое-либо повторение подшаблона фактически не соответствует ни одному символу, цикл принудительно прерывается.

По умолчанию квантификаторы являются «жадными» (greedy). Они совпадают настолько, насколько это возможно, до максимально допустимого количества раз, не вызывая сбоя остальной части шаблона. Классический пример того, где это приводит к проблемам, — это попытка сопоставить комментарии в программах на C. Они появляются между последовательностями /* и */, а внутри комментария могут появляться отдельные символы * и /. Попытка сопоставить комментарии C, применяя следующий шаблон:

/\*.*\*/

на следующую строку:

/* first comment */ not comment /* second comment */

терпит неудачу, потому что она соответствует всей строке из-за жадности элемента .*. Однако, если за квантификатором следует вопросительный знак, он перестает быть жадным и вместо этого соответствует минимально возможному количеству раз, поэтому шаблон:

/\*.*?\*/

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

\d??\d

который предпочтительно соответствует одной цифре, но может соответствовать двум, если это единственный способ сопоставления остальной части шаблона.

Если установлена опция (?U) (опция, недоступная в Perl), квантификаторы не являются жадными по умолчанию, но отдельные квантификаторы можно сделать жадными, поставив после них вопросительный знак. Другими словами, он инвертирует поведение по умолчанию.

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

Если шаблон начинается с .* (или .{0,}) и установлена опция (?s), что позволяет точке совпадать с новой строкой, шаблон неявно привязывается, потому что все, что последует, будет проверяться на каждой позиции символа в строке. Сначала элемент .* использует всю строку, но если остальная часть шаблона не совпадает, он «отдает» символы один за другим до тех пор, пока либо весь шаблон не совпадет, либо не будет достигнуто начало строки (когда .* не соответствует ни одному символу). Если .*? используется вместо .*, происходит то же самое, но в обратном порядке.

Следовательно, для таких шаблонов нет смысла повторять полное совпадение в любой позиции после первой. PCRE обрабатывает такой шаблон так, как если бы ему предшествовал \A. В тех случаях, когда известно, что строка не содержит новых строк, стоит установить (?s), когда шаблон начинается с .*, чтобы получить эту оптимизацию, или альтернативно использовать ^ для явного указания привязки.

Когда подшаблон захвата повторяется, захваченное значение является подстрокой, которая соответствует последней итерации. Например, после следующего:

(tweedie[dume]{3}\s*)+

соответствует tweedledum tweedledee, значение захваченной подстроки равно tweedledee. Однако при наличии вложенных подшаблонов захвата соответствующие захваченные значения могли быть установлены в предыдущих итерациях, и они сохраняют последние установленные значения. Например, следующему:

/(a|(b))+/

соответствует aba, значение второй захваченной подстроки равно b.

B.12 Обратные ссылки

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

Однако, если десятичное число после обратной косой черты меньше 10, оно всегда воспринимается как обратная ссылка и вызывает ошибку только в том случае, если во всем шаблоне не так много захватывающих левых круглых скобок. Другими словами, круглые скобки, на которые делается ссылка, не обязательно должны находиться слева от ссылки для чисел меньше 10. Общая обработка цифр, следующих за обратной косой чертой, обсуждалась ранее (B.3).

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

(sens|respons)e and \libility

соответствует sense and sensibility и response and responsibility, но не sense and responsibility. Если на момент обратной ссылки действует сопоставление по регистру, то регистр букв имеет значение. Например:

((?i)rah)\s+\1

соответствует rah rah и RAH RAH, но не RAH rah, даже если исходный подшаблон захвата сопоставляется без учета регистра.

Может быть более одной обратной ссылки на один и тот же подшаблон. Если подшаблон фактически не использовался в конкретном совпадении, любые обратные ссылки на него всегда терпят неудачу. Например, шаблон:

(a|(bc))\2

всегда терпит неудачу, если он начинает соответствовать a, а не bc.

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

Обратная ссылка, расположенная внутри круглых скобок, на которые она ссылается, завершается ошибкой при первом использовании подшаблона, поэтому, например, (a\1) никогда не совпадает. Однако такие ссылки могут быть полезны внутри повторяющихся подшаблонов. Например, шаблон:

(a|b\1)+

соответствует любому количеству as, а также aba, ababbaa и так далее. На каждой итерации подшаблона обратная ссылка соответствует строке символов, соответствующей предыдущей итерации. Чтобы это работало, шаблон должен быть таким, чтобы первая итерация не совпадала с обратной ссылкой. Это можно сделать с помощью чередования, как в этом примере, или с помощью квантификатора с минимумом, равным нулю.

B.13 Утверждения

Утверждение — это проверка символов, следующих за текущей точкой совпадения или предшествующих ей, которая на самом деле не использует никаких символов. Простые утверждения, закодированные как \b, \B, \A, \Z, \z, ^ и $, описаны в разделе B.3. Более сложные утверждения кодируются как подшаблоны. Есть два типа: те, которые смотрят перед текущей позицией в строке, и те, которые смотрят за ней.

Подшаблон утверждения сопоставляется обычным способом, за исключением того, что он не приводит к изменению текущей позиции сопоставления. Предварительные утверждения начинаются с (?= для положительных утверждений и (?! для отрицательных утверждений. Например:

\w+(?=;)

соответствует слову, за которым следует точка с запятой, но не включает точку с запятой в соответствие, и следующее:

foo(?!bar)

соответствует любому вхождению foo, за которым не следует bar. Обратите внимание, что внешне похожая картина:

(?!foo)bar

не находит вхождения bar, которому предшествует что-то кроме foo; он находит любое появление bar, потому что утверждение (?!foo) всегда верно, когда следующие три символа являются bar. Утверждение просмотра назад необходимо для достижения другого эффекта.

Утверждения просмотра назад начинаются с (?<= для положительных утверждений и (?<! для отрицательных утверждений. Например:

(?<!foo)bar

находит вхождение bar, которому не предшествует foo. Содержимое утверждения просмотра назад ограничено таким образом, что все строки, которым оно соответствует, должны иметь фиксированную длину. Единственным допустимым повторением является квантификатор с фиксированным значением (например, a{4}); неограниченное количество повторов запрещено. Однако, если в утверждении ретроспективного просмотра есть несколько альтернатив, не обязательно, чтобы все они имели одинаковую фиксированную длину. Таким образом:

(?<=bullock|donkey)

разрешено, но:

(?<!dogs?|cats?)

вызывает ошибку во время компиляции. Ветви, которые соответствуют строкам разной длины, разрешены только на верхнем уровне ретроспективного утверждения. Это расширение по сравнению с Perl 5.6, которое требует, чтобы все ветки соответствовали одной и той же длине строки. Такое утверждение, как:

(?<=ab(c|de))

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

(?<=abc|abde)

Точно так же предыдущий пример можно переписать так:

(?<!dog|cat|dogs|cats)

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

Несколько утверждений (любого рода) могут возникать последовательно. Например:

(?<=\d{3})(?<!999)foo

соответствует foo, которому предшествуют три цифры, отличные от 999. Обратите внимание, что каждое из утверждений применяется независимо в одной и той же точке строки. Сначала идет проверка на то, что все три предыдущих символа являются цифрами, затем идет проверка на то, что те же три символа не являются 999. Этот шаблон не соответствует foo, которому предшествуют шесть символов, первые из которых цифры, а последние три из них не 999. Например, не соответствует 123abcfoo. Шаблон для этого выглядит следующим образом:

(?<=\d{3}...)(?<!999)foo

На этот раз первое утверждение просматривает предыдущие шесть символов, проверяя, являются ли первые три цифрами. Затем второе утверждение проверяет, что предыдущие три символа не равны 999.

Утверждения могут быть вложены в любую комбинацию. Например:

(?<=(?<!foo)bar)baz

соответствует вхождению baz, которому предшествует bar, которому, в свою очередь, не предшествует foo, а следующее:

(?<=\d{3}(?!1999)...)f00

— это еще один шаблон, который соответствует foo, которому предшествуют три цифры и любые три символа, кроме 999.

Подшаблоны утверждений не захватывают подшаблоны и не могут повторяться, потому что нет смысла утверждать одно и то же несколько раз. Если какое-либо утверждение содержит в себе подшаблоны захвата, они учитываются при нумерации подшаблонов захвата во всем шаблоне. Однако захват подстроки выполняется только для положительных утверждений, поскольку для отрицательных утверждений он не имеет смысла.

Утверждения учитываются максимум из 200 вложенных подшаблонов в скобках.

B.14 Однократные подшаблоны

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

Рассмотрим, например, шаблон \d+foo, примененный к следующей строке:

123456bar

После сопоставления всех шести цифр, а затем неудачного сопоставления foo, обычное действие сопоставителя состоит в том, чтобы повторить попытку только с пятью цифрами, соответствующими элементу \d+, а затем с четырьмя и так далее, прежде чем в конечном итоге произойдет сбой. Однократные подшаблоны предоставляют средства для указания того, что после того, как часть шаблона совпадет, она не будет переоцениваться таким образом, поэтому сопоставитель немедленно сдастся, если не сможет сопоставить foo в первый раз. Обозначение представляет собой еще один вид специальных скобок, начинающихся с (?>, как в этом примере:

(?>\d+)bar

Скобки такого типа «запирают» часть шаблона, которую они содержат, после того, как они совпадут, и ошибка, находящаяся дальше в шаблоне, не позволит вернуться к нему. Однако возврат к предыдущим элементам работает как обычно.

Альтернативное описание заключается в том, что подшаблон этого типа соответствует строке символов, которой соответствовал бы идентичный автономный шаблон, если бы он был привязан к текущей точке строки объекта.

Однократные подшаблоны не захватывают подшаблоны. Простые случаи, такие как предыдущий пример, можно рассматривать как максимизирующее повторение, которое должно проглотить все, что может. Итак, в то время как оба \d+ и \d+? готовы скорректировать количество цифр, которым они соответствуют, чтобы привести в соответствие остальную часть шаблона, (?>\d+) может соответствовать только целой последовательности цифр.

Эта конструкция, конечно, может содержать сколь угодно сложные подшаблоны и может быть вложенной.

Одноразовые подшаблоны можно использовать в сочетании с утверждениями просмотра назад, чтобы указать эффективное сопоставление в конце строки. Рассмотрим простой шаблон, такой как следующий:

xyz$

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

^.*xyz$

Исходный .* сначала соответствует всей строке, но когда это не удается (из-за отсутствия следующего за ним символа x), выполняется возврат для соответствия всем символам, кроме последнего, затем всем, кроме двух последних символов, и так далее. Опять же, поиск x охватывает всю строку справа налево, так что ситуация не лучше. Однако, если шаблон записывается как:

^(?>.*)(?<=xyz)

для элемента .* не может быть возврата; он может соответствовать только всей строке. Последующее утверждение поиска назад выполняет единственную проверку последних трех символов. Если он терпит неудачу, совпадение немедленно терпит неудачу. Для длинных строк этот подход существенно влияет на время обработки.

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

(\D+|<\d+>)*[!?]

соответствует неограниченному количеству подстрок, состоящих либо из нецифр, либо из цифр, заключенных в <>, за которыми следует ! или ?. Когда он совпадает, он работает быстро. Однако, если он применяется к следующему:

aaaaaaaaaaaaaaaaaaaaaaaadaaaaaaaaaaaaaaaaaaaaaaaaaaa

требуется много времени, прежде чем сообщить о сбое. Это связано с тем, что строка может быть разделена между двумя повторами большим количеством способов, и все они должны быть опробованы[3]. Если шаблон изменить на следующий:

((?>\D+)|<\d+>)*[!?]

последовательности нецифр не могут быть нарушены, и сбой происходит быстро.

  1. В примере используется [!?], а не один символ в конце, потому что и PCRE, и Perl имеют оптимизацию, допускающую быстрый сбой при использовании одного символа. Они запоминают последний одиночный символ, необходимый для совпадения, и преждевременно терпят неудачу, если его нет в строке.

B.15 Условные подшаблоны

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

(?(condition)<yes-pattern>)
(?(condition)<yes-pattern>|<no-pattern>)

Если условие выполняется, используется yes-pattern; в противном случае используется шаблон отсутствия (если он присутствует). Если в подшаблоне имеется более двух альтернатив, возникает ошибка времени компиляции.

Есть два вида состояния. Если текст в круглых скобках состоит из последовательности цифр, условие выполняется, если захватывающий подшаблон этого числа ранее совпадал. Рассмотрим следующий шаблон, который содержит незначащие пробелы, чтобы сделать его более читабельным (предположим, включена опция (?x)) и разделен на три части для простоты обсуждения:

( \( )?    [^()]+    (?(1) \) )

Первая часть соответствует необязательной открывающей скобке и, если этот символ присутствует, устанавливает ее как первую захваченную подстроку. Вторая часть соответствует одному или нескольким символам, не являющимся скобками. Третья часть — это условный подшаблон, проверяющий, совпал ли первый набор скобок или нет. Если это так (то есть, если субъект начинается с открывающей скобки), условие истинно, и поэтому выполняется yes-pattern и требуется закрывающая скобка. В противном случае, поскольку нет шаблона, подшаблон ничему не соответствует. Другими словами, этот шаблон соответствует последовательности символов без круглых скобок, необязательно заключенных в круглые скобки.

Если условие не является последовательностью цифр, оно должно быть утверждением. Это может быть утверждение положительного или отрицательного просмотра вперед или назад. Рассмотрим этот шаблон, снова содержащий незначащие пробелы:

(?(?=[^a-z]*[a-z])   \d{2}-[a-z]{3}-\d{2}   |   \d{2}-\d{2}-\d{2} )

Условие представляет собой положительное опережающее утверждение, которое соответствует необязательной последовательности не букв, за которыми следует буква. Другими словами, оно проверяет наличие хотя бы одной буквы в теме. Если буква найдена, шаблон сопоставляется с первой альтернативой; в противном случае он сопоставляется со второй. Этот шаблон соответствует строкам в одной из двух форм <dd-aaa-dd> or <dd-dd-dd>, где <aaa> — буквы, а <dd> — цифры.

B.16 Комментарии

Последовательность (?# отмечает начало комментария, который продолжается до следующей закрывающей круглой скобки. Вложенные круглые скобки не допускаются. Символы, составляющие комментарий, не играют никакой роли в сопоставлении с образцом.

Если установлена опция (?x), то символ # с umescape-экранированием вне класса символов вводит комментарий, который продолжается до следующего символа новой строки в шаблоне.

B.17 Рекурсивные шаблоны

Рассмотрим проблему сопоставления строки в круглых скобках, допускающую неограниченное количество вложенных круглых скобок. Лучшее, что можно сделать без использования рекурсии, — это использовать шаблон, который соответствует некоторой фиксированной глубине вложенности. Невозможно обрабатывать произвольную глубину вложенности. Perl предоставляет средство, позволяющее рекурсивно выполнять регулярные выражения (среди прочего). Он делает это путем интерполяции кода Perl в выражение во время выполнения, и код может ссылаться на само выражение. Шаблон Perl для решения проблемы со скобками можно создать следующим образом:

$re = gr{\( (?: (?>[^()]+) | (?p{$re}) )* \) }x;

Элемент (?p{...}) интерполирует код Perl во время выполнения и в данном случае рекурсивно ссылается на шаблон, в котором он появляется. Очевидно, что PCRE не может поддерживать интерполяцию кода Perl. Вместо этого для конкретного случая рекурсии предусмотрен специальный элемент (?R). Следующий шаблон PCRE решает проблему со скобками (предположим, что параметр (?x) установлен таким образом, что пробелы игнорируются):

\( ( (?>[^()]+) | (?R) )* \)

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

Этот конкретный образец шаблона содержит вложенные неограниченные повторы, поэтому использование одноразового подшаблона для сопоставления строк без скобок важно при применении шаблона к строкам, которые не совпадают. Например, когда он применяется к следующему:

(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa()

это быстро дает «нет совпадения». Однако, если однократный подшаблон не используется, совпадение выполняется в течение очень долгого времени, потому что существует так много разных способов, которыми повторы + и * могут разделить объект, и все они должны быть проверены, прежде чем можно будет сообщить об ошибке.

Значения, установленные для любых захватываемых подшаблонов, относятся к самому внешнему уровню рекурсии, на котором установлено значение подшаблона. Если предыдущий шаблон сопоставляется со следующим:

(ab(cd)ef)

значение для скобок захвата равно ef, которое является последним значением, принимаемым на верхнем уровне. Если добавляются дополнительные круглые скобки, давая следующее:

\( ( ( (?>[^()]+) | (?R) )* ) \)

строка, которую они захватывают, — это ab(cd)ef, содержимое круглых скобок верхнего уровня. Если в шаблоне более 15 скобок захвата, PCRE должен получить дополнительную память для хранения данных во время рекурсии. Если память не может быть получена, он сохраняет данные только для первых 15 захватываемых скобок, поскольку нет возможности выдать ошибку нехватки памяти из рекурсии.

B.18 Производительность

Некоторые элементы, которые могут появляться в шаблонах, более эффективны, чем другие. Более эффективно использовать класс символов, такой как [aeiou], чем набор альтернатив, таких как (al|e|il|o|u). В общем, самая простая конструкция, обеспечивающая требуемое поведение, обычно является наиболее эффективной.

Когда шаблон начинается с .* и установлен параметр (?s), шаблон неявно привязывается PCRE, поскольку он может совпадать только в начале строки. Однако, если (?s) не установлен, PCRE не может выполнить эту оптимизацию, поскольку метасимвол . не соответствует символу новой строки, и если строка темы содержит символы новой строки, шаблон может совпадать с символом, следующим сразу за одним из них, а не с самого начала. Например, шаблон:

(.*) second

соответствует строке first\nand second (где \n обозначает символ новой строки) с первой захваченной подстрокой, равной and. Чтобы сделать это, PCRE, возможно, придется несколько раз пытаться найти соответствие, начиная после каждой новой строки в теме, а также в начале.

Если вы используете такой шаблон со строками темы, которые не содержат символы новой строки, наилучшая производительность достигается путем установки (?s) или запуска шаблона с ^.* для указания явной привязки. Это избавляет PCRE от необходимости сканировать тему в поисках новой строки для перезапуска.

Остерегайтесь шаблонов, содержащих вложенные бесконечные повторы. Это может занять много времени при применении к строке, которая не соответствует. Рассмотрим следующий фрагмент шаблона:

(а+)*

Это может соответствовать aaaa 33 различными способами[4], и это число очень быстро увеличивается по мере того, как строка становится длиннее. Когда оставшаяся часть шаблона такова, что все сопоставление обречено на провал, PCRE, в принципе, должен попробовать все возможные варианты, и это может занять очень много времени.

Оптимизация улавливает некоторые из более простых случаев, таких как:

(x+)*y

где следует буквальный символ. Перед тем, как приступить к стандартной процедуре сопоставления, PCRE проверяет, есть ли более поздний в строке темы, и если это не так, он немедленно отказывает в сопоставлении. Однако, когда нет следующего литерала, эту оптимизацию использовать нельзя. Вы можете увидеть разницу, сравнив поведение:

(x+)*\d

с предыдущим шаблоном. Первый дает сбой почти мгновенно, когда применяется к целой строке из символов x, тогда как второй занимает значительное время со строками длиннее примерно 20 символов.

  1. Повтор * может соответствовать 0, 1, 2, 3 или 4 раза, и для каждого из этих случаев, кроме 0, повтор + может соответствовать различному количеству раз.

Об авторе

Филип Хейзел имеет докторскую степень по прикладной математике, но последние 30 лет занимался написанием программного обеспечения общего назначения для вычислительной службы Кембриджского университета в Англии. После перехода с мейнфрейма IBM на Unix в начале 1990-х Филип стал все больше и больше заниматься электронной почтой. Он начал разрабатывать Exim в 1995 году и PCRE (библиотеку регулярных выражений) в 1997 году. С тех пор большую часть своего рабочего времени он посвящал поддержке и расширению Exim.

Колофон

Эта книга была создана на рабочей станции Sun, работающей под управлением Solaris, в основном с использованием прикладного программного обеспечения, написанного Филипом Хейзелом. Исходный код был написан с использованием текстового редактора NE для создания размеченного файла для обработки программой форматирования текста SGCAL. Рисунки были созданы на языке рисования Aspic, интегрированном с SGCAL. Основной вывод SGCAL был преобразован в PostScript, после чего были включены скриншоты EPSF. Вспомогательный вывод для указателя и оглавления обрабатывался сценариями Perl, чтобы сделать дальнейший ввод, который был повторно обработан SGCAL. Наконец, сценарий Perl объединил отдельные файлы PostScript вместе, чтобы сформировать полную книгу.

Основной текст книги набран шрифтом Times. Заголовки набраны шрифтом Helvetica; в примерах используется Courier.

Спонсоры

Выпуск этой книги был спонсирован следующими организациями:

SWITCH — Швейцарская образовательная и исследовательская сеть
Linpro AS, Норвегия
Multithread Consultants Ltd, Великобритания
Nova Web Hosting Ltd, Великобритания

Автор и издатель благодарны сообществу Exim за эту поддержку.