В статье «Запросы и обновления Active Directory» (http://www.osp.ru/text/302/2578905/) я рассказывал о том, как можно использовать модули Net::LDAP языка Perl для автоматизации инфраструктуры службы Active Directory (AD) с помощью языка Perl и стандартного протокола Lightweight Directory Access Protocol (LDAP). Но некоторые из функций, которые необходимо реализовать, такие как поиск или удаление объектов, не могут постоянно выполняться на основе стандартных операций LDAP. В некоторых случаях протокол LDAP накладывает на клиента ограничения, не давая ему выполнять нежелательные действия, например, удалять целое дерево иерархии каталогов. Однако во многих ситуациях действительно потребуется выполнять действия, которые невозможно осуществить на основе стандартных операций протокола LDAP. В таких случаях в игру вступают средства управления протокола LDAP.

Средства управления LDAP

Документ Internet Engineering Task Force (IETF) Request for Comments (RFC) 2251 (http://www.ietf.org/rfc/rfc2251.txt) определяет средства управления LDAP как часть спецификации протокола LDAP версии 3 (LDAPv3). Средства управления являются важной особенностью протокола LDAP: они позволяют поставщикам создавать расширения для операций LDAP на высшем уровне сервера каталогов без изменения спецификации протокола LDAP. Вы можете присоединить средства управления к определенному клиентскому запросу, и если сервер поддерживает работу со средствами управления, он выполнит специальную обработку, определяемую средствами управления. Обычно средства управления отвечают изменениям в стандартах Internet и публикуются в документах RFC.

Средства управления LDAP, о которых я расскажу в этой статье, осуществляют сортировку результатов поиска на уровне сервера, поиск с постраничным выводом результатов для более эффективной обработки результатов поиска и полное удаление поддеревьев каталогов за одну операцию. Служба AD поддерживает эти средства управления и многие другие. Полный список поддерживаемых средств управления приведен по адресу http://msdn.microsoft.com/library/en-us/netdir/ldap/exted_controls.asp.

Средства управления и модули Net::LDAP

Перед тем как углубиться в изучение конкретных средств управления, давайте коротко рассмотрим, как средства управления используются совместно с модулями Net::LDAP. Тем, кто впервые сталкивается с модулями Net::LDAP, я рекомендую прочесть статью «Запросы и обновления Active Directory». В ней описана работа с модулями Net::LDAP и дается информация о том, где их можно получить и как установить. На момент подготовки этой статьи последней рабочей версией был модуль Net::LDAP 0.26, его я и использовал для сценариев, описанных ниже.

В модулях Net:: LDAP средства управления представлены в виде объектов. Модуль Net::LDAP::Control позволяет создать новый элемент управления. Для этого надо указать параметр в методе new(), код которого выглядит следующим образом:

Net::LDAP::Control->new(parm, parm, parm)

где parm - параметр, который зависит от типа создаваемого средства управления. Каждый элемент управления, как правило, имеет собственные параметры, которые можно использовать для настройки его поведения. После создания экземпляра объекта средства управления можно использовать его в качестве параметра в операции, которую требуется расширить. Большинство методов доступны в модуле Net::LDAP, отображающем множество операций LDAP, имеющих дополнительный параметр control, в который помещают массив-ссылку на объекты средств управления. Этот параметр позволяет при необходимости указать несколько (более одного) средств управления. Теперь давайте познакомимся с первым элементом управления.

Сортировка на уровне сервера

Возможно, одним из самых простых в использовании среди средств управления является средство сортировки на уровне сервера, описанное в документе RFC 2891 (http://www.ietf.org/rfc/rfc2891.txt). Заголовок документа RFC "LDAP Control Extension for Server Side Sorting of Search Results" упрощает задачу данного средства управления. Когда вы выполняете стандартную операцию поиска протокола LDAP, результаты возвращаются в случайном порядке, а это может быть неудобно, если требуется вывести выходные данные на внешний интерфейс для пользователей. Можно было бы получить все результаты поиска, собрать их в какой-либо структуре данных, такой как список или массив, и отсортировать после завершения выполнения запроса. Однако такой вид сортировки для больших множеств результатов неэффективен. Более удачным, по крайней мере, с точки зрения клиента, является решение осуществлять на сервере сортировку результатов поиска; таким образом, клиент может начинать отображение записей в отсортированном порядке сразу с момента их получения.

Листинг 1 содержит пример использования средства сортировки на уровне сервера. Сценарий осуществляет поиск и печать всех объектов-пользователей в каталоге Global Catalog (GC), которые имеют фамилию (sn). Этот пример имеет сложную структуру, но он показывает, как используется элемент управления, и вы можете легко адаптировать код для выполнения полезных задач в своем окружении. Код в блоке A Листинга 1 содержит присоединения модулей. Использование служебного слова strict не повредит даже в тривиальных сценариях, так как трудно заранее сказать, когда эти сценарии могут перерасти в более сложную структуру. Три модуля Net::LDAP необходимы для выполнения поиска и использования средства управления. Модуль Net::LDAP::Constant возвращает константу LDAP_CONTROL_SORTREQUEST, которая содержит уникальный номер, описывающий элемент управления, в дальнейшем передаваемый сценарием в модуль Net::LDAP::Control.

Код в блоке B Листинга 1 содержит все настройки конфигурации сценария, то есть переменные, которые необходимо изменить, чтобы сценарий корректно работал в вашем окружении. Я описал все переменные в сценарии, чтобы вы знали, для чего используется каждая из них. Код в блоке C Листинга 1 отображает соединение с контроллером домена (DC) через порт GC (порт 3268) и информацию о соединении. Обратите внимание на использование аргумента version при вызове метода Net::LDAP->new(). Модуль Net::LDAP 0.26 по умолчанию использует протокол LDAPv2. Так как средства управления появились только в версии LDAPv3, необходимо использовать параметр version для включения поддержки протокола LDAPv3. В следующей версии модуля Net::LDAP по умолчанию будет использоваться протокол LDAPv3.

Код блока D в Листинге 1 является центральной частью сценария. Первая строка создает экземпляр объекта-средства сортировки на уровне сервера. Я передаю константу LDAP_CONTROL_SORTREQUEST, которая импортируется в код блока A, и параметр order, определяющий атрибут, по которому будет производиться сортировка результатов. Следующий шаг – выполнение запроса. Я передаю стандартные параметры поиска и параметр control, в который записывается ссылка-массив на объекты модуля управления. Оставшаяся часть кода повторяется для каждой совпадающей записи и распечатывает атрибут sn для каждой записи.

Поиск с постраничным выводом результата

Одно существенное ограничение использования модулей Net::LDAP для обращения к AD связано с административным ограничением протокола LDAP в отношении поиска. Чтобы помочь ограничить количество процессов обработки, требуемое для запросов, под которые подходят тысячи записей, службы каталогов, такие как AD, определяют административные ограничения на максимальное количество записей, возвращаемое одним запросом. По умолчанию административный лимит службы AD равен 1000. Таким образом, если вы выполняете запрос, под который подходят тысячи записей, служба AD на выходе выдаст лишь 1000 записей.

Если под запрос в Листинге 1 попадает более тысячи записей, сценарий прерывает работу и выдает сообщение об ошибке: Sizelimit exceeded. Если удалить строку

die $search->error 
if $search->code;

сценарий повторится для 1000 найденных записей. Обычно такого поведения сценария недостаточно. Чаще всего требуется, чтобы в результате поиска на выходе были получены все найденные записи, независимо от административного ограничения. Поэтому документ RFC 2696 (http://www.ietf.org/rfc/rfc2696.txt) определяет элемент управления протокола LDAP для простой работы со страницами результатов. Некоторые службы каталогов, в том числе AD, поддерживают данный элемент управления. Элемент управления с постраничным выводом результатов информирует службу каталогов о том, что на выходе поиска может быть получено очень много записей, и что вы хотите получить их в виде страниц, или «порций». Для работы с соблюдением административного ограничения вы указываете, что сценарий должен возвращать записи в виде страниц, в каждой из которых содержится 1000 и менее записей.

Листинг 2 поясняет использование модуля управления с постраничным выводом результатов; оно несколько сложнее, чем использование модуля сортировки на уровне сервера. В коде блока A Листинга 2 я импортирую константу LDAP_CONTROL_PAGED, чтобы иметь возможность задействовать элемент управления с постраничным выводом результатов. В коде блока B Листинга 2 я устанавливаю фильтр поиска ($filter), чтобы обеспечить выбор всех объектов-пользователей в каталоге GC, и массив @attrs, который будет содержать список возвращаемых атрибутов. Указать желаемые возвращаемые атрибуты важно, так как в противном случае запрос будет возвращать все атрибуты для каждой записи, что сильно увеличит время и объем памяти, необходимые для работы сценария.

Код в блоке C Листинга 2 создает новый экземпляр объекта-модуля управления с постраничным выводом результатов и устанавливает параметр size (количество записей на каждой странице) в значение 1000. Если указать значение, превышающее административное ограничение, определенное на сервере, в ходе обработки результатов сценарий заменит это значение значением ограничения. Следующий раздел кода в блоке C определяет массив @args, который содержит пары ключ/значение, передаваемые в метод search(). Метод search() является частью цикла while, так что сценарий будет продолжать вызов метода до тех пор, пока метод не вернет пустое множество записей. Внутри цикла while цикл foreach обрабатывает каждую запись, возвращаемую на данную страницу поиска. После этого код блока C устанавливает данные cookie-файла, информируя сервер о том, что требуется следующая страница записей. Если по какой-либо причине этот вызов не выполняется, сценарий прерывает работу цикла.

Удаление дерева

Еще одним элементом управления, с которым важно разобраться, является модуль удаления деревьев. Как объяснялось в статье "Запросы и обновления Active Directory", можно использовать метод delete() объекта Net::LDAP ($ldap) для удаления объектов из каталога. Метод delete() имеет одно существенное ограничение: он не может удалять контейнеры или объекты-прародители других объектов.

Например, если бы вы попытались удалить организационную единицу (OU), содержащую 100 объектов, следующим образом:

$rc = 
$ldap->delete($dn_to_delete);

вы бы получили сообщение об ошибке 0000208C: UpdErr: DSID-030A02AF, problem 6003 (CANT_ON_NON_LEAF), data 0. Это сообщение подразумевает, что вы не можете удалять «нестраничные» объекты (то есть контейнеры). Средство удаления деревьев позволяет удалить контейнер и все его объекты. Элемент управления изначально был создан как «черновик» в Internet (http://www.alternic.org/drafts/drafts-a-b/draft-armijo-ldap-treedelete-02.html), и его планировалось оформить в виде документа RFC, но официально RFC никогда не выпускался, и в конце концов про черновик забыли. Тем не менее, служба AD поддерживает этот элемент управления.

Сценарий в Листинге 3 использует средство удаления деревьев для удаления контейнера OU (ou=sales,dc=mycorp,dc=com). Код блока A в Листинге 3 аналогичен коду в двух предыдущих листингах за тем исключением, что он не использует модуль Net::LDAP::Constant. На момент публикации модули Net::LDAP не имели константы для модуля удаления деревьев, поэтому мне пришлось создать собственную константу, которая возвращает параметр Object Identifier (OID) для модуля управления. Именно параметр OID сценарий передает в метод new() модуля Net::LDAP::Control.

Код в блоке B Листинга 3 включает некоторые параметры конфигурации из Листингов 1 и 2, а также переменную $dn_to_delete, которая содержит характерное имя (DN) объекта или контейнера, который требуется удалить. Код блока C Листинга 3 сначала создает экземпляр объекта-модуля удаления деревьев. Элемент управления не требует никаких дополнительных параметров. Для использования данного модуля управления я ввожу в методе delete() параметр control, который указывает на ссылку-массив, содержащий объект-модуль удаления деревьев. Код блока C завершается поиском ошибок и выводом результатов.

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


Листинг 1: Код для использования модуля сортировки на уровне сервера
# Начало Блока A
use strict;
use Net::LDAP;
use Net::LDAP::Control;
use Net::LDAP::Constant qw( LDAP_CONTROL_SORTREQUEST );
# Конец Блока A
 
# Начало Блока B
НАЧАЛО КОММЕНТАРИЯ
# Установка DC согласно запросу.
КОНЕЦ КОММЕНТАРИЯ
my $dc = 'dc1';
 
НАЧАЛО КОММЕНТАРИЯ
# Установка порта согласно запросу.
КОНЕЦ КОММЕНТАРИЯ
my $port = 3268;
 
НАЧАЛО КОММЕНТАРИЯ
# Установка имени пользователя, применяемого при аутентификации.
КОНЕЦ КОММЕНТАРИЯ
my $user = 'administrator@mycorp.com';
 
НАЧАЛО КОММЕНТАРИЯ
# Установка пароля пользователя.
КОНЕЦ КОММЕНТАРИЯ
my $passwd = 'Adminpasswd';
 
НАЧАЛО КОММЕНТАРИЯ
# Установка корневого дерева, в котором будет производиться поиск.
КОНЕЦ КОММЕНТАРИЯ
my $base = 'dc=mycorp,dc=com';
 
НАЧАЛО КОММЕНТАРИЯ
# Установка объекта поиска.
КОНЕЦ КОММЕНТАРИЯ
my $scope = 'subtree';
 
НАЧАЛО КОММЕНТАРИЯ
# Установка фильтра поиска.
КОНЕЦ КОММЕНТАРИЯ
my $filter = '(&(objectclass=user)(objectcategory=Person)(sn=*))';
 
НАЧАЛО КОММЕНТАРИЯ
# Установка атрибута, по которому будет осуществляться сортировка.
КОНЕЦ КОММЕНТАРИЯ
my $sort = 'sn';
# Конец Блока B
 
# Начало Блока C
my $ldap = Net::LDAP->new($dc, port=> $port, version => 3)
 or die "Could not connect: $@";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# Конец Блока C
 
# Начало Блока D
my $sortc = Net::LDAP::Control->new( LDAP_CONTROL_SORTREQUEST,
 order => $sort);
my $search = $ldap->search (
 base => $base,
 scope => $scope,
 filter => $filter,
 control => [ $sortc ] );
die $search->error if $search->code;
 
foreach my $entry ($search->entries) {
 print $entry->get_value('sn'),"
";
}
 
$ldap->unbind;
# Конец Блока D

 

Листинг 3: Код для использования модуля удаления деревьев
# Начало Блока A
use strict;
use Net::LDAP;
use Net::LDAP::Control;
sub LDAP_CONTROL_TREE_DELETE () { "1.2.840.113556.1.4.805" }
# Конец Блока A
 
# Начало Блока B
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
 
НАЧАЛО КОММЕНТАРИЯ
# This variable should contain the DN of the object to delete. 
КОНЕЦ КОММЕНТАРИЯ
my $dn_to_delete = 'ou=sales,dc=mycorp,dc=com';
# Конец Блока B
 
my $ldap = Net::LDAP->new($dc, version => 3)
 or die "Could not connect: $@";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
 
# Начало Блока C
my $treedelc = Net::LDAP::Control->new( LDAP_CONTROL_TREE_DELETE );
$rc = $ldap->delete($dn_to_delete, control => [ $treedelc ]);
 
if ($rc->code) {
 print "ERROR:
",$rc->error;
}
else {
 print "Deletion successful
";
}
 
$ldap->unbind;
# Конец Блока C