Мы — долго запрягаем, быстро ездим, и сильно тормозим.
www.lissyara.su —> документация —> EXIM —> 4.62 —> часть 9

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


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

  • 1. Строка, которая будет раскрыта, может содержать явный запрос поиска. По этой причине, часть строки будет заменена данными, найденными поиском. Поиски этого типа - условия в элементах раскрытия. Различные результаты могут быть заданы для случаев успешного и неудачного поисков. Смотрите раздел 11, где в деталях описано раскрытие строк.
  • 2. Списки доменов, хостов, и адресов e-mail могут содержать запрос поиска, как способ избежать слишком длинного линейного списка. В этом случае, данные возвращённые запросом, обычно (но не всегда) отбрасываются (короче, не нужны они - прим. lissyara); реально засчитывается - успешен ли поиск, или нет. Эта разновидность списков описана в разделе 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`a для необходимого вам запроса к БД.
       Код для каждого типа поиска находится в отдельном файле исходных текстов, и включается в бинарник exim`a лишь если при компиляции установлена соответствующая опция. Дефолтовые настройки в
    src/EDITME таковы:
    LOOKUP_DBM=yes
    LOOKUP_LSEARCH=yes
    

    и означают, что лишь линейный поиск и поиск в DBM включены по дефолту. Для некоторых типов поисков (например, БД SQL), вам необходимо инсталлировать библиотеки и файлы заголовков, до сборки exim`a.

    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`a с поддержкой 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`a. Утилита exim`a для создания файлов DBM (exim_dbmbuild) по дефолту включает нули, но у неё есть опция для их исключения (смотрите раздел 49.9).
  • dsearch: Данный файл должен быть директорией; ищется файл, имя которого равно ключу. Ключ не должен содержать символов слэша. Результат успешного поиска - имя файла. Пример, как этот поиск может использоваться для поддержки виртуальных доменов, дан в разделе 46.7.
  • iplsearch: Данный файл - текстовый файл, содержащий ключи и данные. Ключ завершается двоеточием, или пробелом, или концом строки. Ключи, в файле, должны быть IP-адресами, или IP-адресами с CIDR масками. Ключи включающие в себя адреса IPv6, должны быть заключены в кавычки, для предотвращения интерпретации первого внутреннего двоеточия как завершение ключа. Например:
    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
    

       Ключ для iplsearch поиска  должен быть IP-адресом (без маски). Поиск по файлу линейный, с использованием масок CIDR, где они заданы, до нахождения соответствия ключу. Используется первый совпадающий ключ; дальнейших попыток найти лучшее совпадение, не предпринимается. Кроме совпадения ключей, обработка iplsearch - такая же как у lsearch.
       Предупреждение 1: В отличие от большинства других одноключевых поисков, файл данных для
    iplsearch не может быть превращён в DBM или cdb-файл, поскольку эти типы поиска поддерживают только буквальные ключи.
       Предупреждение 2: В списке хостов,вы всегда должны использовать
    net-iplsearch, таким образом, чтобы неявный ключ был IP-адресом, а не именем (смотрите раздел 10.12).

  • lsearch: Данный файл - текстовый файл, по которому линейно ищется строка, начинающаяся с искомого ключа, законченную двоеточием, или пробелом, или концом строки. Используется первое найденное совпадение. пустое место между ключом и двоеточием разрешается. Остаток строки, после удаления начального и конечного пустого пространства является данными. Они могут быть продолжены на последующие строки путём начала их с любого количества пустого пространства, но только один символ пробела включается в данные при таком соединении. Если данные начинаются с двоеточия, ключ должен быть завершён двоеточием, например:
    baduser:  :fail:
    

       Пустые строки, и строки начинающиеся с # игнорируются, даже если они встречаются в середине строки. Это - традиционный текстовый формат файла альясов. Обратите внимание, что ключи в файле lsearch - литеральные строки. Тут нету подстановки (wildcarding) какого бы то ни было вида.
       В большинстве
    lsearch файлов, ключи не могут содержать двоеточия, или символы #, или пустые пробелы. Однако, если вам необходима эта возможность, она доступна. Если ключ начинается с символа двойной кавычки, она завершается только соответствующей кавычкой (или концом строки), и, к её содержимому, применяются обычные правила экранирования (смотрите раздел 6.16). Опционально, двоеточие разрешено после ключа в кавычках (также как и для ключей без кавычек). Специальная обработка кавычек для части данных строки lsearch отсуствует.

  • nis: Данный файл - имя карты NIS, и поиск NIS производится с данным ключом, без завершающего двоичного нуля. Есть вариант, называемый nis0, который включает двоичный нуль в ключ. По сведениям, это необходимо для файла альясов в стиле SUN. Exim не понимает NIS-альясы; должны использоваться полные имена карт.
  • wildlsearch или
  • nwildlsearch: Поиск по файлу линейный, как lsearch, но вместо того, чтобы интепретировать как литеральную строку, каждый ключ в файле может быть подстановочным. Различие между этими двумя типами поиска в том, что для wildlsearch каждый ключ в файле раскрывается до начала использования, тогда как для nwildlsearch нет раскрытия на месте.
       Как и
    lsearch, тестирование производится без учёта регистра. Признаются следующие формы подстановочных знаков:
  • 1. Строка может начинаться со звёздочки, для обозначения кончается на. Например:
    *.a.b.c       data for anything.a.b.c
    *fish         data for anythingfish
    

  • 2. Строка может начинаться с крышки (^), для обозначения регулярного выражения. Например, для wildlsearch:
    ^\N\d+\.a\.b\N    data for <digits>.a.
    

       Примечание - использование \N отключает раскрытие содержимого регулярного выражения. Если вы используете nwildlsearch там, где ключи не раскрываются, это эквиалентрно:
    ^\d+\.a\.b        data for <digits>.a.b
    

       Если регулярное выражение содержит пустое место, или символы двоеточия, вы должны поместить его в кавычки (смотрите lsearch, выше), или представить эти символы другим образом. Например, \s может быть использовано для обозначения пробела и \x3A - для двоеточия. Это может оказаться легче, чем использовать кавычки, поскольку при использовании кавычек, вы должны экранировать все обратные слэши внутри кавычек.
       Примечание: Невозможно зафиксировать подстроки в совпадении регулярного выражения, для дальнейшего использования, поскольку результаты всех поисков кэшируются. Если поиск повторяется, результат берётся из кэша, и нет фактического сопоставления с образцом. Значения всех цифровых переменных сбрасываются после совпадения
    (n)wildlsearch.

  • 3. Хотя я не вижу много применений, общая функция соответствия, используемая для реализации (n)wildlsearch, означает, что строка может начинаться с имени поиска, завершаемого двоеточием, и сопровождаться данными поиска. Например:
    cdb;/some/file  data for keys that match the file
    

       Данные, полученные из вложенного поиска, отвергаются.
       Ключи, которые не соответствуют ни одному из этих паттернов, интепретируются буквально. Правила продолжения для данных - точно ткие же как для
    lsearch, и ключи могут сопровождаться опциональными двоеточиями.
       Предупреждение: В отличие от большинства других одноключевых поисков, файл данных для
    (n)wildlsearch не может быть превращён в DBM или cdb-файл, поскольку эти типы поиска поддреживают только буквальной соответствие.

    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, например:
    require condition = \
      ${lookup whoson {$sender_host_address}{yes}{no}}
    

       Запрос состоит из единственного IP-адреса. Возвращённое значение - имя аутентифицированного пользователя, который сохранён в переменной $value. Однако, в этом примере, данные $value не используются; результат поиска - одна из фиксированных строк - yes or 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
    

    тогда, когда частичное соответсвие включено, это совпадает (в том числе) 2001.dates.fict.example и 1984.dates.fict.example.  Также совпадает с 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`y работать с форматами файлов используемыми другими 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`a. Средства для межпроцессного кэширования отсутсвуют.
       Для одноключевого поиска, exim оставляет релевантные файлы открытыми в случае, если есть другой поиск, нуждающийся в них. В некоторых типах конфигураций, это может привести к большому числу открытых файлов, сохраняемых открытыми, оставляемых открытыми для сообщений со многими получателями. Для избежания попадений под системные ограничения на число открытых файлов, exim закрывает последний использованный файл, когда необходимо открыть больше файлов чем позволяют его внутренние ограничения, которое можно изменить через опцию
    lookup_open_max.
       Файлы одноключевого поиска закрываются и сбрасывается кэш поиска в стратегических точках доставки - например, после завершения всех роутингов.

    9.9 Квотирование (помещение в двойные кавычки) данных поиска

       Когда данные из входящего сообщения включаются в поиск типа запросов, возможно появление специальных символов в данных, нарушающих синтаксис запроса. Например, запрос NIS+ содержащий
    [name=$local_part]
    

    будет прерван, если локальная часть будет содержать правую квадратную скобку. Для данных NIS+, данные должны быть помещены в двойные кавычки, как в примере:
    [name="$local_part"]
    

    но это оставляет проблемы с кавычками в данных. Правила для NIS+ состоит в том, что двойные кавычки должны быть удвоены. Другие типы поиска имеют иные правила, и для решения этих требований существет оператор раскрытия такой формы:
    ${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, and 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}}
    

    имеет два разделённых пробелами поля: код авторизации и имя целевого хоста. Авторизационный код может быть Y для yes, N для no, X для явно требуемой, но отсутствующей авторизации, или ? для неизвестного.

    9.12 Множественные поиски dnsdb

       В предыдущих разделах описаны поиски для одиночного домена. Однако, вы можете задать список доменов или адресов в отдельном dnsdb поиске. Список задаётся в нормальном виде exim`a, с двоеточием в качестве дефолтового разделителя, но с возможностью изменить его. Например:
    ${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; она стала Open LDAP, и сейчас существует два различных релиза. Другая реализация происходит из Netscape, Solaris 7 и последующие релизы содержат встроенную поддержку LDAP. К сожалению, хотя все они совместимы на уровне функционирования запросов, обработка их ошибок различна. По этой причине необходимо установить переменную, во время компиляции exim`a с 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, как определено в RFC2255. Например, в конфигурации роутера 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
    

    в соответствии с RFC2254. Результирующая строка квотируется согласно правилам для URL, т.е. все не алфавитно-цифровые символы, кроме:
    ! $ ' - . _ ( ) * +
    

    конвертируются в их шастнадцатеричные значения, с предшествующим им символом процента. Например:
    ${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, в запросех. Концептуально, вначале он конвертирует строку, вставляя обратный слэш перед любым из следующих символов:
    , + " \ < > ;
    

       Он также вставляет обратный слэш перед любыми пробелами или символом #, и перед конечными пробелами. (Правила находятся в RFC2253.) Тогда результирующая строка квотирована согласно правилам для 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.  Он предоставляет список серверов, разделённых двоеточиями, пробуемых по очереди, пока запрос не будет успешно обработан, или не произойдёт серьёзная ошибка. Успешная обработка или вернёт запрошенные данные, или укажет, что они не существуют. Серьёзные ошибки - синтаксические, или много значений, когда ожидается только одно. Ошибки, приводящие к пробе следующего сервера - сбои подключения, привязки, и таймауты.
       Для каждого имени сервера, в списке, можно задать номер порта. Стандартный способ задания хоста и порта - использование двоеточия, как разделителя (RFC1738). Поскольку
    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-запросах. Нижеследующее (имеется ввиду - про ldapi - прим. lissyara), применяется только в 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 установить параметр разименования
    NETTIME установить таймаут сетевой операции
    USER установить DN для аутентификации связи LDAP
    PASS установить пароль для аутентификации связи LDAP
    SIZE установить ограничение числа возвращаемых входов
    TIME установить таймаут запроса


       Значение параметра
    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`a, использующем некотрые из этих значений. Это - одна строка, перенесённая, чтобы поместиться на странице:
    ${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`a, включающие поиски содержащие информацию о пароле, необходимо предварять hide, чтобы предотвратить возможность увидеть эти значения не-административными пользователями, при использовании опции -bP.
       Вспомогательные данные могут быть даны в любом порядке. По умолчанию - таймаут отсутсвует (используется системный таймаут), нет пользователя или пароля, нет ограничений на число возвращённых значений, и нет ограничений по времени запроса.
       Когда DN квотирован в USER= setting для LDAP аутентификации, exim удаляет любое URL-квотирование, которое может быть до LDAP. Очевидно, некоторые библиотеки делают это для себя, но некоторые нет. Удаление URL-квотирование даёт два преимущества:

  • Это позволяет использовать тоже самое раскрытие quote_ldap_dn для USER= DNs, что и для DNs внутри фактических запросов.
  • Это разрешает пробелы внутри USER= DNs.
       Например, настройка типа
    USER=cn=${quote_ldap_dn:$1}
    

    должна работать, даже если $1 содержит пробелы.
       Раскрытые данные для PASS= value должны быть квотированы с использованием оператора раскрытия
    quote, а не оператора квотирования LDAP.  Единственная причина, по которой это поле нуждается в квотировании, состоит в том, чтобы гарантировать его соответствие синтаксису exim`a, который не разрешает пробелы вне кавычек. Например:
    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`a -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
    

    без кавычек. Поиск NIS+ неудачен если NIS+ возвращает больше одного элемента таблицы для данного индексного ключа. Эффект оператора раскрытия quote_nisplus удваивает любые символы кавычек внутри текста.

    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`a требует чтобы имя файла, как абсолютный путь, было задано в начале запроса. Оно отделяется от запроса пустым пространством. Это означает что путь и имя файла не могут содержать пустые символы. Вот пример раскрытия поиска:
    ${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.


    =============
    translated by lissyara





  •  

      Этот информационный блок появился по той простой причине, что многие считают нормальным, брать чужую информацию не уведомляя автора (что не так страшно), и не оставляя линк на оригинал и автора — что более существенно. Я не против распространения информации — только за. Только условие простое — извольте подписывать автора, и оставлять линк на оригинальную страницу в виде прямой, активной, нескриптовой, незакрытой от индексирования, и не запрещенной для следования роботов ссылки.
      Если соизволите поставить автора в известность — то вообще почёт вам и уважение.

    © lissyara 2006-10-24 08:47 MSK

    Время генерации страницы 0.0621 секунд
    Из них PHP: 54%; SQL: 46%; Число SQL-запросов: 56 шт.
    Исходный размер: 140789; Сжатая: 28102