. Контроллеры домена хранят схему в виде набора объектов, объединенных в папке, в то время как серверы, использующие протокол Lightweight Directory Access Protocol (LDAP), хранят схему в виде набора текстовых файлов, не объединенных в одну папку. Хранение схемы в виде объектов, заключенных в контейнере Schema, имеет некоторые преимущества. Самое важное преимущество заключается в том, что вы можете использовать службы и сценарии для изменения схемы, аналогично тому, как используете их для работы с другими типами объектов.

Распространенным способом расширения схемы AD является использование формата данных LDAP Data Interchange Format (LDIF), определенного в документе Engineering Task Force (IETF) Request for Comments (RFC) 2849. Все основные поставщики программного обеспечения для службы каталогов поддерживают формат LDIF. Благодаря этому их продуктам доступны службы, использующие LDIF для импорта и экспорта данных службы каталогов. Например, утилита LDIF Directory Exchange (Ldifde – средство командной строки в системе Windows 2000 и более поздних версиях) и модули Perl Net::LDAP используют файлы LDIF для импорта и экспорта данных службы AD. Давайте посмотрим, как можно использовать модули Net::LDAP для автоматизации не только импортирования файлов LDIF в схему, но и верификации расширений схемы, что поможет снизить вероятность ошибок при импортировании. Но сначала познакомимся с принципами работы файлов LDIF и процессом установки модулей Net::LDAP.

Пример использования файлов LDIF

Приложения, которым разрешено сохранять данные в службе AD, обычно поставляются в комплекте с файлами LDIF, которые можно использовать для расширения схемы. На Рисунке 1 изображен пример файла LDIF, который расширяет схему AD, создавая новый объект-атрибут и добавляя его в класс User.

  

Первый фрагмент кода LDIF на Рисунке 1 (фрагмент А) создает объект, который представляет собой новый атрибут под названием rallencorp-LanguagesSpoken. Первая строка каждого фрагмента кода LDIF начинается с выражения dn, после которого записывается уникальное имя объекта (DN). Следующая строка начинается с выражения changetype, после которого указывается один из трех параметров: add (если вы хотите создать объект), modify (если вы хотите изменить объект) или delete (если вы хотите удалить объект). Если указывается параметр add, все последующие строки настраивают атрибуты объекта. Эти строки записывают в формате

attributeName: Value

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

description: My description 1 
description: My description 2

задают два значения (то есть My description 1 и My description 2) для сложного атрибута с именем description. Любой код LDIF заканчивается пустой строкой.

Второй фрагмент кода LDIF на Рисунке 1 (фрагмент B) необходим для перезагрузки кэша схемы. Контроллеры домена хранят копию схемы на диске в файле ntds.dit и в памяти в кэше схемы. Независимо от того, добавляете вы данные или изменяете схему, система немедленно прописывает изменения на диск, но не может обновлять кэш схемы в течение 5 минут. Если вы создаете объект для последующего обращения к нему, как это сделано в файле на Рисунке 1, чтобы избежать возникновения ошибки, нужно перезагрузить кэш схемы.

Хотя код во фрагменте B имеет только 6 строк, он содержит несколько важных характеристик. Как можно было заметить, строка dn не является указателем на DN. Этот пропуск - не ошибка. Пустая строка dn эквивалентна объекту RootDSE, который можно использовать для получения уникального имени контейнера Schema (об этом я расскажу позже). Строка changetype указывает, что я хочу изменить атрибуты данного объекта. Следующая строка задает тип выполняемой операции изменения. Вы можете указать типы add (если хотите добавить значение атрибута, которое не было ранее опубликовано в службе каталогов), replace (если вы хотите заменить текущее значение атрибута новым значением) или delete (если вы хотите удалить текущее значение атрибута). В данном случае я добавляю значение в атрибут schemaUpdateNow, являющийся операционным атрибутом, который динамически перезагружает кэш схемы. Пятая строка содержит дефис (-), который необходимо добавлять во все фрагменты изменения. Если вы хотите изменить другой атрибут того же объекта, просто добавляйте информацию об операции изменения атрибута сразу после строки, содержащей дефис. Другими словами, не нужно снова прописывать строки dn и changetype. Листинг 2 показывает пример кода LDIF, изменяющего два атрибута одного и того же пользовательского объекта. Если вы не хотите изменять другие атрибуты данного объекта, просто добавьте пустую строку после строки с дефисом, чтобы обозначить конец фрагмента изменения.

