9. Поиски в файлах и базах данных
Перевод выполнен Алексеем Паутовым в рамках некоммерческого проекта RussianLDP (http://www.rldp.ru/). Именно на этом сайте и надлежит искать новые версии, если таковые будут.
9. Поиски в файлах и базах данных
Exim может быть сконфигурирован для поиска данных в файлах или базах данных, когда он обрабатывает сообщение. Могут использоваться два различных синтаксиса:
- 1. Строка, которая будет раскрыта, может содержать явный запрос поиска. По этой причине часть строки будет заменена данными, найденными поиском. Поиски этого типа: условия в элементах раскрытия. Различные результаты могут быть заданы для случаев успешного и неудачного поисков. Смотрите раздел 11, где в деталях описано раскрытие строк.
- 2. Списки доменов, хостов и адресов e-mail могут содержать запрос поиска, как способ избежать слишком длинного линейного списка. В этом случае данные возвращённые запросом, обычно (но не всегда) отбрасываются, но реально засчитывается, успешен ли поиск или нет. Эта разновидность списков описана в разделе 10.
Раскрытие строк, списков и поисков взаимодействуют друг с другом в каждом случае, нет порядка, в котором описан любой из них и нет ссылок к другим. Каждая из этих трёх частей даст намного больше, если Вы прочтёте вначале две другие. Если Вы читаете эту часть первой, то знайте, что поймёте больше, после прочтения глав 10 и 11.
9.1. Примеры различных синтаксисов поиска
Очень легко перепутать два разных способа поиска, тем более, что списки, которые могут содержать вторую разновидность, всегда раскрываются, прежде чем быть обработанными как списки. Поэтому они также могут содержать поиски первого вида. Будьте точны в различии между следующими двумя примерами:
domains = ${lookup{ $sender_host_address}lsearch{/some/file}} domains = lsearch;/some/file |
Первый использует раскрытие строки, результат которого должен быть списком доменов. Строки для успешного или безуспешного поиска не заданы, значения по умолчанию в этом случае: найденные данные и пустая строка, соответственно. Раскрытие помещается прежде, чем строка обрабатывается как список, и файл, по которому ведётся поиск, может содержать строки как эти:
192.168.3.4: domain1:domain2:... 192.168.1.9: domain3:domain4:... |
Когда поиск успешен, результат раскрытия: список доменов (и, возможно, другие типы элементов, разрешённые в списке доменов). Во втором примере, ищется один элемент в списке доменов. Это вынуждает exim к использованию поиска для того, чтобы узнать, может ли обрабатываемый домен быть найден в файле. Файл может содержать строки как эти:
domain1: domain2: |
Любые данные, сопровождаемые ключами, неуместны при проверке, что домен совпадает с элементом списка. Их можно спутать при использовании обоих видов поиска сразу. Рассмотрите файл, содержащий, например, такие строки:
192.168.5.6: lsearch;/another/file |
Если значение $sender_host_address 192.168.5.6, то раскрытие первой установки domains генерирует вторую установку, которая вызывает второй поиск.
Оставшаяся часть этой главы описывает различные доступные типы поиска. Любой из них может использоваться в любой части конфигурации, где разрешены поиски.
9.2. Типы поиска
Реализованы два различных типа поиска:
- Одноключевой (single-key) тип поиска, требует задания файла, в котором будет происходить поиск, и одного ключа для поиска. Ключ должен быть непустой строкой, чтобы поиск был успешен. Тип поиска определяет, как найден файл.
- Поиск в стиле запроса (query-style) принимает обобщённый запрос базы данных. Exim не предполагает никакого специфического ключевого значения для поисков в стиле запроса. Вы можете использовать любые переменные exim для необходимого Вам запроса к БД.
Код для каждого типа поиска находится в отдельном файле исходных текстов и включается в исполняемый модуль exim лишь, если при компиляции установлена соответствующая опция. Настройки по умолчанию в src/EDITME таковы:
LOOKUP_DBM=yes LOOKUP_LSEARCH=yes |
9.3. Одноключевые типы поиска
Реализованы следующие одноключевые типы поиска:
- cdb: По данному файлу производится поиск как по файлу
статической базы данных (Constant DataBase), используя ключевую строку без
завершающего двоичного нуля. Формат cdb спроектирован для индексных файлов,
которые часто читаются и никогда не обновляются, исключая полное
пересоздание. Также он является наиболее подходящим для больших файлов,
содержащих алиасы, или другие индексированные данные, на которые ссылается
MTA. Информация о cdb может быть найдена в нескольких местах:
http://www.pobox.com/~djb/cdb.html,
ftp://ftp.corpit.ru/pub/tinycdb/,
http://packages.debian.org/stable/utils/freecdb.html.
Дистрибутив cdb не нужен для сборки exim с поддержкой cdb, поскольку код для чтения cdb-файлов непосредственно включён в exim. Однако, с exim не предоставляется никаких средств для сборки или тестирования cdb-файлов, таким образом, Вам необходимо получить дистрибутив cdb для этого. - dbm: Вызовы к библиотечным функциям dbm используются для извлечения данных из файлов DBM, путём поиска записей с данным ключом. Завершающий ноль включён в ключ, который передаётся библиотеке DBM. Смотрите раздел 4.3 для обсуждения библиотек DBM. Для всех версий Berkeley DB exim использует стиль базы данных DB_HASH, когда собирает DBM-файлы, используя утилиту exim_dbmbuild. Однако, когда используется Berkeley DB версий 3 и 4, он открывает для чтения существующие базы данных с опцией DB_UNKNOWN. Это позволяет ему обработать любой из типов БД, поддерживаемых библиотекой, что может быть полезно для доступа к DBM-файлам, созданным другими приложениями. Для более ранних версий DB всегда используется DB_HASH.
- dbmnz: Это тоже самое, что и dbm, за исключением того, что завершающий ноль не включен в ключ, передаваемый библиотеке DBM. Вам может понадобиться использовать это, если Вы хотите искать данные в файлах, которые созданы или разделены с каким-либо иным приложением, которое не использует завершающий ноль. Например, Вы должны использовать dbmnz, а не dbm, если необходимо аутентифицировать входящие SMTP-подключения, используя пароли из файла /etc/userdbshadow.dat Сourier. Утилита exim для создания файлов DBM (exim_dbmbuild) по умолчанию включает нули, но у неё есть опция для их исключения (смотрите раздел 49.9).
- dsearch: Данный файл должен быть каталогом: ищется файл, имя которого равно ключу. Ключ не должен содержать символов слэша. Результат успешного поиска: имя файла. Пример, как этот поиск может использоваться для поддержки виртуальных доменов, дан в разделе 46.7.
- iplsearch: Данный файл текстовый, содержащий ключи и данные.
Ключ завершается двоеточием, пробелом или концом строки. Ключи в файле должны
быть IP-адресами или IP-адресами с CIDR-масками. Ключи, включающие в себя
адреса IPv6, должны быть заключены в кавычки для предотвращения интерпретации
первого внутреннего двоеточия как завершения ключа. Например:
Ключ для iplsearch поиска должен быть IP-адресом (без маски). Поиск по файлу линейный с использованием масок CIDR, где они заданы, до нахождения соответствия ключу. Используется первый совпадающий ключ, дальнейших попыток найти лучшее совпадение не предпринимается. Кроме совпадения ключей, обработка iplsearch такая же, как у lsearch.1.2.3.4: data for 1.2.3.4 192.168.0.0/16 data for 192.168.0.0/16 "abcd::cdab":data for abcd::cdab "abcd:abcd::/32"data for abcd:abcd::/32
Предупреждение 1: В отличие от большинства других одноключевых поисков, файл данных для iplsearch не может быть превращён в DBM или cdb-файл, поскольку эти типы поиска поддерживают только буквальные ключи.
Предупреждение 2: В списке хостов Вы всегда должны использовать net-iplsearch таким образом, чтобы неявный ключ был IP-адресом, а не именем (смотрите раздел 10.12). - lsearch: Данный файл текстовый, по которому линейно ищется строка,
начинающаяся с искомого ключа, законченную двоеточием, пробелом или концом
строки. Используется первое найденное совпадение. Пустое место между ключом и
двоеточием разрешается. Остаток строки, после удаления начального и конечного
пустого пространства, является данными. Они могут быть продолжены на
следующие строки путём начала их с любого количества пустого пространства, но
только один символ пробела включается в данные при таком соединении. Если
данные начинаются с двоеточия, ключ должен быть завершён двоеточием, например:<
Пустые строки и строки, начинающиеся с #, игнорируются, даже если они встречаются в середине строки. Это традиционный текстовый формат файла алиасов. Обратите внимание, что ключи в файле lsearch литеральные строки. Тут нет подстановки какого бы то ни было вида. В большинстве файлов lsearch ключи не могут содержать двоеточия, символы # или пустые пробелы. Однако, если необходима эта возможность, она доступна. Если ключ начинается с символа двойной кавычки, он завершается только соответствующей кавычкой (или концом строки), и к её содержимому применяются обычные правила экранирования (смотрите раздел 6.16). Опционально, двоеточие разрешено после ключа в кавычках (также как и для ключей без кавычек). Специальная обработка кавычек для части данных строки lsearch отсуствует.baduser: :fail:
- nis: Данный файл имя карты NIS, и поиск NIS производится с данным ключом без завершающего двоичного нуля. Есть вариант, называемый nis0, который включает двоичный нуль в ключ. По сведениям, это необходимо для файла алиасов в стиле SUN. Exim не понимает NIS-алиасы: должны использоваться полные имена карт.
- wildlsearch или nwildlsearch: Поиск по файлу линейный, как
lsearch, но вместо того, чтобы интепретировать как литеральную строку,
каждый ключ в файле может быть подстановочным. Различие между этими двумя
типами поиска в том, что для wildlsearch каждый ключ в файле
раскрывается до начала использования, тогда как для nwildlsearch нет
раскрытия на месте. Как и в lsearch, тестирование производится
без учёта регистра. Признаются следующие формы подстановочных знаков:
- 1. Строка может начинаться со звёздочки для обозначения
"кончается на". Например:
*.a.b.c data for anything.a.b.c *fishdata for anythingfish
- 2. Строка может начинаться с циркумфлекса (^) для обозначения регулярного
выражения. Например, для wildlsearch:
Примечание: использование \N отключает раскрытие содержимого регулярного выражения. Если Вы используете nwildlsearch там, где ключи не раскрываются, это эквиалентрно:^\N\d+\.a\.b\N data for <digits>.a.
Если регулярное выражение содержит пустое место или символы двоеточия, Вы должны поместить его в кавычки (смотрите lsearch, выше) или представить эти символы другим образом. Например, \s может быть использовано для обозначения пробела, а \x3A для двоеточия. Это может оказаться легче, чем использовать кавычки, поскольку при использовании кавычек, Вы должны экранировать все обратные слэши внутри кавычек.^\d+\.a\.b data for <digits>.a.b
Примечание: Невозможно зафиксировать подстроки в совпадении регулярного выражения для дальнейшего использования, поскольку результаты всех поисков кэшируются. Если поиск повторяется, результат берётся из кэша, и нет фактического сопоставления с образцом. Значения всех цифровых переменных сбрасываются после совпадения (n)wildlsearch. - 3. Хотя я не вижу много применений, общая функция соответствия,
используемая для реализации (n)wildlsearch означает, что строка
может начинаться с имени поиска, завершаемого двоеточием, и сопровождаться
данными поиска. Например:
Данные, полученные из вложенного поиска, отвергаются. Ключи, которые не соответствуют ни одному из этих паттернов, интерпретируются буквально. Правила продолжения для данных точно такие же, как для lsearch, а ключи могут сопровождаться опциональными двоеточиями.cdb;/some/file data for keys that match the file
Предупреждение: В отличие от большинства других одноключевых поисков, файл данных для (n)wildlsearch не может быть превращён в DBM или cdb-файл, поскольку эти типы поиска поддреживают только буквальное соответствие.
- 1. Строка может начинаться со звёздочки для обозначения
"кончается на". Например:
9.4. Типы поиска в стиле запроса
Поддерживаемые типы поиска в стиле запроса перечислены ниже. Дальнейшие детали о многих из них даны в дальнейших разделах.
- dnsdb: Этот производит поиск одной или более записей, чьи доменные имена даны в предоставленном запросе. Результирующие данные: содержимое записей. Смотрите раздел 9.10.
- ibase: Этот производит поиск по БД Interbase.
- ldap: Этот производит поиск по LDAP, используя запрос в форме URL, и возвращает атрибуты единственного элемента. Есть вариант, вызывающий ldapm, который разрешает возврат значений от нескольких элементов. Третий вариант, называемый ldapdn, возвращает Distinguished Name (отличительное имя) одного элемента вместо любых значений атрибутов. Смотрите раздел 9.13.
- mysql: Формат запроса: SQL-выражение, передаваемое MySQL. Смотрите раздел 9.20.
- nisplus: Этот производит поиск в NIS+, используя запрос, который может задать имя поля для возврата. Смотрите раздел 9.19.
- oracle: Формат запроса: SQL-выражение, передаваемое Oracle. Смотрите раздел 9.20.
- passwd: Поиск с запросами, которые содержат лишь имя пользователя.
Поиск вызывает getpwnam() для запроса данных системного пароля и, при успехе,
строка результата представляет собой то же самое, что Вы получили бы из
поиска lsearch в традиционном файле паролей /etc/passwd file,
со значением * в качестве значения пароля. Например:
*:42:42:King Rat:/home/kr:/bin/bash
- pgsql: Формат запроса SQL-выражение, передаваемое PostgreSQL. Смотрите раздел 9.20.
- sqlite: Формат запроса имя файла, сопровождаемое SQL-выражением, передаваемым SQLite. Смотрите раздел 9.24.
- testdb: Это тип поиска, используемый для тестирования exim. Он вряд ли будет полезен в обычной ситуации.
- whoson: Whoson (http://whoson.sourceforge.net) предложенный протокол
Интернета, который разрешает программам серверов Интернета проверять, какой
из двух специфических (динамически выделенных) IP-адресов в данный момент
выделен извеcтному (доверенному) пользователю, и опционально получить
идентификацию упомянутого пользователя. В exim это может использоваться для
реализации проверки условия ACL POP перед SMTP, например:
Запрос состоит из единственного IP-адреса. Возвращённое значение: имя аутентифицированного пользователя, которое сохранёно в переменной $value. Однако, в этом примере данные $value не используются, результат поиска: одна из фиксированных строк, yes или no.require condition = \ ${lookup whoson {$sender_host_address}{yes}{no}}
9.5. Временные ошибки в поисках
Функции поиска могут вернуть коды временных ошибок, если поиск не может быть завершён. Например, SQL или LDAP могут быть недоступны. Поэтому нежелательно использовать поиск, который мог бы сделать такое для критичных опций, например, списка локальных доменов.
Когда поиск не может быть завершён в роутере или транспорте, доставка сообщения (к релевантному адресу) задерживается, как и для других временных ошибок. При других обстоятельствах exim может предположить, что поиск был неудачен, или может вообще всё бросить.
9.6. Значения по умолчанию в одноключевых поисках
В этом контексте значения по умолчанию представляют собой значения, заданные администратором, которые должны использоваться, если поиск неудачен. Если * добавляется к одноключевому типу поиска (например, к lsearch*) и начальный поиск неудачен, ключ * ищется в файле, для нахождения значения по умолчанию. Также смотрите раздел о частичном соответствии ниже. Альтернативно, если * добавляется к одноключевому типу поиска (например, dbm*@), тогда, если начальный поиск неудачен, а ключ содержит символ @, второй поиск производится заменив все на * до последнего @. Это позволяет предоставить значения по умолчанию на домен, в файлах алиасов, включающих домены в ключи. Если второй поиск неудачен (или его нет, потому что в ключе нет @), ищется *. Например, роутер redirect мог бы содержать:
data = ${lookup{ $local_part@$domain}lsearch*@{/etc/mix-aliases}} |
Предположим, обрабатываемый адрес: jane@eyre.example. Exim ищет эти ключи в таком порядке:
jane@eyre.example *@eyre.example * |
Данные берутся из любого ключа, найденного первым. Примечание: в файле lsearch это не означает первый из этих ключей в файле. Полное сканирование производится для каждого ключа, и лишь если он не найден, exim пробует следующий ключ.
9.7. Частичное совпадение в одноключевых поисках
Нормальная операция одноключевого поиска, поиск в файле точного соответствия заданному ключу. Однако, во множестве ситуаций, в которых ищутся домены, было бы полезным частичное соответствие. В этом случае информация в файле, которая начинается с *., совпадает с любым доменом, заканчивающимся компоненами, следующими за точкой. Например, если ключ в DBM-файле такой:
*.dates.fict.example |
Примечание: частичное соответствие недоступно для поисков в стиле запроса. Также оно недоступно для поиска любых элементов в списках адресов (смотрите раздел 10.18).
Частичное соответствие реализовано путём отдельных поисков с использованием ключей, сконструированных путём модификации оригинального ключа. Это означает, что он может использоваться с любым типом одноключевого поиска, при условии, что частично совпадающие ключи, начинающиеся со специального префикса (по умолчанию *.), включены в файл данных. Ключи в файле, которые не начинаются с префикса, совпадают только с немодифицированными ключами, когда используется частичное соответствие.
Частичное соответствие вызывают путём добавления строки partial к началу имени одноключевого типа поиска, например, partial-dbm. Когда это происходит, вначале ищется немодифицированный объект ключа. Если поиск неудачен, *. добавляется в начале ключа и снова производится поиск. Если он неудачен, будущие поиски пробуют удалять разделённые точками компоненты от начала ключа один за другим и добавляя *. к началу того, что осталось.
Требуемое минимальное число не-* компонентов: два. Это может быть скорректировано включением числа до дефиса в типе поиска. Например, partial3-lsearch задаёт минимум три не-* компонента в измененённых ключах. Отстутствие числа эквивалентно partial2. Если ключ 2250.dates.fict.example, тогда следующие ключи ищутся, когда минимальное число не-* компонентов два:
2250.dates.fict.example *.2250.dates.fict.example *.dates.fict.example *.fict.example |
Как только один ключ в последовательности успешно найден, поиск завершён. Использование *. как префикса может быть изменено. Мотивацией для этой возможности является разрешение exim работать с форматами файлов, используемыми другими MTA. Иной префикс может быть предоставлен в круглых скобках вместо дефиса после partial. Например:
domains = partial(.)lsearch;/some/file |
В этом примере, если домен a.b.c, последовательность поисков a.b.c, .a.b.c и .b.c (при неизменённом минимуме в 2 компонента). Префикс может состоять из любых символов пунктуации, кроме закрывающей круглой скобки. Он может быть пустым, например:
domains = partial1()cdb;/some/file |
Для этого примера, если домен a.b.c, последовательность поиска будет a.b.c, b.c и c. Если задан partial0, что случается в конце зависимостей от префикса (когда поиск с лишь одним неподстановочным компонентом неудачен, и оригинальный ключ укорачиватся вправо на нулевую строку):
Если префикс имеет нулевую длинну, весь поиск неудачен.
Если длина префикса равна 1, поиск производится лишь для префикса. Например, заключительный поиск для partial0(.) является единственным для .
Иначе, если префикс заканчиватся точкой, точка удаляется, а ищется оставшаяся часть. Поэтому с префиксом по умолчанию финальный поиск для * самостоятелен. Иначе, ищется полный префикс.
Если тип поиска заканчивается на * или *@ (смотрите выше, раздел 9.6), поиск окончательного значения, подразумевающего эти последовательности, происходит после неудачи всех поисков. Однако, тут можно использовать поиск типа partial0(.)lsearch*. Использование * в частично соответствующем поиске отличается от использовния как подстановочного символа в списках доменов и тому подобном. Частичное соответствие работает только в виде компонентов разделённых точкой. Ключ, например, *fict.example, бесполезен в БД, поскольку звёздочка в частично совпадающем ключе всегда сопровождается точкой.
9.8. Кэширование поиска
Exim кэширует все результаты поисков для избежания бесполезных повторений поисков. Однако, поскольку (кроме демона) exim работает как коллекция независимых короткоживущих процессов, это кэширование применяется только в пределах одного процесса exim. Средства для межпроцессного кэширования отсутствуют.
Для одноключевого поиска exim оставляет релевантные файлы открытыми в случае, если есть другой поиск, нуждающийся в них. В некоторых типах конфигураций это может привести к большому числу открытых файлов, оставляемых открытыми для сообщений со многими получателями. Для избежания попадений под системные ограничения на число открытых файлов, exim закрывает последний использованный файл, когда необходимо открыть больше файлов, чем позволяют его внутренние ограничения, которое можно изменить через опцию lookup_open_max. Файлы одноключевого поиска закрываются и сбрасывается кэш поиска в стратегических точках доставки, например, после завершения всех роутингов.
9.9. Цитирование (помещение в двойные кавычки) данных поиска
Когда данные из входящего сообщения включаются в поиск типа запросов, возможно появление специальных символов в данных, нарушающих синтаксис запроса. Например, запрос NIS+ содержащий?
[name=$local_part] |
[name="$local_part"] |
${quote_<lookup-type>:<string>} |
Например, самый безопасный способ написания NIS+ запроса:
[name="${quote_nisplus:$local_part}"] |
Смотрите раздел 11 для полного обзора раскрытия строк. Оператор кавычек может использоваться для всех типов поисков, но он не имеет эффекта в одноключевых поисках, так как кавычки в них никогда не бывают необходимы.
9.10. Дополнительные сведения о dnsdb
Тип поиска dnsdb использует DNS как базу данных. Простой запрос содержит тип записи и имя домена, разделённые знаком равно (=). Например, строка раскрытия может содержать:
${lookup dnsdb{mx=a.b.example}{$value}fail} |
Если поиск успешен, результат помещается в $value, которая в этом случае используется как результат. Если поиск успешен, ключевое слово fail вызывает принудительную ошибку раскрытия (forced expansion failure), смотрите раздел 11.4 для понимания, что это означает.
Поддерживаемые типы DNS-записей: A, CNAME, MX, NS, PTR, SRV и TXT, а когда exim скомпилирован с поддержкой IPv6, еще AAAA (и A6, если это тоже сконфигурировано). Если тип не задан, предполагается TXT. Когда тип PTR, данные могут быть нормально записанным IP-адресом, инверсия и добавление in-addr.arpa или ip6.arpa происходят автоматически. Например:
${lookup dnsdb{ptr=192.168.4.5}{$value}fail} |
Если данные для PTR-записи не являются синтаксически допустимым IP-адресом, он не изменяется и ничего не добавляется. Для поиска MX для каждой записи возвращаются оба привелигированных значения и имя хоста, разделённые пробелом. Для поиска SRV приоритет, вес, порт и имя хоста возвращаются для каждой записи, разделённые пробелами.
Для любых типов записей если найдено много записей (или для поиска A6, если одна запись ведёт ко многим адресам), данные возвращаются как объединение с символом новой строки, как разделителем по умолчанию. Порядок, разумеется, определяется DNS-сервером. Вы можете задать иной разделитель символов, между несколькими записями, путём помещения в начале запроса правой угловой скобки, сопровождаемой (без пробелов) новым разделителем. Например, так:
${lookup dnsdb{>: a=host1.example}} |
Разрешается задать пробел как разделитель. Дальнейшее пустое пространство игнорируется.
9.11. Псевдо-dnsdb типы записей
По умолчанию предпочтительное значение и имя хоста
возвращаются для каждой MX-записи, разделённые пробелами. Если нужны только
имена хостов, Вы можете использовать псевдо-тип MXH:
${lookup dnsdb{mxh=a.b.example}} |
В этом случае предпочтительное значение опущено, а возвращаются только имена хостов. Другой псевдотип ZNS (расшифровывается zone NS). Он выполняет поиск NS-записи для данного домена, но если она не найдена, он удаляет первый компонент имени домена и пробует снова. Этот процесс продолжается, пока не найдена NS-запись, или не останется компонентов имени (или произойдёт ошибка DNS). Другими словами, он может вернуть сервер имён домена верхнего уровня, но никогда не вернёт корневой сервер имён. Если нет NS-записей домена верхнего уровня, поиск неудачен. Рассмотрите эти примеры:
${lookup dnsdb{zns=xxx.quercite.com}} ${lookup dnsdb{zns=xxx.edu}} |
Предполагается, что в каждом случае тут нет NS-записей для полного доменного имени, в первом случае сервером имён возвращается значение для quercite.com, во втором случае сервером имён возвращается значение для edu.
Вы должны быть внимательны при использовании этого типа поиска, поскольку, если домен верхнего уровня не существует, поиск всегда вернёт какое-то имя домена. Это могло бы использоваться для того, чтобы видеть, находится ли сервер имён данного домена в чёрном списке. Вероятно, Вы можете предполагать, что серверы имён для доменов верхнего уровня таких, как su или co.uk, не собираются находиться в таких списках.
Третий псевдо-тип CSA (Client SMTP Authorization). Он ищет SRV-записи для правил CSA, которые описаны в разделе 39.37. Хотя dnsdb непосредственно поддерживает поиски SRV, этого недостаточно из-за дополнительного режима поиска родительских доменов CSA. Результат успешного поиска, например:
${lookup dnsdb {csa=$sender_helo_name}} |
9.12. Множественные поиски dnsdb
В предыдущих разделах описаны поиски для одиночного домена. Однако, Вы можете задать список доменов или адресов в отдельном dnsdb-поиске. Список задаётся в нормальном виде exim, с двоеточием в качестве разделителя, но с возможностью изменить его. Например:
${lookup dnsdb{one.domain.com:two.domain.com}} ${lookup dnsdb{a=one.host.com:two.host.com}} ${lookup dnsdb{ptr = <; 1.2.3.4 ; 4.5.6.8}} |
Для сохранения обратной совместимости, есть один специальный случай: если тип поиска PTR и не указано изменение разделителя, exim смотрит, не является ли остаток строки одним IPv6-адресом. В этом случае он не обрабатывает её как список.
Данные каждого поиска объединены с символом новой строки в качестве разделителя, таким образом обрабатываются множественные DNS-записи для одного элемента. Может быть задан иной разделитель, как указано выше.
Поиск dnsdb неудачен, лишь если неудачны все DNS-поиски. Если для любого из них происходит временная ошибка DNS, то поведением управляет опциональное ключевой слово с последующей запятой, могущей появиться перед типом записи. Возможные ключевые слова: defer_strict, defer_never и defer_lax. С strict-поведением любая временная ошибка DNS вызывает задержку всего поиска. С never-поведением, временные ошибки DNS игнорируются, а поведение такое, будто поиск в DNS не привёл ни к чему. С lax-поведением, предпринимаются все запросы, но временые ошибки DNS вызывают задержку лишь в случае, если остальные поиски были безуспешны. По умолчанию принято lax, таким образом, следующие поиски эквивалентны:
${lookup dnsdb{defer_lax,a=one.host.com:two.host.com}} ${lookup dnsdb{a=one.host.com:two.host.com}} |
Следовательно, в случае по умолчанию поиск успешен до тех пор, пока хоть один поиск в DNS привёл к каким-то данным.
9.13. Дополнительные сведения о LDAP
Оригинальная реализация LDAP была сделана в University of Michigan, она стала OpenLDAP, и сейчас существует два различных релиза. Другая реализация происходит из Netscape, Solaris 7 и последующие релизы содержат встроенную поддержку LDAP. К сожалению, хотя все они совместимы на уровне функционирования запросов, обработка их ошибок различна. По этой причине необходимо установить переменную во время компиляции exim с LDAP для указания, какая библиотека LDAP используется. Одна из следующих строк должна быть в Вашем Local/Makefile:
LDAP_LIB_TYPE=UMICHIGAN LDAP_LIB_TYPE=OPENLDAP1 LDAP_LIB_TYPE=OPENLDAP2 LDAP_LIB_TYPE=NETSCAPE LDAP_LIB_TYPE=SOLARIS |
Если LDAP_LIB_TYPE не задана, exim предполагает OPENLDAP1, имеющий такой же интерфейс, как и версия University of Michigan. Есть три типа поиска LDAP в exim. Они ведут себя по-разному, когда обрабатывают результаты запроса:
- ldap требует, чтобы результат содержал только один элемент. Если их больше, он выдаёт ошибку.
- ldapdn также требует, чтобы результат содержал только один элемент, но запросом должно быть возвращено Distinguished Name, а не любые атрибуты со значением.
ldapm разрешает результату содержать более одного элемента, все их атрибуты возвращаются запросом. Для ldap и ldapm, если запрос находит лишь входы без атрибутов, exim ведёт себя, как будто вхождения не найдены, и поиск неудачен. Формат данных, возвращаемых успешным поиском, описан в следующей секции. Сначала мы объясняем, как кодируются LDAP-запросы.
9.14. Формат запросов LDAP
Запрос к LDAP имеет форму URL, как определено в RFC 2255. Например, в конфигурации роутера redirect могла бы быть такая установка:
data = ${lookup ldap \ {ldap:///cn=$local_part,o=University%20of%20Cambridge,\ c=UK?mailbox?base?}} |
URL может начинаться с ldap или ldaps, если Ваша библиотека LDAP поддерживает безопасные (шифрованные) LDAP-соединения. Второй из них гарантирует, что используются шифрованные подключения TLS.
9.15. Квотирование (использование двойных кавычек и спецсимволов) в LDAP
В запросах LDAP требуются два уровня квотирования: первый непосредвственно для LDAP и второй, поскольку запрос LDAP представлен как URL. Кроме того, внутри LDAP-запроса, требуются два различных вида квотирования. Поэтому есть два различных, LDAP-специфичных, оператора квотирования.
Оператор quote_ldap спроектирован для использования на строках, являющихся частью спецификации фильтра. Концептуально, он вначале производит следующие преобразования строки:
*=>\2A (=>\28 )=>\29 \=>\5C |
! $ ' - . _ ( ) * + |
${quote_ldap: a(bc)*, a<yz>;} |
%20a%5C28bc%5C29%5C2A%2C%20a%3Cyz%3E%3B%20 |
Удалив квотирование URL, это (с начальным и конечным пустым пространством):
a\28bc\29\2A, a<yz>; |
Оператор quote_ldap_dn спроектирован для использования на строках, являющихся частью базовых спецификаций DN, в запросех. Концептуально, вначале он конвертирует строку, вставляя обратный слэш перед любым из следующих символов:
, + " \ < > ; |
Он также вставляет обратный слэш перед любыми пробелами или символом # и перед конечными пробелами. Правила находятся в RFC 2253. Тогда результирующая строка квотирована согласно правилам для URL. Например:
${quote_ldap_dn: a(bc)*, a<yz>; } %5C%20a(bc)*%5C%2C%20a%5C%3Cyz%5C%3E%5C%3B%5C%20 |
Удалив квотирование URL, получится (с конечными пробелами):
\ a(bc)*\, a\<yz\>\;\ |
Есть некоторые дальнейшие комментарии о цитировании в секции об аутентификации LDAP ниже.
9.16. Соединения LDAP
Подключение к серверу LDAP может быть через TCP/IP, или, когда используется OpenLDAP, через сокет UNIX. Пример, данный выше, не определяет сервер LDAP. Сервер, который доступен по TCP/IP, может быть задан в запросе, запуская его так:
ldap://<hostname>:<port>/... |
Если порт (и предыдущее двоеточие) опущены, используется стандартный порт LDAP (389). Если в запросе не указан сервер, список серверов берётся из конфигурационной опции ldap_default_servers. Она представляет список серверов, разделённых двоеточиями, пробуемых по очереди, пока запрос не будет успешно обработан, или не произойдёт серьёзная ошибка. Успешная обработка вернёт запрошенные данные или укажет, что они не существуют (ага, успешно обработали запрос, круто, успешней некуда...). Серьёзные ошибки: синтаксические или много значений, когда ожидается только одно. Ошибки, приводящие к пробе следующего сервера: сбои подключения, привязки и таймауты.
Для каждого имени сервера в списке можно задать номер порта. Стандартный способ задания хоста и порта использование двоеточия, как разделителя (RFC 1738). Поскольку ldap_default_servers представляет собой список значений, разделённых двоеточиями, такие двоеточия должны быть удвоены. Например:
ldap_default_servers = ldap1.example.com::145:ldap2.example.com |
Если ldap_default_servers не задана, библиотеке LDAP передаётся URL без имени сервера и используется значение по умолчанию библиотеки (обычно локальный компьютер).
Если Вы используете библиотеку OpenLDAP, можете соединится с LDAP-сервером, используя сокет UNIX, вместо подключения через TCP/IP. Это задаётся использованием ldapi вместо ldap в LDAP-запросах. Нижеследующее применяется только в OpenLDAP. Если exim скомпилирован с поддержкой различных LDAP-библиотек, эта возможность недоступна.
Для этого типа соединения, вместо имени хоста требуется имя-путь сокета, номер порта неуместен. Имя-путь может быть указан как элемент в ldap_default_servers или встроено в запрос. В первом случае Вы будете иметь настройки типа таких:
ldap_default_servers = /tmp/ldap.sock : backup.ldap.your.domain |
Когда путь с именем указываются в запросе, Вы должны заменить прямые слэши последовательностью %2F для соблюдения синтаксиса LDAP URL. Например:
${lookup ldap {ldapi://%2Ftmp%2Fldap.sock/o=... |
Когда exim производит поиск LDAP и находит, что имя хоста (hostname) реальный путь к сокету, он использует код сокета UNIX, даже если запрос задаёт использование ldap или ldaps. В частности, для соединения с сокетом не используется шифрование. Это поведение означает, что Вы можете использовать настройки, например, ldap_default_servers, в примере выше с традиционными ldap или ldaps, и это будет работать. Вначале, exim пробует соединиться через через сокет UNIX; если это не удаётся, он пробует подключиться по TCP/IP к резервному хосту.
Если в запросе задаётся явный тип ldapi, при указанном имени хоста, диагностируется ошибка. Однако, если есть другие элементы в ldap_default_servers, пробуются они. Другими словами:
- Использование пути к сокету с ldap или ldaps вызывает использование интерфейса сокета UNIX.
- Использование ldapi с именем хоста вызывает ошибку.
- Использование ldapi без хоста или пути в запросе и без установки ldap_default_servers делает то, что библиотека делает по умолчанию.
9.17. Аутентификация LDAP и управляющая информация
Синтаксис LDAP URL не предоставляет пути передачи аутентификационной и иной управляющей информации на сервер. Чтобы сделать это возможным, URL в запросе LDAP может предшествоваться любым числом установок <name>=<value>, разделённых пробелами. Если значение содержит пробелы, они должны быть помещены в двойные кавычки, и, когда используются двойные кавычки, надо использовать обратный слэш как обычно. Распознаются следующие имена:
|
Значение параметра DEREFERENCE должно быть одним из слов never, searching, finding или always. Имя CONNECT устаревшее NETTIME, сохраненноё для обратной совместимости. Этот таймаут (заданный как число секунд) устанавливатся с клиентской стороны для операций, которые могут быть выполнены по сети. Специально это применяется к сетевым соединениям и вызовам функции ldap_result(). Если значение больше, чем ноль, используется LDAP_OPT_NETWORK_TIMEOUT, если задано в заголовках LDAP (OpenLDAP), или, если в заголовках LDAP (Netscape SDK 4.1) задано LDAP_X_OPT_CONNECT_TIMEOUT. Нулевое значение вызывает явную установку no timeout для Netscape SDK; для OpenLDAP никакого действия не происходит.
Параметр TIME (также число секунд) передаётся на сервер для установки серверных ограничений на время потраченное на поиск. Вот пример запроса LDAP в поиске exim, использующем некоторые из этих значений. Это одна строка, перенесённая, чтобы поместиться на странице:
${lookup ldap {user="cn=manager,o=University of Cambridge, c=UK" pass=secret ldap:///o=University%20of%20Cambridge,c=UK?sn?sub?(cn=foo)} {$value}fail} |
Кодирование пробелов, как %20 из URL, его нельзя делать для каких-либо вспомогательных данных. Конфигурационные настройки exim, включающие поиски, содержащие информацию о пароле, необходимо предварять hide, чтобы предотвратить возможность увидеть эти значения не-административными пользователями при использовании опции -bP.
Вспомогательные данные могут быть даны в любом порядке. По умолчанию таймаут отсутствует (используется системный таймаут), нет пользователя или пароля, нет ограничений на число возвращённых значений и нет ограничений по времени запроса.
Когда DN квотирован в USER=setting для LDAP аутентификации, exim удаляет любое URL-квотирование, которое может быть до LDAP. Очевидно, некоторые библиотеки делают это для себя, но некоторые нет. Удаление URL-квотирование даёт два преимущества:
- Это позволяет использовать тоже самое раскрытие quote_ldap_dn для USER=DN, что и для DN внутри фактических запросов.
- Это разрешает пробелы внутри USER=DN. Например, настройка типа
должна работать, даже если $1 содержит пробелы. Раскрытые данные для PASS=value должны быть квотированы с использованием оператора раскрытия quote, а не оператора квотирования LDAP. Единственная причина, по которой это поле нуждается в квотировании, состоит в том, чтобы гарантировать его соответствие синтаксису exim, который не разрешает пробелы вне кавычек. Например:USER=cn=${quote_ldap_dn:$1}
PASS=${quote:$3}
Аутентификационный механизм LDAP может использоваться для проверки паролей как часть SMTP-аутентификации. Смотрите условие ракрытия строки ldapauth в разделе 11.
9.18. Формат данных, возвращённых LDAP
Типы поиска ldapdn возвращают Distinguished Name (отличительное имя) из единственного элемента, как последовательность значений, например:
cn=manager, o=University of Cambridge, c=UK |
Тип поиска ldap генерирует ошибку, если более одного элемента соответствует фильтру поиска, тогда как ldapm разрешает этот случай и вставляет новую строку в результат до данных от различных входов. Это возможно для многочисленных значений, возвращённых для обоих ldap и ldapm, но в первом случае Вы знаете, что независимо от возвращённого значения, исходили из одиночного вхождения в каталоге.
В общем случае, где Вы задаёте один атрибут в Вашем LDAP-запросе, результат не квотируется и не содержит имя атрибута. Если атрибут имеет множественные значения, они разделются запятыми.
Если Вы определяете множественные атрибуты, результат содержит разделённые пробелами, квотированные строки, каждая с предшествующим именем атрибута и символом "равно". В пределах кавычек символы двойной кавычки, обратного слэша и новой строки экранируются обратным слэшем, и запятые используются для разделения многочисленных значений атрибута. В части для экранирования строка внутри кавычек принимает такую же форму, как вывод, когда запрашивается единственный атрибут. Если никакие атрибуты не заданы, это тоже самое, что и задание всех атрибутов.
Это некотрые примеры формата вывода. Превая строка каждой пары запрос LDAP, а вторая возвращённые данные. Атрибут attr1 имеет два значения, тогда как attr2 лишь одно:
ldap:///o=base?attr1?sub?(uid=fred) value1.1, value1.2 ldap:///o=base?attr2?sub?(uid=fred) value two ldap:///o=base?attr1,attr2?sub?(uid=fred) attr1="value1.1, value1.2" attr2="value two" ldap:///o=base??sub?(uid=fred) objectClass="top" attr1="value1.1,value1.2" attr2="value two" |
Оператор extract в раскытиях строки может быть использован для выбора индивидуальных полей из данных, состоящих из пар key=value. Вы модете использовать опцию exim -be для теста раскрытия и таким образом проверить результаты поиска в LDAP.
9.19. Дополнительные сведения о NIS+
Запросы NIS+ состоят из индексного имени (indexed name) NIS+, сопровождаемого опциональным двоеточием и именем поля. Если это задано, разультат успешного запроса: содержимое именованного поля, иначе результат состоит из объединённых пар 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 |
Martin Guerre |
9.20. Поиски SQL
Exim может поддерживать поиски в базах данных Interbase, MySQL, Oracle, PostgreSQL и SQLite. Запросы для этих БД содержат SQL-выражения, таким образом, пример мог бы быть таким:
${lookup mysql{select mailbox from users where id='userx'} {$value}fail} |
Если результат запроса содержит более одного поля, данные возвращаются для каждого поля, предшествуеиые его именем, таким образом, результат:
${lookup pgsql{select home,name from users where id='userx'} {$value}} |
home=/home/userx name="Mister X" |
Пустые значения и значения, содержащие пробелы, помещаются в двойные кавычки, внутренние кавычки экранируются обратным слэшем. Если результат запроса содержит лишь одно поле, значение возвращется дословно, без имени поля, например:
Mister X |
Если результат запроса приводит более, чем к одной строке, они все объединяются с новой строкой между данными для каждой строки.
9.21. Дополнительные сведения о MySQL, PostgreSQL, Oracle, и Interbase
Если используются какие-либо поиски в MySQL, PostgreSQL, Oracle или Interbase, должна быть установлена опция mysql_servers, pgsql_servers, oracle_servers или ibase_servers (соответственно) в виде списка информации о сервере, разделённого двоеточиями. Каждый элемент в списке разделённый слэшами список четырёх пунктов: имя хоста, имя базы данных, имя пользователя и пароль. В случае Oracle поле имени хоста используется для имени сервиса (service name), поле имени базы данных не используется и должно быть пустым. Например:
hide oracle_servers = oracle.plc.example//userx/abcdwxyz |
Поскольку данные пароля секретны, Вы всегда должны предшествовать настройку словом hide, для предотвращения просмотра установки неадминистративными пользователями при использовании опции -bP. Вот пример, где перечислены два сервера MySQL:
hide mysql_servers = localhost/users/root/secret:\ otherhost/users/root/othersecret |
Для MySQL и PostgreSQL хост может быть задан как <name>:<port>, но так как это список значений, разделённых двоеточиями, то оно должно быть удвоено. Для каждого запроса эти параметры групп проверяются в порядке успешности соединений и запросов.
Операторы раскрытия quote_mysql, quote_pgsql и quote_oracle конвертируют новую строку, табуляцию, возврат каретки и обратный слэш в \n, \t, \r и \b соответственно, а символы одиночной кавычки, двойной кавычки и обратного слэша экранируются обратным слэшем. Оператор раскрытия quote_pgsql, кроме того, экранирует символы процента и подчёркивания. Это нельзя делать для MySQL, поскольку эти символы экранирования не распознаются в контексте, где они не экранируют специальные символы.
9.22. Специальные возможности MySQL
Для MySQL пустое имя хоста или использование localhost в mysql_servers вызывает соединение с сервером на локальном хосте через сокет UNIX. Альтернативный сокет может быть указан в круглых скобках. Полный синтаксис каждого элемента в mysql_servers таков:
<hostname>::<port>(<socket name>)/<database>/<user>/<password> |
Любая из трёх частей первого поля может быть опущена. Для нормального использования на локальном хосте можно оставить пробел или установить лишь localhost.
Нет необходимости в указании БД: если она тут отсутсвует, то должна быть дана в запросах. Если запрос MySQL не возвращает никаких данных (команды insert, update или delete), результат поиска: число затронутых строк.
Внимание: Это может ввести в заблуждение. Если обновление ничего, фактически, не меняет (например, устанаваливая поле на то же самое значение), результат ноль, поскольку нет затронутых строк.
9.23. Специальные возможности PostgreSQL
Поиски в PostgreSQL также могут использовать сокет UNIX для соединения с БД. Обычно это быстрей и стоит меньше процессорного времени, чем подключение по TCP/IP. Однако он может использоваться лишь в случае, если сервер БД работает на той же самой машине, что и почтовый сервер. Конфигурационная строка для PostgreSQL, через сокет UNIX, выглядит так:
hide pgsql_servers = (/tmp/.s.PGSQL.5432)/db/user/password : ... |
Другими словами, вместо имени хоста даётся путь к сокету. Путь заключён в круглые скобки так, чтобы его прямые слэши не были визуально перепутаны с разделителями других параметров сервера.
Если запрос PostgreSQL не возвращает никаких данных (команды insert, update, или delete), результатом поиска станет число затронутых им строк.
9.24. Дополнительные сведения о SQLite
SQLite отличается от других поисков SQL, поскольку требуется имя файла, в дополнение к SQL-запросу. БД SQLite один файл, и нет демона, как в других БД. Интерфейс exim требует, чтобы имя файла как абсолютный путь было задано в начале запроса. Оно отделяется от запроса пустым пространством. Это означает, что путь и имя файла не могут содержать пустые символы. Вот пример раскрытия поиска:
${lookup sqlite {/some/thing/sqlitedb \ select name from aliases where id='userx';}} |
В списке похожий синтаксис. Например:
domainlist relay_domains = sqlite;/some/thing/sqlitedb \ select * from relays where ip='$sender_host_address'; |
Единственный символ, затрагиваемый оператором quote_sqlite: символ одиночной кавычки, которую он удваивает. Библиотека SQLite обрабатывает множественные одновременные доступы к базе данных внутренне. Множественные чтения разрешены, но лишь один процесс может производить обновление. Попытки обращения к БД во время обновления отклоняются после таймаута ожидания, в течение которого библиотека SQLite ждёт освобождения блокировки. В exim таймаут по умолчанию установлен в 5 секунд, но это может быть изменено с помощью опции sqlite_lock_timeout.