LDAP: сложная служба каталогов
Службы LDAP (Lightweight Directory Access Protocol, облегченный протокол доступа к каталогам) и ADSI гораздо богаче и более сложны в обращении. В настоящее время существуют две популярные версии протокола LDAP (версия 2 и версия 3; при необходимости номер версии будет указываться). Этот протокол быстро стал промышленным стандартом для доступа к каталогам. Системные администраторы воспользовались протоколом LDAP, т. к. он предлагал способ централизовать и сделать доступной всю информацию об инфраструктуре. Помимо стандартного «каталога компании» существуют такие примеры приложений:
Кроме того, LDAP является базой для других сложных служб каталогов, подобных активным каталогам Microsoft (Microsoft Active Directory), о которых пойдет речь в разделе «ADSI (Интерфейсы служб активных каталогов)».
Даже в случае, когда LDAP применяется только для ведения «домашней» телефонной книги, существуют веские причины научиться использовать этот протокол. LDAP-серверы можно администрировать при помощи этого же протокола; что очень напоминает серверы баз
данных SQL, которые тоже можно администрировать средствами SQL. И в этом случае Perl предлагает отличные механизмы склейки для автоматизации задач администрирования LDAP. Но сначала нужно убедиться, что протокол LDAP разобран и понятен.
В приложении В «Десятиминутное руководство по LDAP» приводится краткое введение в LDAP для тех, кто не знаком с протоколом. Самая большая преграда, встающая перед системным администратором при изучении LDAP, - это неуклюжая терминология, унаследованная от
родительских протоколов службы каталогов Х.500. LDAP является упрощенной версией Х.500, но, к сожалению, терминология от этого легче не стала. Стоит потратить время на приложение В и изучение терминов - тогда вам будет проще понять, как использовать LDAP из Perl.
Программирование LDAP на Perl
Как и с многими другими задачами системного администрирования в Perl, первым делом при программировании LDAP следует выбрать нужный модуль. Хотя LDAP еще не самый сложный протокол, но это уже и не обычный текстовый протокол. В результате, создать что-нибудь, «говорящее» на LDAP, задача нетривиальная. К счастью, другие авторы уже сделали эту работу за нас: Грэм Бар (Graham Barr) написал модуль Net: : LDAP, а Лейф Хедстром (Leif Hedstrom) и Клейтон Донли (Clayton Donley) создали модуль Mozilla: :LDAP (также известный как PerLDAP). Отметим некоторые различия между этими двумя модулями (табл. 6.1).
Таблица 6.1. Сравнение двух LDAP-модулей
Возможность |
Net::LDAP |
Mozilla::LDAP (PerLDAP) |
Переносимость Зашифрован- ные SSL-сеансы Асинхронные операции |
Только Perl Да Да |
Требует Mozilla/Netscape LDAP C-SDK (исход- ный код свободно доступен). SDK компилиру- ется на многих вариантах Unix, NT и MacOS Да Только с не объектно-ориентированными API низкого уровня |
главе 10 «Безопасность и наблюдение за сетью». Для облегчения сравнения в большей части примеров из этого раздела приведен синтаксис обоих LDAP-модулей. Строка use modulename в тексте каждого примера подскажет, какой модуль используется на этот раз.
В демонстрационных целях мы почти равнозначно будем использовать коммерческий сервер Netscape 4.0 Directory Server и свободно распространяемый сервер OpenLDAP (они находятся на http://www.netscape.com и http:// www.openldap.org). В состав обоих серверов входят практически идентичные утилиты командной строки, которые можно использовать для прототипирования и проверки программ на Perl.
Первоначальное LDAP-соединение
Соединение с аутентификацией - это, обычно, первый шаг в любой клиент-серверной LDAP-транзакции. На «языке» LDAP это называется «связыванием с сервером» (binding to the server). B LDAPv2 требовалось связаться с сервером перед отправкой команд, в LDAPvS усло-
вия не такие жесткие.
Связывание с LDAP-сервером выполняется в контексте определенного отличительного имени (Distinguished name, DN), описанного как привязанное отличительное имя (bind DN) для данного сеанса. Такой контекст похож на регистрацию пользователя в многопользовательской системе. В многопользовательской системе текущее регистрационное имя (по большей части) определяет уровень доступа пользователя к данным в этой системе. В LDAP именно привязанное отличительное имя определяет, какие данные на LDAP-сервере доступны для просмотра и изменения. Существует также специальное корневое отличительное имя (root Distinguished Name), дабы не путать его с относительным (Relative Distinguished Name) именем, для которого не существует акронима. Корневое отличительное имя имеет полный контроль над всем деревом, что очень похоже на регистрацию с правами пользователя root в Unix или с правами пользователя Administrator
в
NT/2000. На некоторых серверах это имя называется manager DN.
Если клиент не предоставляет аутентификационной информации (например, DN-имя и пароль) во время связывания или вообще не связывается с сервером до оправки команд, это называется анонимной аутентификацией (anonymous authentication). Анонимно зарегистрированные клиенты обычно получают очень ограниченный доступ к данным на сервере.
В спецификации LDAPvS определяется два типа связывания: простой и SASL. В простом связывании для аутентификации используются обычные текстовые пароли. SASL (Simple Authentication and Security Layer, слой простой аутентификации и безопасности) - это расширенный интерфейс аутентификации, определенный в RFC2222, позволяющий авторам клиентов/серверов встраивать различные схемы аутентификации, подобные Kerberos и одноразовым паролям. Когда клиент соединяется с сервером, он запрашивает определенный механизм аутентификации. Если сервер его поддерживает, он начнет диалог, соответствующий такому механизму, для аутентификации клиента. Во время этого диалога клиент и сервер могут договориться об уровне безопасности (например, «весь трафик между нами будет зашифрован при помощи TLS»), применяемом после завершения аутентификации.
Некоторые серверы и клиенты LDAP добавляют еще один метод аутентификации к SASL и стандартному простому способу. Этот метод является побочным продуктом использования LDAP по зашифрованным SSL (Secure Socket Layer, уровень защищенных сокетов). Для установки этого канала серверы и клиенты LDAP обмениваются криптографическими сертификатами на основе открытого ключа так же, как веб-сервер и броузеры при работе по протоколу HTTPS. LDAP-серверу
можно дать указание использовать в качестве аутентификационной информации только надежный клиентский сертификат (trusted client's certificate). Из доступных Perl-модулей только PerLDAP предла-
гает LDAPS (зашифрованные SSL-соединения). В примерах, для того чтобы не слишком усложнять их, будем пользоваться только простой аутентификацией и незашифрованными соединениями.
Вот как выполняется простое соединение и его завершение средствами Perl:
use Mozilla::LDAP::Conn;
используем пустые $binddn и Spasswd анонимной связи
$с = new Mozilla: :LDAP: :Conn($server, Sport, SPinddn, Spasswd);
die "Невозможно соединиться с Sserver" unless $c;
$c->close(); или:
use Net::LDAP;
$c = Net:;LDAP->new($server1 port => Sport) ск
die " Невозможно соединиться с Sserver: $?\n" не передаем параметры для связи $c->bind($:ii"ddn, password -> Spasswd) or
die "Невозможно соединиться: $@\п";
В Mozilla: : LDAP: :Conn создание нового объекта соединения также связано с сервером. В Net: : LDAP этот процесс состоит из двух шагов. Для инициализации соединения без выполнения привязывания в Mozilla: : LDAP необходимо использовать функцию (ldap_init()) из не объектно-ориен-
тированного модуля Mozilla: :LDAP::API.
Приготовьтесь тщательно заключить в кавычки значения атрибутов
Небольшой совет перед тем, как перейти к дальнейшему программированию на Perl: если в относительном отличительном имени есть атрибут, значение которого содержит один из следующих символов: « + », «(пробел)», «, », « '», «>», «<» или «; », необходимо либо заключить значение в кавычки, либо экранировать эти символы обратным слэшем (\). Если значение содержит кавычки, их также нужно экранировать при помощи обратного слэша. Обратные слэши в значениях тоже экранируются обратными слэшами.
Если вы не будете аккуратны, то недостаток кавычек может сыграть с вами злую шутку.
Выполнение поиска в LDAP
Буква «D» в LDAP означает Directory (т. е. каталог), и наиболее распространенной операцией с каталогами является поиск. Для начала знакомства с LDAP неплохо выяснить, как искать информацию. Поиск в LDAP определяется такими понятиями:
Откуда начинать поиск
Это называется базовым относительным именем (base DN) или базой поиска (search base). Базовое DN-имя представляет собой всего лишь DN-имя элемента в дереве каталогов, от которого начинается поиск.
Где искать
Это называется пространством (scope) поиска. Пространство может быть трех видов: base (поиск только по базовому DN-имени), one (поиск по уровню, лежащему непосредственно под базовым DN-
именем, не включая само базовое DN-имя) или sub (поиск по базовому DN-имени и всему дереву, лежащему ниже).
Что искать
Это называется фильтрами поиска (search filter). О фильтрах и их определении мы поговорим очень скоро.
Что возвращать
С целью ускорения операций поиска можно выбрать, какие атрибуты должны возвращаться для каждого элемента, найденного при помощи фильтров поиска. Кроме того, можно запросить, чтобы возвращались только имена атрибутов, а не их значения. Это полезно, когда нужно узнать, у каких элементов есть данные атрибуты, но совсем не важно, что эти атрибуты содержат.
В Perl поиск выглядит примерно так (процесс соединения заменен многоточиями):
use Mozilla::LDAP::Conn;
Sentry = $c->search($basedn, Sscope, $fiiter)
die "Неуспешный поиск: ". $c->getErrorString()."\n" if $c->getErrorCode():
или:
use Net::LDAP;
Ssearchobj = $c->search(base => Sbasedn, scope => $scope.
filter => $filter);
die "Неуспешный поиск, номер ошибки #".
Ssearchobj->code() if Sseatchooj- >code();
Перед тем как перейти к полному примеру, поговорим о загадочном параметре $fliter. Простые фильтры поиска имеют следующий вид:
<attribute name> Comparison operator> ottribute value>
где <comparison operator> определяется в RFC2254 как один из операторов, перечисленных в табл. 6.2.
Таблица 6.2. Операторы сравнения LDAP
Оператор |
Значение |
= |
Точное совпадение значений. Может означать и частичное совпадение, если в определении <attnbute value> используется * (например cn=Tiin 0*). |
=* |
Соответствует всем элементам, у которых есть значения для атри- бута <attnbute name>, независимо от того, каковы эти значения. Если вместо <attnbute value> указать *, будет проверяться нали- чие именно этого атрибута в элементе (например, сп=* выберет эле- менты, у которых есть атрибуты сп). |
-= |
Приблизительное совпадение значений. |
>= |
Больше либо равно значению. |
<= |
Меньше либо равно значению. |
«приблизительное» зависит от сервера. Большинство серверов применяют алгоритм, первоначально используемый в soundex для определения совпадающих значений при поиске слов, которые «произносятся, как» заданное значение (в английском языке), но записываются иначе.
Другая конструкция, которая может конфликтовать с вашими знаниями Perl, - это оператор =. Помимо проверки точного совпадения значений (как строковых, так и численных), оператор = можно использовать вместе с символом * в виде префикса или суффикса в качестве символов подстановки, подобно тому как это происходит в командных интерпретаторах. Например, сл=а* получит все элементы, имена которых (common name) начинаются с буквы «а». Строка сп=*а* выполнит именно то, чего вы ждете, и найдет все элементы, в атрибуте ел которых есть буква «а».
Можно объединить в одну строку два или более простых фильтра
Ottribute пате>, <companson operator>, ottribute value> при помощи логических операторов, создав таким образом более сложный фильтр.
Он имеет следующий вид:
(<boolean operator> (<simple1>)
(<simple2>) (<simple3>) ... )
Те, кто знаком с LISP, без труда разберутся с подобным синтаксисом; всем остальным придется просто запомнить, что оператор, объединяющий простые формы поиска, записывается первым. Чтобы найти элементы, удовлетворяющие обоим критериям поиска А и В, нужно использовать запись (&(А)(В)). Для элементов, удовлетворяющих критериям А или В или С, следует применить (|(А)(В)(С)). Восклицательный знак отрицает указанный критерий: так, А и не В записывается следующим образом: (&(А)(!В)). Составные фильтры можно объединять друг с другом, чтобы создавать фильтры поиска произвольной сложности. Вот пример составного фильтра для поиска всех Финкель-
штейнов, работающих в Бостоне:
(&(sn=Finkelstein)(l=Boston))
Следующий фильтр ищет человека, чья фамилия либо Финкельштейн, либо Хайндс:
(|(sn=Finkelstein)(sn=Hinds))
Для поиска всех Финкелыптейнов, работающих не в Бостоне:
(&(sn=FinKelstein)((l=Boston)))
Для поиска всех Финкелыптейнов или Хайндсов, работающих не в Бостоне:
(&(|(sn=Finkelstein)(sn=Hinds))(!l=Boston))
Тот, кто захочет поэкспериментировать с алгоритмом soundex, может воспользоваться модулем Марка Милке (Mark Mielke) Text:: Soundex. LDAP:
Вот два примера программ, принимающих имя LDAP-сервера и фильтр и возвращающих результаты запроса:
use Mozilla::LDAP::Conn;
Sserver = $ARGV[0];
Sport = getservbyname("ldap","tcp") || "389"
Sbasean = "c=US";
Sscope = "sub";
$c = new Mozilla::LDAP::Conn($server, Sport, "",""); анонимное соединение die "Невозможно связаться с $server\n" unless $c;
Sentry = $c->search($basedn, Sscope, $ARGV[1]);
die "Ошибка поиска: ". Sc->getErrorString(),"\n" if Sc->getErrorCode()
обрабатываем полученные от search() значения while (Sentry) {
$entry->printLDIF();
Sentry = $c->nextEntry(); } $c->close();
use Net::LDAP;
use Net::LDAP::LDIF;
Sserver = $ARGV[0];
Sport = getservbyname("ldap","tcp") | "389";
Sbasedn = "c=US";
Sscope = "sub";
$c = new Net::LDAP($server, port=>$port) or
die "Невозможно соединиться с Sserver: $@\n"; $c->bind() or die "Unable to bind: $@\n"; анонимное соединение
Ssearchobj = $c->search(base => Sbasedn, scope => Sscope,
filter => $ARGV[1]);
die " Неуспешный поиск, номер ошибки и",Ssearchobj->code() if Ssearcnobj- >code();
ft обрабатываем полученные от search() значения if (Ssearchobj){
Sldif = new Net::LDAP::LDIF("-");
$ldif->write($searchobj->entries());
$ldif->done();
А вот отрывок из получаемых данных:
$ Idapsrch ldap.bigfoot.com '(sn=Pooh)'
dn: cn="bear pooh", mail=poohbear219(s>hotmail. com. c=US,o=hotmail. com
mail: poohbear219iahotmail.com
en: bear pooh
o: hotmail.com
givenname: bear
surname: pooh
Перед тем как улучшить этот пример, посмотрим на код, обрабатывающий результаты, полученные от search(). Это одно из тех мест, где модули отличаются моделью программирования. Оба примера возвращают одну и ту же информацию в формате LDIF (LDAP Data Interchange Format, формат обмена данными LDAP), о котором речь пойдет позже, но данные они получают совершенно разными способами.
Модель Mozilla: : LDAP остается справедливой для подпрограмм анализа поиска, описанных в спецификации С API в RFC1823. Если поиск был успешным, возвращается первый найденный элемент. Для просмотра результатов необходимо последовательно запросить следующие эле-
менты. Вывод содержимого каждого получаемого элемента выполняет метод printLDIF().
Модель программирования Net: : LDAP имеет больше сходства с определением протокола из RFC2251. Результаты поиска LDAP возвращаются в объекты сообщений. Для получения списка элементов из этих пакетов в предыдущем примере использовался метод entries(). Вывод
всех элементов вместе выполняет метод из смежного модуля Net:: LDAP: :LDIF. Для последовательного вывода всех элементов, как было с printLDIF() в первом примере, можно использовать похожий метод write(), но показанный выше вызов более эффективен.
Немного поработаем с предыдущим примером. Как уже отмечалось в данной главе, поиск можно выполнять быстрее, ограничив количество возвращаемых в результате атрибутов. С модулем Mozilla: : LDAP это настолько же просто, насколько просто добавить дополнительные параметры в вызов метода search():
use Mozilla::LDAP::Conn;
Sentry = $c->search($basedn,$scope,$ARGV[1],0,@attr):
Первый дополнительный параметр - это логический флаг, определяющий, будут ли значения атрибутов опущены в результатах поиска. Значение по умолчанию - ложь (0), т. к. в большинстве случаев нас интересуют не только имена атрибутов.
Следующий дополнительный параметр - это список имен возвращаемых атрибутов. Знатоки Perl заметят, что список внутри списка интерполируется, так что последняя строка эквивалентна строке (ее можно так и прочитать):
Sentry = $c->search($basedn,$scope,$ARGV[1],0,$attr[0],$aTtr[1].$attr[2]. ..):
Если мы изменим строку первоначального примера:
Sentry = $c->searcn($basedn.Sscope,$ARGV[1]);
на:
@attr = qw(mail);
Sentry = $c->search($basedn,Sscope,$ARGV[l],0,@attr);
то получим следующий результат, в котором для элемента будут показаны только атрибуты DN и mail:
dn: cn="bear pooh",mail=poohbear219@notmail.com,c=US,o=hotmail.com mail: poohbear219@hotmail.com
Изменения, которые необходимо внести, чтобы получить определенные атрибуты средствами Net: : LDAP, тоже не сложны:
use Net::LDAP;
# можно было бы добавить "typesonly => 1" для получения только
# типов атрибутов, как и в предыдущем случае для первого
# необязательного параметра
Ssearchobj = $c->search(base => Sbasedn, filter => $ARGV[1], attrs => \@attr);
Обратите внимание, что Net: : LDAP принимает ссылку на массив, а не сами значения массива, как в случае с Mozilla: : LDAP.
Представление элементов в Perl
Эти примеры программ могут вызвать ряд вопросов о представлении элементов и о работе с ними, — в частности, как сами элементы хранятся и обрабатываются в программе на Perl. Дополняя рассказ о поиске в LDAP, ответим на некоторые из них, хотя позже в разделе о добавле-
нии и изменении элементов подобные вопросы будут рассматриваться подробно.
Если Mozilla: : LDAP выполняет поиск и возвращает экземпляр объекта элемента, то можно обратиться к отдельным атрибутам этого элемента, применяя синтаксис, используемый в Perl при работе с хэшами списков. $entry->{attributenaiTie} - это
список
значений атрибута с таким именем. Я выделил слово «список», т. к. атрибуты даже с одним
значением хранятся в анонимном списке, на который ссылается этот ключ хэша. Для получения единственного значения атрибута необходимо использовать запись $entry->{aUr ннг.'л аге}->[0]. Некоторые методы модуля Mozilla: : LDAP: : Entry возвращают атрибуты элемента (табл. 6.3).
Таблица 6.3. Методы Mozilla::LDAP::Entry
Вызов метода |
Возвращает |
Sent ry- >exists( $att r name ) |
true, если элемент имеет атрибут с таким именем |
$entry->hasValue($attrname,$att rvalue) |
true, если элемент имеет названный атрибут с указанным значением |
$entry->matchValue($attrnamet $att rvalue |
Так же, как и предыдущий, только ищется соответствие регулярному выражению, определенному в качестве значения атрибута |
$entry->size($attrname) |
Количество значений этого атрибута (обычно 1, если только атрибут не обладает несколькими значениями) |
Из программы видно, что методы для доступа к атрибутам элементов в Net:: LDAP несколько отличаются. После проведения поиска все результаты инкапсулируются в один объект. Получить отдельные атрибуты каждого элемента из этого объекта можно, применив один из двух способов.
Во-первых, модуль может преобразовать все полученные элементы в одну большую структуру данных, доступную пользователям. Ssearchobj ->as_struct() возвращает структуру данных, представляющую собой хэш хэшей списков. Метод возвращает ссылку на хэш, ключами которого являются DN-имена полученных элементов. Значения ключей - это ссылки на анонимные хэши, ключами которых являются имена атрибутов. Ключам соответствуют ссылки на анонимные массивы, содержащие значения данных атрибутов (рис. 6.1).
Вывести первое значение атрибута сп для всех элементов из структуры данных позволяет такой код:
$searchstruct = $searchobj->as_struct; for (keys %$searchstruct){
print $searchstruet->{$._}{en}[0], "\r";
Можно также сначала использовать один из этих методов и выделить объекты для отдельных элементов из объекта, возвращаемого в результате поиска:
Рис. 6.1. Структура данных, возвращаемая методом as_struct()
возвращает указанный элемент
Sentry = $searchob]->entry($entrynum);
действует подобно shift() в Perl для списка элементов Sentry = $searchobj->shift_entry;
действет подобно рор() в Perl для списка элементов Sentry = $searchobj->pop_entry;
возвращает все элементы в виде списка ©entries = $searcnobj->entries;
После того как получен объект элемента, можно применить один из указанных методов (табл. 6.4).
Таблица 6.4. Методы элементов Net::LDAP
Вызов метода |
Возвращает |
$entry->get($attrname) $entry->attributes() |
Значение атрибута в указанном элементе Список имен атрибутов для этого элемента |
Svalue = $searchobj->entry(1)->get(cn)
Теперь, когда вы умеете получать доступ к отдельным атрибутам и значениям, возвращаемым в результате поиска, посмотрим, как поместить подобные данные в каталог сервера.
Добавление элементов при помощи LDIF
Перед тем как рассматривать общие методы добавления элементов в каталог LDAP, давайте вспомним о названии этой книги и рассмотрим технологию, полезную, в основном, системным администраторам и администраторам каталогов. Она использует формат данных, помогающий загрузить данные на сервер каталогов. Мы рассмотрим способы записи и чтения LDIF.
LDIF, определенный в
нескольких стандартах RFC предлагает простое текстовое представление для элементов каталогов. Вот простой пример LDIF из последнего чернового стандарта Гордона Гуда (Gordon Good):
version: 1
dn: cn=Barbara Jensen, ou=Product Development, ac=airius, dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
en: Barbara Jensen
en: Barbara J Jensen
en: Babs Jensen
sn: Jensen
uid: bjensen
telephonenumber: +1 408 555 1212
description: A big sailing fan.
dn: cn=Bjorn Jensen, ou=Accounting, dc=airius, dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
en: Bjorn Jensen
sn: Jensen
telephonenunber: +1 408 555 1212
Формат должен быть вам понятен. После номера версии LDIF перечислены DN-имена каждого элемента, определения objectclass и атрибуты. Разделителем элементов является пустая строка.
Наша первоочередная задача - научиться создавать файлы LDIF из существующих элементов каталогов. Кроме того что мы обеспечим себе данные для следующего раздела (в котором рассматривается чтение файлов LDIF), такая возможность позволит использовать LDIF-файлы
любым способом при помощи обычных операций Perl, работающих с текстом.
При обсуждении поиска в LDAP было показано, как вывести элементы в формате LDIF. Изменим код предыдущего примера так, чтобы он записывал данные в файл:
use Mozilla::LDAP::Conn: use Mozilla::LDAP::LDIF:
<выполняем связывание и поиск>
open(LDIF,">$LDIFfile1") or die ""Невозможно записать в SLDIFfile:$!\n": П создаем новый объект LDIF и передаем дескриптор Sldif = new Mozula: :LDAP: :LDIF(\*LDIF);
while (Sentry) (
$ldif->wr :reOneEntry($entry): Sentry = $c->nextEntry():
$c->close(); close(LDIF):
Модуль Mozilla: : LDAP располагает методом writeEntries(), позволяющим принять массив элементов и записать их подобным образом.
Используя Net: : LDAP, изменить первоначальную программу еще проще. Вместо:
$ldif = new Net::LDAP::LDIF("-"); применим:
Sldif = new Net::LDAP::LDIF($filename,"w");
для записи выводимых данных в указанный файл, а не на стандартный вывод.
Теперь совершим обратное действие и прочитаем файлы LDIF (вместо того, чтобы в них записывать). Методы объекта из модуля, о котором пойдет речь, позволяют легко добавить элементы в каталог.
При чтении LDIF- данных из Perl осуществляется процесс, обратный тому, который применялся в предыдущих примерах для записи. Каждый список элементов считывается и преобразуется в экземпляр объекта элемента, который затем передается соответствующему методу изменения каталога. Оба модуля считывают и анализируют данные, так что процесс довольно прост. Например, с использованием Mozilla I DAP можно написать такую программу:
use Mozilla::LDAP::Conn; use Mozilla::LDAP::LDIF;
Sserver = $ARGV[0];
SLDIFfile = $ARGV[1]
Sport = getservbynameC'ldap"."tcp") II "389"
Srootdn = "cn=Manager, ou=Systems, dc=ccs, dc-hogwarts, dc=edu"; $pw = "secret";
считываем файл LDIF, указанный втооым азгумо--о« в
и командной строке
open(LDIF,"SLDIFflie") or die "Невозможно отквыгь $LDIF*iie:$!\n";
Sldif = rew Mozilla::LOAP::LDIF(\*LDIF):
анализируем все элеменгь сохраняем их з 3ertri.es
Gentries = $ldif->readEnrnes():
close(LOIF):
tt неанонимное соединение
$c = new Mozilla::LDAP::Conn($server,$port.Srootdn,$pw);
die "Невозможно соединиться с $server\n" unless $c:
№ обходим в цикле список элементов, добавляя их на каждой итерации for (gentries)!
$c->add($_); ft добавляем этот элемент в каталог
warn "Ошибка при добавлении ". $_->getDN(),": ".$c->getErrorString()."\n"
if $c->getErrorCode(); } $c->close():
В этом примере отражено применение методов getErrorCodeO и getErrorString() для получения любых ошибок (и сообщения о них), происходящих в процессе загрузки данных. Ошибки могут появиться по целому ряду причин, включая дублирование DN/RDN-имен, нарушение схемы, проблемы с иерархией и т. д., так что очень важно проверить их при изменении элемента.
И еще одно замечание, перед тем как перейти к рассмотрению Net:: LDAP: в этом и последующих примерах в демонстрационных целях используется корневое DN-имя (manager DN). Обычно же, если можно избежать применения такого контекста в повседневной работе, это следует делать. Образец правильной настройки LDAP-сервера включает создание могущественной учетной записи или группы учетных записей (которые не являются корневым DN-именем) для управления каталогами. При создании собственных приложений не забывайте этот совет.
Для Net: : LDAP программа, добавляющая LDIF-элемент, выглядит таким образом:
use Net::LDAP;
use Net::LDAP::LDIF:
$server = SARGV[0]:
SLDIFfile = SARGV[1]:
Sport = getserveyfiaTiei. "Idap" 'tcu") || '389';
Srootdn = 'cn=Manager, ou=Systems. dc=ccs, dc="ogwarts. cc=ea^':
spw = ' secret ': П считываем файл LDIF. указанный вторым аргументом в 8 командной строк-з
» последний 'nipaw'-rp '>" для чтения, "w" для запис.' $ldif = пел Ne*' .'_CAP'-LDIF($LDIFfile. "r"): Gentries = $1(1:f >-^ad():
$с = new Net::LDAPfSserver, port => $por*) n<-
die "Невозможно соединиться с Sserver: $д'-п"
$c->bind(dn => $rootdn, password => $pw) or die л^'бка пр/ с-зязкаа-^:.- $@\n";
for (@entries)i
$res - $c->add($_):
warn "Ошибка при добавлении ". $_-^dri(). -.од ошибки ". $'ts->cudc?.
if $res->code(); }
$c->unbind();
Несколько замечаний к этому примеру:
gentries = new Net::LDAP::LOIF($LDIFflie."r")->read;
Ошибка при добавлении cn=Ursula Hampster, oiJ=Almnn.i Association.
ou=People,
o=University of Michigan, c=US: код ошибки 68
Если сервер возвращает текстовое сообщение, метод его г () получает его так же, как это было в примере с Moziila: : LDAP:
print "Сообщение об ошибке: ".$res->error."\п":
Безопаснее было бы проверить код возврата, как в предыдущем примере, поскольку серверы LDAP не всегда передают текстовые сообщения об ошибках в своих ответах. Если нужно преобразовать
десятичный код ошибки в сообщение об ошибке или в название сообщения, модуль Net; :LDAP: :UtiI предлагает для этого две подпрограммы.
Теперь нам известно, что мы пытались добавить элемент из файла LDIF, который уже существовал в каталоге.
Добавление элементов при помощи стандартных операций LDAP
На этот раз мы заглянем вглубь процесса добавления элементов, чтобы научиться создавать и заполнять элементы вручную, не считывая их из файла, как в прошлый раз. Два модуля обрабатывают этот процесс по-разному, поэтому работать с ними следует отдельно. Модуль Миilia:: LDAP ближе к классическому стилю объектно-ориентированного программирования. Создадим новый экземпляр объекта:
use Mozilla::LDAP::Entry;
$e = new Mozilla::LDAP::Entry()
и начнем его заполнять. Следующий шаг - дать элементу отличительное имя DN. Это можно сделать при помощи метода setDN():
$e->setDN("uid=jay,
ou=systems,
ou=people,
dc=ccs,
dc=hogwarts,
dc=edu");
Для заполнения других атрибутов, таких как objectClass, следует пойти по одному из двух путей. Можно сделать ряд предположений относительно структуры данных, используемой для представления элемента (по существу, это хэш списков), и заполнить ее напрямую:
$е->{сп} = ['Jay Sekora'];
В данном случае используется имя атрибута в качестве ключа хэша и ссылка на анонимный массив, хранящий данные. Модуль Mozilla: : LDAP ожидает, что значениями хэша будут ссылки на массив, а не сами данные, так что следующее, хоть и выглядит заманчиво, но будет неверным:
# воплощенное зло (или, по крайней мере, просто неверно)
$e->{cn} = 'Jay Sekora;
В качестве альтернативы можно действовать наверняка и применять метод объекта для добавления данных:
$e->addValue('en', 'Jay Sekora1);
Для добавления нескольких значений атрибуту нужно повторно вызывать метод addValue():
$e->addValue( 'title', 'Unix SysAdmin');
$e->addValue('title' 'Part-time Lecturer'):
Мне больше по душе второй подход, т. к. при его использовании менее вероятно, что программа перестанет работать, если в следующих версиях модуля изменится способ представления данных.
После того как элемент заполнен, можно вызвать метод ado() для внесения его в каталог. Вот маленький сценарий, который добавляет элемент в каталог. В качестве аргументов командной строки он принимает имя сервера, идентификатор пользователя (будет использоваться как часть отличительного имени) и общее имя:
use Mozilla::LDAP::Conn;
Sserver = $ARGV[0];
Sport = getservbyrianieC'ldap". "tcp") ii "389"
Ssuffix = "ou=People, ou=Systems, dc=ccs, dc=hogwarrs. ac=edu":
Srootdn = "cn=Manager, ou=Systems. dc=ccs. oc=hogwarrs, dc=edu":
$pw = "secret";
ft неанонимное соединение
$c = new Mozilla::LDAP::Conn($server,Sport,Srootdn,$pw):
die "Невозможно соединиться с $server\n" unless $c:
$e = new Mozilla::LDAP::Entry;
ft DN-имя - это идентификатор пользователя плюс суффикс,
ft определяющий, куда поместить его в дереве каталогов
$e->setDN("uid=$ARGV[1],Ssuffix");
$e->addValue('uid', $ARGV[1]);
$e->addValue('cn', $ARGV[2]);
$c->add($e);
die "Ошибка при добавлении: ". $c->getErrorString(),"\n" if $c-
>getErrorCode();
Обратите внимание, что в программе не выполняется проверка ошибок при вводе. Если вы пишете сценарий, который действительно может использоваться в интерактивном режиме, необходимо проверять вводимые данные, чтобы убедиться, что в них нет неэкранированных специальных символов, подобных запятым. Обратитесь к ранее приведенному «совету с совой» за разъяснениями о том, как заключать в кавычки значения атрибутов.
Теперь перейдем к Net: :LDAP. При желании процесс добавления элементов для Net: : LDAP может быть менее объектно-ориентированным. В него входят модуль Entry (Net:: LDAP: : Entry) и конструктор для экземпляра объекта элемента. Однако он содержит еще одну функцию add(), которая способна принимать структуру данных для добавления элемента за один шаг:
$res = $c->add(
dn => 'uid=jay, ou=systems, ou=people. dc=ccs, dc=hogwarts, dc=edi/ attr =>
[ 'en' => 'Jay Sekora', ' sn => 'Sekora', 'mail' => 'jayguy@ccs.hogwarts.edj', 'title' = -
[ 'Sysadmin'.' Part-time Lect-jrer' ]. 'uid' => 'jayguy' ]
На этот раз add() передается два аргумента. Первый - это DN-имя для элемента; второй - ссылка на анонимный массив пар атрибут-значение. Обратите внимание, что атрибуты с несколькими значениями, например title, определяются при помощи вложенного анонимного массива. Тем, кто привык работать со структурами данных в Perl и кому не нравится объектно-ориентированный стиль программирования, такой подход придется больше по душе.
Удаление элементов
Удаление элементов из каталога - это простое дело ( и необратимое, так что будьте осторожны). Вот отрывок программы, из которой, для краткости, снова удален код, реализующий соединение с сервером:
use Mozilla::LDAP::Conn;
П если у вас есть элемент, вы можете использовать
if $c->delete($entry->getDN()) $c->delete($dn) or
die "Невозможно удалить элемент: ". $c->getErrorString()."\n";
use Net::LOAP;
$res = $c->delete($dn);
die "Невозможно удалить, код ошибки #".
$res->code() if $res->code();
Важно обратить внимание на то, что в обоих модулях delete ()удаляет по одному элементу за один раз. Если необходимо убрать поддерево целиком, сначала следует найти все дочерние элементы этого поддерева, используя пространство sub или one, а затем обойти в цикле возвращаемые значения, удаляя элементы на каждой итерации. После того как уничтожены дочерние элементы, можно удалить вершину этого поддерева.
Изменение имен элементов
Последние операции с LDAP, которые мы рассмотрим, касаются двух типов изменений элементов LDAP. Первый тип - это изменение DN- и RDN-имен. Преобразовать RDN-имя элемента просто, и эта операция поддерживается обоими модулями. Вот версия для Mozilla: : LDAP:
use Mozilla::LDAP::Conn:
$c->modifyRDN($newRDN.SoldDN.$delold) or
die "Невозможно переименовать элемент'".
$c->getErrO''St":pg(). "\n".
В приведенном отрывке все должно быть понятно, за исключением параметра $delod метода Mjai;'yRDN(). Если он равен 0, то LDAP-библиотеки удалят из элементов значения, совпадающие с измененными RDN-именами. Например, если первично в RDN-имени элемента содержался атрибут ; (от «location», местоположение), но само RDN-имя было изменено, то старый атрибут 1 элемента будет удален и останется только новое значение.
Вот эквивалентный вариант для переименования элемента в Net: : LUA:
use Net::iDAP;
$res = $c->inoddn($oldDN
newrdn => SnewRDN.
deleteoldrdn => 1);
"Невозможно переименовать, код ошибки it".
$res->code() if $ies->oede()
В действительности метод moddn() модуля Net;: LDAP может гораздо больше, чем показано в предыдущем примере. До сих пор изменялось только RDN-имя элемента, в то время как местоположение элемента в иерархии дерева каталогов оставалось прежним. В LDAP версии 3 появилась более мощная операция для переименования, позволяющая произвольным образом менять местоположение элемента в дереве каталогов. Метод moddn(), вызванный с дополнительным параметром т„ superior, предоставляет доступ к такой возможности. Если добавить параметр таким образом:
Sresult = $c->nioddn($oldDN.
newrdn => SnewRDN,
deleteoldrdn => 1,
newsuperioi" => SparentDN);
die "Невозможно переименовать, код ошибки #".
$res->code() if $res->codef).
то элемент из SoldDN будет перенесен и станет дочерним элементом DN-имени, определенного в SparentDN. Гораздо эффективнее использовать этот метод, а не последовательность add() или delete(), как требовалось раньше, для перемещения элементов в дереве каталогов, но подобная возможность поддерживается не всеми LDAP-серверами. В любом случае, если вы скрупулезно проектируете структуру дерева каталогов, вам реже придется переносить элементы с места на место.
Изменение атрибутов элемента
Теперь перейдем к более распространенным операциям - изменению атрибутов и значений атрибутов элемента. В этом случае тоже существуют значительные различия между модулями mgziи not : :LOAP. Применяя Мо/ИЛа: : LDAP для изменения атрибута элемента.
необходимо использовать .один из методов, представленных в табл. 6.5.
Таблица 6.5. Методы изменения элементов в Mozilla::LDAP
Метод |
действие |
$entry->addValue($attrname, Sattrvalue) |
Добавляет указанное значение заданному атрибуту в указанном элементе. |
$entry-> removeValue($attrname! Sattrvalue) |
Удаляет указанное значение для заданного атрибута указанного элемента. Если это значение единственное для атрибута, то удаляется и весь атрибут. |
$entry-> setValue($attrname, $attrvalue1,...) |
Изменяет значения указанного атрибута в заданное значение или значения. |
$entry-> rerTTOve(Sattrname) |
Удаляет указанный атрибут (вместе со значениями) из элемента. |
После того как внесены все изменения элементов (при помощи перечисленных методов), нужно вызвать метод update() для данного LDAP-соединения, чтобы распространить эти изменения на сервер каталогов. update() вызывается со ссылкой на элемент в качестве аргумента (т. е. $cupdate($entry)).
Применим эти методы для глобального поиска и замены. Рассмотрим такой сценарий: один из отделов вашей компании переводят из Бостона в Индиану. Эта программа изменит все элементы, местоположением которых является Бостон:
use Mozilla::LDAP::Conn;
Sserver = $ARGV[0];
Sport = getservbyname("ldap","tcp") || "389";
Sbasedn = "dc=ccs,dc=hogwarts,dc=edu";
Sscope = "sub";
Srootdn = "cn=Manager, ou=Systems, dc=ccs, dc=hogwarts, dc=edu";
$pw = "secret";
№
неанонимное соединение
$c = new Mozilla;:LDAP::
Conn(Sserver,Sport.Srootdn,$pw);
die "Невозможно соединиться с сервером
$server\n" unless $c;
tt
обратите внимание, что мы запрашиваем как можно меньше
информации для ускорения поиска
Sentry = $c->search($Pasedn, Sscope, "(l=Boston)", 1, ");
die "Ошибка поиска;". $c->getErrorStnng().
"\n" if $c->gettrrorCode();
if ($entry){ . - -while(Sentry)!
$entry->removeVali;e("l". "Boston");
$entry->addValue("l", "Indiana");
$c->update($entry);
die 'Ошибка при обновлении:" .
$c->getErrorString() . "\n"
if $c-'getErrorCode(); Sentry = $c->nextEntry(); }; }
$c->close();
Для изменения элементов в Net: : LDAP применяется другой подход. В нем все только что рассмотренные методы модуля Mozilla:. LDAP объединены в одном «суперметоде» modify(). Параметры, передаваемые этому методу, и определяют его функциональность (табл. 6.6).
Таблица 6.6. Методы для изменения элементов в Net::LDAP
Параметр |
Действие |
add => {Sattrname => Sattrvalue} |
Добавляет указанный элемент с заданным значением. |
add => {Sattrname => [$attrvalue1, $attrvalue2. . . ]} |
Добавляет указанный атрибут с заданным набором значений. |
delete => {Sattrname => Sattrvalue} |
/i> I
Удаляет указанный атрибут с заданным значением.
delete => {Sattrname => []} delete => [Sattrnamel, $attrname2. . . ]
replace => {Sattrname => Sattrvalue}
Удаляет атрибут или набор атрибутов независимо от их значений.
Действует, как add, только заменяет текущее значение указанного атрибута. Если Sattrvalue является ссылкой на пустой анонимный список ([]), метод становится синонимом для приведенной выше операции удаления.
Можно объединять несколько таких параметров в одном и том же вызове modify, но это представляет собой потенциальную проблему. Когда modify вызывается с набором параметров, например, так:
$c->modify($dn, replace => {'!' => "Medfora"},
add => {'1' =N "Boston"). add => {'1 => "Cambridge"});
нет никаких гарантий, что указанные операции добавления будут выполняться после замены. Если необходимо, чтобы операции выполнялись в определенном порядке, можно применять синтаксис, подобный только что рассмотренному. Вместо использования набора дискретных параметров можно передать единственный массив, содержащий очередь команд. Вот как это работает: Tiod:fy() принимает параметр changes, значение которого- список. Данный список считается набором пар. Первая половина пары - это операция, которую необходимо выполнить, вторая половина - ссылка на анонимный массив, содержащий данные для этой операции. Например, если мы хотим гарантировать, что операции из предыдущего фрагмента кода выполнятся в нужном порядке, то можем написать:
$c->Tiodify($dn. changes =>
[ replace -;" ['!' => "Kedfrr'd"].
add --> ['!' => "Boston"],
add =>['!' => "Caubndge"]
]);
Внимательно посмотрите на пунктуацию: она отличается от других параметров, которые приводились раньше.
Учитывая информацию, передаваемую функции modify(), можно переписать для Net: : ЮАР предыдущую программу, меняющую Бостон на Индиану:
use Net::LDAP;
$server = $ARGV[0];
Sport = getservbynameC'ldap", "tcp") |j "389";
Sbasedn = "dc=ccs,dc=hogwarts,c!c=edu";
$scope = "sub";
Srootdn = "c!i=Manager, ou=Syste'ns, dc=ccs, dc-hogwarrs, do-edu".
$pw = "secret",
$c = new Net::LDAP($server, port => Sport) or
die "Невозможно соединиться с сервером Ssorver
$«>'\n": $c->bind(dn --> S'ootrin. password => $pw) or
die "Ошибка при соедимении; $@\n";
Ssearchob] = $c->search(base => Soasedn, fiiiei => "(l-Bosion)".
scope => $scope, attrs -s [''}.
typeso.il у => 1): dio "Ошибка поиска: ". Ssear-chonj->er! or
if (SsearchobJ){
(Sentries = $searcnopj->entries;
ОГ ( aPI't ' .OS ) {
Собираем все вместе
Теперь, когда нам известны все основные LDAP-функции, напишем несколько небольших сценариев для системного администрирования. Мы импортируем базу данных машин из главы 5 «Службы имен TCP/IP» на сервер LDAP и затем сгенерируем некую полезную информацию, основываясь на LDAP-запросах. Вот пара выдержек из этого простого файла (для того только, чтобы напомнить вам формат):
name: shimmer
address: 192.168.1.11
aliases: shim shimmy shimmydoodles
owner: David Davis
department: software
building: main
room: 909
manufacturer: Sun
model: UltraGO
name: bendir address: 192.168.1,3 aliases:
ben bendoodles owner: Cindy Coltrane department:
IT building: west room: 143
manufacturer: Apple model: 7500/100
Первое, что нужно сделать, приготовить сервер каталогов для приема этих данных. Мы будем использовать нестандартные атрибуты, так что придется обновить схему сервера. Различные серверы выполняют это по-разному. Например, сервер каталогов Netscape имеет симпатичную графическую консоль Directory Server Console для подобных изменений. Другие серверы требуют внесения изменений в текстовые конфигурационные файлы. Работая с OpenLDAP, можно использовать нечто подобное в файле, включенном основным конфигурационным файлом для определения собственных пользовательских классов объектов для машины:
objectclass machine requires-:
,-• п ullOrtS
a-:a;;es
building, room.
manufacturer, model
После того как сервер настроен нужным образом, можно подумать об импортировании данных. Один из вариантов - провести загрузку большой единой операцией с помощью LDIF. Если приведенный выше отрывок из базы данных напомнил вам о формате LDIF, значит, вы на правильном пути. Эта схожесть упрощает преобразование. Тем не менее, нужно остерегаться ловушек:
Продолжающиеся строки
В нашей базе данных нет элементов, значения которых занимали бы несколько строк, иначе следовало бы убедиться, что вывод удовлетворяет стандарту LDIF. Стандарт LDIF требует, чтобы все длинные строки начинались строго с одного пробела.
Разделители элементов
Между элементами в базе данных в качестве разделителя используется симпатичная последовательность -=-. Два разделителя строк (т. е. пустая строка) должны находиться между элементами LDIF, так что нужно будет удалить эту последовательность из вводимых данных.
Разделители атрибутов
В настоящее время в наших данных есть только один атрибут с несколькими значениями: aliases (псевдонимы). LDIF обрабатывает многозначные атрибуты, перечисляя каждое значение на отдельной строке. Если встретится несколько атрибутов, то понадобится специальный код, печатающий для каждого значения отдельную строку. Если бы не эта особенность, программа, преобразующая наш формат в LDIF, представляла бы собой одну строку кода на Perl.
Но даже и с этими ловушками программа преобразования на удивление проста:
Sdatafile = "database";
Srecordsep = "-=-\n";
Ssuffix = "ou=data, ou=systems, dc=ccs. dc=hogwarts. dc=edu";
Sobjectclass = «EOC;
objectclass: top
objectclass: machine
EOC
open(DATA, Sdatafile) or aie "Невозможно открыть Sdataf ile: $''.n";
Модули Perl не работают с зги», даже если в специфики-;'!'
while (<DATA>) {
ft выводим заголовок для каждого элемента if (/name:\s-(.-)/){
print "arr сп=$1, $suffix\n":
print Soijjoctclass;
print "en: $1\n":
next: I tt обрабатываем многозначный атрибут aliases
if (s/~aliases:\s*//){
@aliases = split:
foreach $name (@aliases){ print "aliases: $name\n";
}
next; }
ft обрабатываем конец разделителя записей if ($_ eq $recordsep){
print "\n";
next; }
ft в противном случае просто печатаем найденный атрибут print;
close(OATA);
Если выполнить эту программу, то она выведет файл LDIF,
выглядящий примерно так:
dn: cn=shimmer, ou=data, ou=systems, dc=ccs, dc=hogwarts. dc=edu
objectclass: top
objectclass: machine
en: shimmer
address: 192.168.1.11
aliases: shim
aliases: shimmy
aliases: shimmydoodles
owner: David Davis
department: software
building: main
room: 909
manufacturer: Sun
model: UltraGO
dn: cn=bendir, ou=data. Ob=systems, ac=ccs, dc=hogwarts, dc=edu
objectclass: top
objectclass: machine
en: bendir
address: 192.168. 1.3
aliases: ben aliases: bendoodles owner: Cindy Colt rant; department:
building1 west room: 143
manufacturer: Apple model: 7500/100
Имея этот LDIF-файл, можно применять одну из программ, распространяемых с сервером, для загрузки этих данных на сервер. Например, Idif2ldbm,
входящий в состав обоих серверов OpenLDAP и Netscape Directory Server, считывает LDIF-файл и напрямую импортирует его в формат сервера каталогов, избавляя от необходимости проходить через LDAP. Хотя эта программа используется только при неработающем сервере, она может обеспечить самый быстрый способ загрузки большого количества данных. Если нельзя остановить сервер, можно применить только что написанную на Perl программу для чтения LDIF-файлов и передать подобный файл на LDAP-сервер.
Добавим еще один способ: вот программа, в которой пропускается промежуточный шаг создания LDIF-файла, и наши данные напрямую импортируются на LDAP-сервер:
use Net::LDAP;
use Net::LDAP::Entry;
Sdatafile = "database";
Srecordsep = "-=-":
Sserver = $ARGV[0];
Sport = getservbynameC'ldap","tcp") || "389";
Ssuffix = "ou=data, ou=systems, dc=ccs, dc=hogwarts, dc=edu";
$rootdn = "cn=Manager. o=University of Michigan, c=US";
$pw = "secret";
$c = new Net::LDAP($server.port => Sport) or
die "Невозможно соединиться с сервером Sserver: $@\n";
$c->bind(dn => Srootdn. password => Spw) or die "Ошибка при соединен;',
open(DATA,Sdatafile) or die "Невозможно открыть $datafile:$!\n";
while (<DATA>) {
chomp;
в
начале новой записи создаем
if (,/"nare:\S'(. •)/'){
$d^="cn=$1. Ssbffix":
Sentry = new fiet: : LDAP 'E:"t'\.
$еп:г^,-аса( с *' . $i)
ne:xt i
$entry-vad<l( aliases'. [splitO]):
next }
$entry->add( "objectciass". [ "тор", "riacii'ie" ]):
$entry->dn($dn);
$res = $c->add($entry);
warn "Ошибка добавления для "
undef Sentry;
next; }
ft добавляем все остальные атрибуты
$entry->add(split(':\s*')); # считаем, что у атрибута только одно значение >
close(DATA); $c->unbind();
После того как данные были импортированы на сервер, можно приступить к довольно любопытным вещам. В следующих примерах мы будем поочередно обращаться к двум LDAP-модулям. Для краткости в каждом примере не будет повторяться заголовок, в котором устанавливаются конфигурационные переменные, и код для соединения с сервером.
Так что же можно сделать с данными, расположенными на сервере LDAP? Можно на лету создать файл узлов:
use Mozilla::LDAP;
Sentry = $c->search($basedn,'one','(objectclass=machirie) ,0.
'en','address','aliases'); die "Ошибка поиска:". $c->getErrorString()."\n" if $c->getErrorCode( ;.
if (SentryM
print "#\n\U host file - GENERATED BY $0\n
tt
DO NOT EDIT BY HAND!\n«\n"; while(Sentry)!
print $entry->{adflress}[0]."\t". $ег!!-у->{сп}[0]," ".
jdir(' ' .?{$e^rry->{a:iasesM V " i": $entry = Sc-'-nextEntr1, (): }; ) $o->close();
Вот что получается:
host file - GENERATED BY Idap2host.s
В DO NOT EDIT BY HAND
192.168.1.11 shimmer shim shimmy sriimmydoodles
192.168.1,3 bendir ben bendoodles
192.168.1.12 Sulawesi sula su-lee 192.168.1.55
sander sandy mickey mickeydoo
Можно найти имена всех машин, произведенных Apple:
use Net::LDAP;
Ssearchobj = $csearch(base => Sbasedn,
filter => "(manufacturer=Apple)", scope => 'one', attrs => ['en']);
die "Ошибка поиска: ".$searchobj->error()."\n" if ($searchobj->code());
if ($searchobj){
for ($searchobj->entries){ print $_->get('en'),"\n";
$c->unbind();
Вот и результат:
bendir Sulawesi
Можно сгенерировать список владельцев машин:
use Mozilla::LDAP;
Sentry = $c~>search($basedn,'one','(objectclass=machine)',0,
'en','owner'): die "Ошибка поиска:". $c->getErrorString()."\n" if $c->getErrorCode():
if ($entry){
while($entry){
push(@{$owners{Sentry->{owner}[0]}}. $er,try->{cn}[0]); Sentry = $c->nextEntry();
};
$c->close():
for (sort ke>s %owners){
ADSI (Интерфейсы служб активных каталогов) 241
Получилось так:
Alex Rollins: sadder
Cindy Coltrane: oenciir
David Davis: sfurrmer
Ellen Monk: Sulawesi
Заодно можно проверить, является ли владельцем машины пользователь с текущим идентификатором (псевдоаутентификация):
use Mozilla::LDAP::Conn; use Sys::Hostname;
$user = (getpwuid($<))[6];
Shostname = hostname;
$hostname =" s/"([".]+)\..«/$1/; # удаляем имя домена из имени узла
Sentry = $c->search("cn=$hostname,$suffix",'base',"(owner=$user)",1,''):
if ($entry){
print "Владелец ($user) зарегистрирован на машине $hostname.\n"; } else {
print "Suser не является владельцем этой машины ($hostname)\n."; } $c->close();
Эти отрывки должны показать, как можно использовать доступ к LDAP из Perl для системного администрирования, и вдохновить вас, на создание собственных программ. В следующем разделе эти идеи будут перенесены на новый уровень, что позволит нам увидеть целый интерфейс администрирования, построенный на основе LDAP.