Последний фрагмент на Рисунке 1(фрагмент C) изменяет класс User, добавляя новый объект rallencorp-LanguagesSpoken как часть атрибута mayContain. Этот код похож на код во фрагменте B. Для установки значения любого атрибута необходимо знать его синтаксис. В данном случае в качестве значения атрибута mayContain необходимо указать выражение lDAPDisplayName.

Как можно было заметить, Рисунок 1 не содержит примера кода удаления. В отличие от других типов объектов, служба AD не позволяет удалять классы и объекты-атрибуты.

Этот обзор возможностей файлов LDIF был достаточно кратким. Более подробную информацию о файлах LDIF можно найти в документе RFC 2849 по адресу http://www.faqs.org/rfcs/rfc2849.html. Информацию о схеме службы AD и ее синтаксисе можно найти по адресу http://msdn.microsoft.com/library/en-us/adschema/adschema/active_directory_schema.asp.

Установка модулей Net::LDAP

Прежде чем показать, как использовать модули Net::LDAP для автоматизации импортирования файлов LDIF, я расскажу, как устанавливать эти модули. Если вы настроили среду Comprehensive Perl Archive Network (CPAN), можете устанавливать модули прямо из нее, запустив следующие два компонента:

perl -MCPAN -e shell 
install Net::LDAP

Для корректной работы оболочки CPAN требуется несколько программ. Поэтому, если оболочка не была настроена ранее, непосредственная загрузка модулей Net::LDAP может показаться более простым решением. Последнюю версию модулей Net::LDAP и его сетевую документацию можно найти на странице Perl-LDAP Homepage (http://perl-ldap.sourceforge.net). Если вам еще не приходилось устанавливать модули Perl, советую посетить страницу «What To Do Once You've Downloaded A Module From The CPAN» (http://www.cpan.org/modules/INSTALL.html), чтобы познакомиться с алгоритмом установки модулей на конкретную платформу.

К моменту написания статьи версия Net::LDAP 0.29 была последней, поэтому сценарий ImportLdapFile.pl, приведенный в Листинге 1, использует именно эту версию. Чтобы определить, какая версия модулей Net::LDAP установлена (если установлена) на вашей системе, выполните команду

perl -MNet::LDAP -e "print $Net::LDAP::VERSION"

Принципы работы сценария

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

Шаг 1: Получение данных из командной строки. Код во фрагменте A Листинга 1 гарантирует, что пользователи передают четыре параметра при запуске сценария из командной строки. В качестве первого параметра должно указываться имя или IP-адрес контроллера домена, который исполняет роль Schema Flexible Single-Master Operation (FSMO). В качестве второго параметра должно быть указано основное (принципиальное) имя пользователя (UPN – например, administrator@rallencorp.com) или уникальное имя (например, cn=administrator,cn=users,dc=rallencorp,dc=com) пользователя из группы Schema Admins. Третьим параметром должен быть пароль пользователя. Четвертым параметром должен быть путь к файлу LDIF. Когда все 4 параметра получены, сценарий сохраняет их в переменных $server, $user, $passwd и $ldif_file соответственно. Если один или несколько параметров отсутствуют, сценарий возвращает сообщение об ошибке и завершает работу.

Шаг 2: Получение уникального имени контейнера Schema. Используя имя и пароль, указанные в командной строке, код фрагмента B дает модулю Net::LDAP команду установить соединение с указанным сервером. Затем сценарий опрашивает объект RootDSEЮ чтобы получить значение атрибута schemaNamingContext, которое является уникальным именем контейнера Schema.

Шаг 3: Проверка расширений схемы. Код во фрагменте C использует полученное DN для открытия и чтения указанного файла LDIF, после чего проверяет расширения схемы. Сценарий открывает файл с помощью метода new() модуля Net::LDAP::LDIF, который получает 3 параметра. Первый параметр – путь к файлу LDIF, который в нашем случае содержится в переменной $ldif_file. Второй параметр сообщает методу, какое действие выполнять с файлом: чтение ("r") или запись ("w"). Сценарий ImportLdapFile.pl осуществляет чтение файла, так как целью сценария является импортирование содержания файла. Если вы хотите, чтобы сценарий экспортировал данные в файл LDIF, можете использовать параметр "w". Третий параметр, под названием onerror, определяет, какое действие будет выполнено, если метод read_entry() модуля::LDAP::LDIF (используемый сценарием для чтения файла LDIF) вызовет ошибку. Сценарий устанавливает параметр onerror в значение 'undef', то есть метод read_entry() будет возвращать значение undef if при возникновении ошибки. Если нужно, что бы при возникновении ошибки сценарий выводил сообщение об ошибке и завершал работу, установите параметр в значение 'die'. Если вы хотите, чтобы сценарий только выводил сообщение об ошибке, установите параметр в значение 'warn'.

После открытия файла LDIF, сценарий использует метод read_entry() в цикле while для чтения каждого фрагмента файла LDIF. Если при чтении фрагмента сценарий находит ошибку, он прерывает работу цикла. Если ошибок не обнаружено, сценарий осуществляет поиск в службе AD. Если в строке changetype фрагмента файла LDIF указан параметр add, в ходе поиска проверяется, не существует ли добавляемый объект. Если запись существует, сценарий сообщает об ошибке. Если в строке changetype указан параметр modify или delete, в ходе поиска проверяется существование объекта, который должен быть изменен или удален. Если объекта не существует, сценарий сообщает об ошибке.

Метод eof() модуля Net::LDAP::LDIF возвращает значение true, если цикл достиг конца файла. В этот момент, убедившись, что ошибок не возникает, сценарий выводит сообщение Verification complete. Если ошибки возникают, сценарий прерывает работу.

Шаг 4: Импортирование файла LDIF. Если процесс верификации прошел успешно, сценарий ImportLdapFile.pl импортирует файл LDIF в службу AD, как видно из кода фрагмента D. Как и код верификации, процедура импортирования использует метод new() для открытия файла LDIF и метод read_entry() в цикле while для чтения фрагментов файла. Если при чтении не возникает ошибок, сценарий вызывает метод update(). Этот метод прописывает добавления и изменения классов и атрибутов в схему службы AD. Цикл while исполняется до тех пор, пока не будет достигнут конец файла, при условии, что метод code() не вернет значение, сигнализирующее о возникновении ошибок в процессе обновления. В этом случае работа сценария будет прервана.

Если вы хотите использовать сценарий ImportLdapFile.pl для импортирования файла LDIF, подобного файлу, приведенному на Рисунке 1, я настоятельно рекомендую сначала проделать это в тестовом окружении. Никогда не стоит использовать технологию LDIF или любые другие средства для расширения схемы в реальном окружении, предварительно не протестировав расширения схемы. Для импортирования файла LDIF, изображенного на Рисунке 1, в свое окружение, необходимо изменить этот файл, и помимо прочего заменить атрибуты dc=rallencorp,dc=com уникального имени, атрибутами корневого домена своего окружения.

Вам не нужно изменять код в сценарии ImportLdapFile.pl, так как вся индивидуальная для системы информация указывается в командной строке. Например, следующая команда импортирует файл LDIF e C:myldif.ldf в схему на контроллере домена с именем dc01, если пользователь работает под учетной записью administrator с паролем xyz123:

perl ImportLdapFile.pl dc01 
administrator@rallencorp.com
xyz123 c:myldif.ldf

После корректного завершения работы сценария необходимо проверить изменения в схеме AD. Можно просмотреть добавленные или измененные класс и атрибуты в оснастке Schema Manager консоли Microsoft Management Console (MMC).

Основы и более сложные решения

Итак, мы изучили основы технологии LDIF и разобрались в том, как использовать модули Net::LDAP для автоматизации импортирования файлов LDIF при расширении схемы. Вооружившись этими знаниями, вы можете адаптировать сценарий ImportLdapFile.pl под решение собственных задач. Например, можно усложнить алгоритм верификации, добавив новые дополнительные условия проверки. Или же можно расширить возможности сценария с помощь таких средств, как оснастка Schema Manager или утилита Ldifde. В конечном счете, автоматизировав проверку схемы и процесс расширения, вы сможете получить более полное представление о расширении схемы, а, следовательно, добьетесь более эффективного использования вашими приложениями возможностей службы AD.


Листинг 1. Сценарий ImportLdapFile.pl.
use strict;
use Net::LDAP;
use Net::LDAP::LDIF;
$|++;
# Начало фрагмента A
unless ($ARGV[3]) {
print "Usage: $0
";
exit(0);
}
my ( $server, $user, $passwd, $ldif_file ) = @ARGV;
# Конец фрагмента A
# Начало фрагмента B
my $ldap = Net::LDAP->new($server) || die "Error: Could not
connect to server: $server ";
my $bind = $ldap->bind($user, password => $passwd);
die "Error: Could not bind to server: ", $bind->error if $bind->code;
my $rootdse = $ldap->search(base => '', filter => '(objectclass=*)',
scope => 'base', attrs => ['schemaNamingContext']);
my $schema_dn =
($rootdse->entries)[0]->get_value('schemaNamingContext');
print "Schema Container: $schema_dn ";
# Конец фрагмента B
# Начало фрагмента C
my $schema = $ldap->schema();
my $ldif = Net::LDAP::LDIF->new( $ldif_file, "r", onerror => 'undef' );
print "Verifying LDIF entries ";
my $ldf_err;
while( not $ldif->eof() ) {
my $schemaobj;
my $entry = $ldif->read_entry();
if ( $ldif->error() ) {
print " Error msg: ",$ldif->error()," ";
print " Error lines: ",$ldif->error_lines()," ";
$ldf_err++;
last;
}
$schemaobj = $ldap->search(base => $entry->dn,
filter => '(objectclass=*)', scope => 'base', attrs => ['cn']);
if ($entry->changetype eq 'add' and $schemaobj->entries > 0) {
print "Add entry already exists: ",$entry->dn," ";
$ldf_err++;
}
elsif ( ($entry->changetype eq 'delete' or $entry->changetype
eq 'modify') and $schemaobj->entries == 0) {
print "Modify or Delete entry does not exist: ",
$entry->dn," ";
$ldf_err++;
}
else {
print "Entry Ok: ", $entry->dn," ";
}
}
$ldif->done();
die " Aborting, errors found " if $ldf_err;
print "Verification complete ";
# Конец фрагмента C
# Начало фрагмента D
$ldif = Net::LDAP::LDIF->new( $ldif_file, "r", onerror => 'undef' );
print "Importing LDIF entries";
while( not $ldif->eof() ) {
my $entry = $ldif->read_entry();
if ( $ldif->error() ) {
print " Error msg: ",$ldif->error()," ";
print " Error lines: ",$ldif->error_lines()," ";
last;
}
else {
print ".";
my $res = $entry->update( $ldap );
if ($res->code) {
print " Error inserting entry: ", $res->error," ";
last;
}
}
}
print "done ";
$ldif->done();
# Конец фрагмента D

Листинг 2. Пример кода LDIF, изменяющего два атрибута одного и того же пользовательского объекта
dn: cn=rallen,cn=users,dc=rallencorp,dc=com
changetype: modify
replace: sn
sn: Allen
-
add: givenName
givenName: Robbie