ADSI (Интерфейсы служб активных каталогов)
В последнем разделе этой главы мы обсудим платформо-зависимый интерфейс службы каталогов, разработанный с учетом только что рассмотренного материала.
В Microsoft создали сложную службу каталогов, основанную на LDAP, под названием Active Directory для использования ее в сердцевине интерфейса администрирования Windows 2000. Служба Active Directory является репозиторием для всей важной конфигурационной информации (пользователи, группы, системные политики, поддержка установки программного обеспечения и т.д.), применяемой в сети манит с Windows 2000.
При разработке активных каталогов в Microsoft осознали, что для этой службы необходимо создать программный интерфейс более высокого уровня. Для этого был разработан интерфейс ADSI (Active Directory Service Interfaces, интерфейсы служб активных каталогов). Надо отдать должное разработчикам Microsoft, им удалось понять, что новый интерфейс нужно будет расширить и охватить такие области системного администрирования, как принтеры и службы NT. Подобный размах делает ADSI чрезвычайно полезным для тех, кто пишет сценарии, автоматизирующие выполнение задач системного администрирования. Перед тем как ознакомиться с его работой, приведем несколько основных концепций и терминов, которые нам понадобятся.
Основы ADSI
ADSI можно считать оболочкой вокруг произвольной службы каталогов, действующей в рамках ADSL В среде интерфейса есть провайдеры (providers),
которые представляют собой реализации ADSI для LDAP, WinNT4.0 и Novell Directory Service. Говоря на «языке» ADSI, каждая из этих служб каталогов (WinNT не является службой каталогов) и домены данных (data domains) называются
пространством имен (namespaces). ADSI предоставляет единый способ запроса и изменения данных, найденных в этих пространствах имен.
Чтобы понять ADSI, необходимо иметь представление об объектной модели компонентов COM (Component Object Model), на которой построен интерфейс ADSL О модели СОМ существует много книг, но следует остановится на таких ключевых понятиях:
Все, с чем работают в СОМ, - это объекты (objects).
Объекты имеют интерфейсы (interfaces), обеспечивающие набор
методов (methods), применяемых для взаимодействия с этими объектами. Из Perl можно использовать методы, предоставляемые или наследуемые от интерфейса под названием IDispatch. К счастью, большинство методов ADSI, предоставляемых интерфейсами ADSI и их производными (например, lADsUser, lADsComputer, lADsPrint-Queue), унаследованы от IDispatch.
Значения, инкапсулируемые объектом, запрашиваемые и изменяемые посредством этих методов, называются свойствами (properties). В этой главе будут рассматриваться два типа значений: свойства, определяемые интерфейсом (interface-definedproperties), и свойства, определяемые схемой (schema-defined properties). Иными словами, первые будут определяться как часть интерфейса, а вторые - в объекте схемы. Подробнее об этом говорится чуть ниже. Если в данной главе не будут явно упоминаться «свойства схемы», значит, подразумевать следует свойства интерфейса.
Все это относится к стандартным понятиям объектно-ориентированного программирования. Сложности начинаются, когда сталкиваются терминологии ADSI/COM и других объектно-ориентированных миров, подобных LDAP.
Например, в ADSI рассматривается два различных типа объектов: лист (leaf) и контейнер (container). Объект-лист содержит данные; объект-контейнер содержит другие объекты, т. е. является для них родительским (parent). В LDAP самыми точными определениями для этих терминов были бы «элемент» и «точка ветвления». С одной стороны, мы говорим об объектах со свойствами, а с другой - об элементах с атрибутами. Как разобраться с подобными разногласиями, если учесть, что оба названия определяют одни и те же данные?
Вот как можно это понимать: в действительности, сервер LDAP обеспечивает доступ к дереву элементов и связанных с ними атрибутов. Когда интерфейс ADSI используется вместо LDAP для получения элемента дерева, ADSI вытягивает элемент из сервера LDAP, заворачивает его в несколько слоев блестящей оберточной бумаги и передает вам в качестве СОМ-объекта. Для получения содержимого этой посылки следует применять нужные методы, которые теперь называются «свойствами». Если внести изменения в свойства данного объекта, можно вернуть объект ADSI, чтобы последний распаковал информацию и передал ее обратно в дерево LDAP.
Вполне разумным выглядит вопрос: « А почему бы не обратиться напрямую к серверу LDAP?» На этот вопрос есть два хороших ответа: если мы знаем, как использовать ADSI для работы с одним типом служб каталогов, то мы знаем, как работать со всеми ними (или, по крайней мере, с теми, которые имеют ADSI-провайдеры). Второй ответ будет дан чуть позже, когда станет ясно, как можно упростить программирование служб каталогов при помощи инкапсуляции ADSL
Необходимо ввести понятие AdsPaths, чтобы перейти к ADSI-програм-мированию в Perl. ADsPaths предоставляет нам уникальный способ ссылаться на объекты из любого пространства имен.
Вот как выглядят примеры ADsPath из документации ADSI SDK:
WinNT://MyDornain/MyServer/User
WinNT://MyDomain/JohnSmith,user
LDAP://ldapsvr/CN=TopHat,DC=DEV,DC=MSFT,DC=COM,[^Internet
LDAP://MyDomain.microsoft.com/CN=TopH,DC=DEV,DC=MSF[,DC=COM.0=Internet
Это не совпадение, что они похожи на URL, т. к. и URL и ADsPath служат одним и тем же целям. Они оба пытаются обеспечить недвусмысленный способ сослаться на данные, предоставляемые различными службами данных. В случае с AdsPath из LDAP используется синтаксис LDAP URL из RFC, упомянутых в приложении В (RFC2255).
Более подробно AdsPath будет рассматриваться при обсуждении двух уже упомянутых пространств имен - WinNT и LDAP. Но сначала разберемся, как, в общих чертах, ADSI используется из Perl.
Использование ADSI из Perl
Семейство модулей Win32::OLE, поддерживаемое Жаном Дюбуа (Jan Dubois) и Гурусами Сарати (Gurusamy Sarathy), предоставляет мост от Perl к ADSI (который построен на СОМ как часть OLE). После загрузки основного модуля он используется для запроса ADSI-объектов:
use Win32::OLE;
Sadsobj = Win32::OLE->GetObject($ADsPath) or
die "Невозможно получить объект для $ADsPath\n";
Win32: :OI_E->GetObject() принимает моникер (moniker) OLE (уникальный идентификатор объекта, в данном случае это ADsPath) и возвращает объект ADSL Этот вызов также обрабатывает процесс связывания (binding) с объектом, уже рассмотренный при обсуждении LDAP. По умолчанию связывание с объектом производится от имени пользователя, запускающего сценарий.
Этот совет может уберечь вас от испуга. Если выполнить эти две строчки кода в отладчике и изучить содержимое возвращаемой ссылки на объект, то можно увидеть нечто подобное:
DB<3> x Sadsobj
О Win32::OLE=HASH(Ox10feOd4)
empty hash
He волнуйтесь. Win32: :OLE использует все могущество связанных переменных (tied variables). Кажущаяся пустой структура данных, как по волшебству, передаст информацию из объекта, если верно к ней обратиться.
Для доступа к значениям свойств интерфейса объекта ADSI используется ссылка на хэш:
Инструменты ADSI
Для использования материала из этой главы необходимо установить ADSI хотя бы на одной машине в сети. Эта машина может служить (через DCOM) ADSI шлюзом для остальных машин. Посетите сайт Тоби Эверета (Toby Everett), ссылка на который приведена ниже, чтобы узнать подробнее, как настроить ADSI для работы с DCOM.
Любая машина с Windows 2000 имеет встроенный в операционную систему интерфейс ADSL Для всех остальных Win32-Ma-шин придется загрузить и установить бесплатный дистрибутив ADSI 2.5, находящийся в http://www.microsoft.com/adsi. По этой же ссылке вы найдете документацию по ADSI, включая ad-si25.chm,
сжатую помощь в формате HTML, содержащую лучшую доступную документацию по ADSL
Даже если вы работаете с Windows 2000, я советую загрузить ADSI SDK с сайта Microsoft по указанной ссылке, поскольку в него входит эта документация и удобный броузер объектов ADSI под названием AdsVW. SDK поставляется с примерами программирования ADSI на нескольких языках, включая Perl. К сожалению, примеры из текущего дистрибутива ADSI полагаются на устаревший модуль OLE.pm, так что, в лучшем случае, вы сможете получить несколько советов, но не надо использовать эти примеры в качестве стартовой точки.
Перед тем как начать писать программы, стоит загрузить броузер объектов ADSI Тоби Эверета (написанный на Perl) с http:// opensource.activestate.com/authors/tobyeverett. Он научит вас перемещаться по пространствам имен ADSL Обязательно посетите этот сайт, начиная карьеру программиста ADSI, поскольку он является одним из лучших доступных сайтов по применению ADSI из Perl.
$value = $adsobj->{key}
Например, если этот объект имеет свойство Name, определенное как часть его интерфейса (а так и есть), вы можете применить:
print $adsobj->{Name}."\п";
При помощи такой же записи можно присваивать значения свойствам интерфейсов:
$adsob]->{FullNanie}= "Oog":
Свойства объекта ADSI хранятся в кэше (называемом кэшем свойств (property cache)). Первый запрос к свойствам объекта заполняет дан-
ный кэш. Последующие запросы к тем же свойствам позволяют получить информацию из этого кэша, а не из службы каталогов. Если вы хотите вручную заполнить кэш, можно вызвать методы Getlnf"() или GetInfoEx() (расширенная версия Getlr.foо) для данного экземпляра объекта, применяя синтаксис, который скоро будет рассмотрен.
Из-за того что первое считывание информации происходит автоматически, методы GetlnfoO и GetInfoEx() часто остаются незамеченными. Существуют ситуации, когда эти методы следует употреблять, хотя в книге такие случаи рассматриваться не будут. Вот две подобные ситуации:
Некоторые свойства объектов можно получить, только явно вызвав GetInfoEx(). LDAP-провайдер Microsoft Exchange 5.5 представляет собой самый характерный пример, поскольку многие из его свойств не доступны, если не вызвать сначала GetInfoEx(). Детальную информацию об этой несовместимости можно найти на http://ope.nso-urce.activestate.com/authors/tobyeverett.
Если несколько человек имеют право изменять в каталоге данные, то вызванный вами объект может быть кем-то преобразован, пока вы с ним работаете. Если это произойдет, данные в кэше свойств этого объекта устареют. Getlnfо() и GetInfoEx() обновят этот кэш.
Для обновления службы каталогов и источников данных, предоставляемых через ADSI, после изменения объекта нужно вызвать специальный метод Setlnfo(). SetlnfoO сбрасывает изменения из кэша свойств в службу каталогов и источники данных. (Это должно напомнить вам о необходимости вызывать метод upoate() в Mozilla: :LDAP. В данном случае идея та же.)
Вызывать методы экземпляра объекта ADSI не сложно:
$adsobj->Method($argijments. . .)
Поэтому, если бы мы изменили свойства объекта, как это предлагалось сделать в предыдущем предупреждении, то могли бы использовать такую строку сразу же после кода, вносящего изменения:
$adsob]->Set!nfo():
В результате данные из кэша свойств помещаются обратно в службу каталогов или источник данных.
Он возвращает ошибку, полученную в результате последней операции OLE. Применение ключа -и> с Perl (т. е. peri ~w script) также
приводит к подробным сообщениям о неудачных попытках OLE-операций. Зачастую эти сообщения об ошибках - единственная помощь, которая вам доступна, так что попытайтесь с толком ее использовать.
ADSI-код, который до сих пор рассматривался, выглядел как обычный код на Perl, поскольку внешне они похожи. Теперь перейдем к бо лее сложным вопросам.
Работа с объектами контейнер/коллекция
Ранее в этом разделе уже упоминались два типа объектов ADSI: лист и контейнер. Объект-лист представляет собой только данные, тогда как контейнер (известный еще как коллекция - в терминах OLE/COM) содержит другие объекты. Еще одно отличие двух типов объектов в контексте ADSI состоит в том, что объект-лист не имеет дочерних объектов в иерархии, а у контейнеров такие объекты есть.
Объекты-контейнеры требуют специальной обработки, т. к. в большинстве случаев нас интересуют данные, инкапсулированные их дочерними объектами. Существует два способа обратиться к таким объектам из Perl. Win32: :OLE имеет специальную функцию под названием in(), которая недоступна по умолчанию, если модуль загружается стандартным способом. Если необходимо получить к ней доступ, надо в начале программы использовать следующее:
use Win32::OLE 'in';
in() возвращает список ссылок на дочерние объекты, хранящиеся в этом контейнере. Это позволяет писать легко читаемые программы на Perl:
foreacn Schiid (in $adsobj){
print $child->!Name}
Другой путь заключается в том, чтобы загрузить один из полезных потомков
Win32: :OLF под названием Win32: :OLE: :Enum. Win32: :0! F.' : f.-",:'1 ->new()
создает объект-перечислитель из какого-либо объекта-контейнера:
use Win32 : : OLE: : Enuti:
$onobj = Win32: :OLE: : Enurr->new($adsobj);
Для этого объекта можно вызвать несколько методов и получить дочерние объекты $adscoj. Подобный подход должен напомнить вам способ, применяемый в операциях поиска с Moziila: : LOAP; процесс тот же самый.
Идентификация объекта-контейнера
Заранее нельзя узнать, является ли объект контейнером. Не существует способа из Perl «спросить» объект, не контейнер ли он. Максимум, что можно сделать, - попытаться создать объект-перечислитель и, если эта попытка не удастся, фиксировать данный результат. Вот короткий пример, который делает именно это:
use Win32::OLE;
use Win32::OLE::Enum:
eval {$enobj = Win32::OLE::Enum->new($adsobj)};
print "Объект " . ($@ ? "не " : "") . "является контейнером \п";
Второй способ - посмотреть на другие источники, описывающие этот объект. Все это плавно перетекает в третью сложность.
Как же узнать что-нибудь об объекте?
До сих пор мы избегали одного большого и, возможно, самого важного вопроса. Скоро нам придется работать с объектами из двух пространств имен. Уже понятно, как получить и установить свойства объектов и как вызвать методы для этих объектов, но все справедливо только в случае, если известны названия этих свойств и методов. Откуда берутся эти названия? Как их можно найти?
Нет единого места, в котором можно найти ответы на эти вопросы, но существует несколько источников, из которых можно почерпнуть нужную информацию для формирования практически всей картины. Первое место - это документация по ADSI, особенно та помощь, о которой говорилось во врезке «Инструменты для ADSI». В этом файле содержится огромное количество материала. Для ответа на наш вопрос о названиях свойств и методов нужно начать с Active Directory Service Interfaces 2.5—>ADSI Reference—>ADSI System Providers.
Иногда имена методов можно найти только в документации, но существует другой, более интересный подход для поиска названий свойств. Можно использовать метаданные, предоставляемые самим ADSI. Именно здесь на сцену выходят свойства схемы, о которых говорилось раньше.
Каждый объект ADSI имеет свойство под названием Schema, которое связывает ADsPath с его объектом схемы. В частности, следующий пример:
use Win32::OLE:
SADsPath = "WinNT://BEESKNEES. computer": Sadsob] = Win32::OLE->GetObject($ADsPath) or
die "Невозможно получить объект для $ADsPatn\n";
print "Это обьекг ". $adsob]->{Class}. . схема находится r.vi".
$adsobj->{Schema}."\n"; выведет:
Это объект Computer, схема находится в:
WinNT://Do[r.ainName/Schorra/Co""p .
Значение $adsobj->{Schema} - это путь ADsPath к объекту, описывающему схему для объектов класса Computer в этом домене. Здесь мы используем термин «схема» в том же смысле, что и в разговоре про схемы LDAP. В LDAP схемы определяют, какие атрибуты могут и должны присутствовать в элементах определенных классов объектов. В ADSI схема содержит ту же информацию об объектах определенного класса и их свойства схемы.
При желании посмотреть на возможные имена атрибутов объекта следовало бы взглянуть на значения двух свойств объекта схемы: MauJatoryProperties и OptionalProperties. Изменим предыдущий оператор
print:
$schmobj = Win32::OLE->GetObject($adsobj->{Schema}) or
die "Невозможно получить объект для $ADsPath\n";
print join("\n",@{$schmob]->{MandatoryProperties}},
@{$schmob]->{OptionalProperties}}),"\n";
Тогда получится:
Owner
Division
OperatingSystem
OperatingSystemVersion
Processor
ProcessorCount
Теперь известны возможные имена свойств схемы в пространстве имен WinNT для объектов Computer. Отлично.
Свойства схемы получаются и устанавливаются несколько иначе, чем свойства интерфейсов. Свойства интерфейсов обрабатываются примерно так:
получение и установка свойств ИНТЕРФЕЙСОВ
lvalue = $obj->{property); $ob]->{property} = SvaTje;
Свойства схемы получаются и устанавливаются при помощи специальных методов:
# получение и установка свойств СХЕМЫ Svalue = $obj->Get("property"); $obj->Put("property" "value"):
Все, что касается свойств интерфейсов, о чем говорилось до сих пор, остается справедливым и для свойств схемы (т. е. кэш свойств, и т. д.). Помимо необходимости применения специальных методов для получения и установки значений, единственное, что отличает данные свойства, - это их имена. Иногда один и тот же объект может иметь два различных имени для одного и того же свойства, одно для свойств интерфейса, другое для свойств схемы. Например, два этих свойства получают основные настройки для пользователя:
$1еп = $userot)j-><PasswordMininoniLeiigir}: я свойство :ih :ерое/са
$len = $userobj->Get("MinPasswordLength"); и то же самое свойство с>,о:-ь
Наличие двух типов свойств обусловлено тем, что свойства интерфейса существуют в виде части модели СОМ. Разработчики, определяя интерфейс при создании программы, также определяют свойства интерфейса. Позже, если они хотят расширить набор свойств, им приходится изменять и СОМ-интерфейс, и любой код, использующий этот интерфейс. В ADSI разработчики могут изменить свойства схемы в провайдере без необходимости изменять лежащий в основе СОМ интерфейс этого провайдера. Очень важно разобраться с обоими типами свойств, т. к. иногда некоторые данные объекта доступны через свойства только одного типа.
На практике, если вы ищете только названия свойств интерфейса или схемы и не собираетесь писать программы для их поиска, я рекомендую использовать ADSI-броузер Тоби Эверета, о котором я упоминал ранее. Вот пример этого броузера в действии.
Как альтернативный вариант упомянем программу ADSIDump из каталога General примеров SDK, которая может вывести содержимое всего дерева ADSL
Поиск
Эта последняя сложность, которую следует обсудить, перед тем как двигаться дальше. В разделе «LDAP: сложная служба каталогов» мы провели достаточно времени в разговорах о поиске в LDAP. Но в мире ADSI мы вряд ли услышим хоть слово по этому поводу. Все из-за того, что в Perl (и любом другом языке, в котором используется тот же OLE-интерфейс автоматизации) поиск с ADSI очень сложен; более того, поиск поддеревьев и поиск, в котором используются не самые простые фильтры, мучительно сложен. (Все остальное не так плохо.) Сложный поиск проблематичен, т. к. для его выполнения необходимо выйти за пределы ADSI и использовать совершенно иную методологию для получения данных (не говоря уже о том, что придется выучить новые акроним от Microsoft).
Но тот, кто занимается системным администрированием, привык смеяться над сложностями, так что начнем с простого поиска, а потом пе рейдем к более сложным вопросам. Простой поиск, затрагивающий один объект (пространство base) или его непосредственных потомков
(пространство one), можно выполнить вручную при помощи Perl. Сделать это можно так:
Для одного объекта получите нужные свойства и используйте обычные операторы сравнения для определения соответствия:
Для поиска дочерних объектов примените технологии доступа к контейнерам, о которых говорилось раньше, а затем изучите каждый дочерний объект. Несколько примеров поиска такого типа будут рассмотрены очень скоро.
Для того чтобы выполнить более сложный поиск, затрагивающий, скажем, все дерево каталогов или поддерево, вам придется переключиться на использование другой технологии «промежуточного уровня» под названием ADO (ActiveX Data Objects, объекты данных ActiveX). ADO предоставляет языкам сценариев интерфейс к базам уровня Microsoft OLE DB. OLE DB обеспечивает общий интерфейс, ориентированный на базы данных, к источникам данных, подобным реляционным базам данных и службам каталогов. В нашем случае ADO будет применяться для «разговора» с ADSI (который, в свою очередь, общается с самой службой каталогов). Поскольку ADO - это методология, ориентированная на базы данных, рассматриваемая программа предваряет материал об ODBC, о котором речь пойдет в главе 7.
ADO работает только с провайдером LDAP ADSI. В пространстве имен WinNT она работать не будет.
ADO - это отдельная тема, которая лишь затрагивает службы каталогов, поэтому будет рассмотрен только один пример с короткими пояснениями, остальные примеры работают с ADSL Дополнительную информацию об ADO можно найти на http://www.microsoft.com/ado.
Вот пример программы, выводящей имена всех групп, найденных в данном домене. Детальное обсуждение программы приведено ниже.
use Win32::OLE 'in';
# получаем объект ADO, устанавливаем провайдер, открываем соединение
$с = Win32::OLE->new("ADODB.Connection");
$c->{Provider} = "ADsDSOObject";
$c->OpenC'ADSI Provider");
die Win32::OLE->LastError() if Win32::OLE->LastError();
№
подготавливаем и выполняем запрос
$ADsPath = "LDAP://ldapserver/dc=example,dc=com";
$rs = $c->Execute("<$ADsPath>;(objectClass=Group);Name;SubTree");
die Win32::OLE->LastError() if Win32::OLE->LastError();
until ($rs->EOF){
print $rs->Fields(0)->{Value},"\n"; $rs->MoveNext;
$rs->Close; $c->Close;
Блок кода после загрузки модуля получает экземпляр объекта ADO Connection, устанавливает имя провайдера для этого экземпляра объекта, а затем просит его открыть соединение. Соединение открывается от имени пользователя, запускающего сценарий, хотя можно было установить другие свойства объекта, позволяющие изменить такое поведение.
Затем выполняется собственно поиск при помощи Execute(). Поиск можно осуществлять средствами одного из двух «диалектов»: SQL или ADSL Диалект ADSI, как видно из программы, использует командную строку, состоящую из четырех аргументов, каждый из которых разделен точкой с запятой.2 Вот эти аргументы:
ADsPath (в угловых скобках), определяющий сервер и базовое DN-имя для поиска.
Фильтр поиска (применяется тот же синтаксис LDAP-фильтров, что упоминался раньше).
Имя или имена (разделенные запятыми) возвращаемых свойств.
Пространство поиска: либо Base, либо OneLevel, либо SubTree (в соответствии со стандартом LDAP).
Execute() возвращает ссылку на первый из объектов ADO RecordSe, получаемых в результате запроса. По очереди запрашивается каждый из объектов RecordSet, распаковываются объекты, которые в нем содержатся, и выводится свойство Value, возвращаемое методом Fields() для каждого из этих объектов. Свойство Value содержит значение, которое запрашивалось в командной строке (имя объекта Group). Вот как выглядит отрывок получаемых данных на машине с Windows 2000:
Administrators
Users
Guests
Backup Operators
Replicator
Server Operators
Account Operators
Print Operators
DHCP Users
DHCP Administrators
Domain Computers
Domain Controllers
Schema Admirs
Enterprise Admins
Cert Publishers
Domain Admins
Domain Users
Domain Guests Group Policy Admins RAS and IAS Serveis DnsAdmins DnsUpdateProxy
Выполнение распространенных задач при помощи пространства имен WinNT и LDAP
Теперь, когда мы разобрались со «списком» сложностей, можно перейти к выполнению некоторых распространенных задач системного администрирования, используя ADSI из Perl. Цель - дать понять, ка кие задачи можно решать при помощи представленной информации об ADSL Затем рассмотреть и использовать код, который пригоден для написания собственных программ.
Для этих целей будет использоваться одно из двух пространств имен. Первое пространство - WinNT, которое предоставляет доступ к объектам Windows NT 4.0, таким как пользователи, группы, принтеры, службы и т. д.
Второе - это наш старый знакомый - LDAP. LDAP мы выбираем про вайдером при переходе к Windows 2000 и ее службе Active Directory, основанной на LDAP. Большинство объектов WinNT также доступны через LDAP. Ведь даже в Windows 2000 существуют задачи, которые можно выполнить, только используя пространство имен WinNT (например, создание учетных записей на локальной машине).
Программы, работающие с этими различными пространствами имен, похожи друг на друга (в конце концов, частично в этом и заключается смысл применения ADSI), но необходимо обратить внимание на два важных различия. Во-первых, формат ADsPath немного отличается. В соответствии с ADSI SDK, ADsPath в WinNT может иметь следующий вид:
WinNT: [//DomainNarre[/Cor;puterName[/'Ou;iectNa!T!e[. classNarne] ]]]
WinNT: [//DomainName[/ObjectNa.ne[, CjassNane]]]
WinNT:[//ComputerName,computer] WinNT:
ADsPath в LDAP выглядит так:
LDAP://HostName[: PortNumberj[/DisTinguisneaNar;e]
Обратите внимание, что при работе с NT 4 ADsPath в LDAP требует указывать имя сервера (в Windows 2000 это изменилось). Это означает, что пространство имен LDAP нельзя просмотреть с верхнего уровня, как пространство WinNT, т. к. необходимо указать начальный сервер. В пространстве имен WinNT любой может применить ADsPath или просто Wr'NT. для начала поиска в иерархии доменов.
Также обратите внимание, что свойства объектов в двух пространствах имен похожи, но не идентичны. Например, можно обратиться к одним и тем же объектам из обоих пространств имен WinNT и LDAP, но обратиться к некоторым свойствам Active Directory конкретного объекта пользователя можно только через пространство имен LDAP.
Особенно важно заметить различия между схемами в этих двух пространствах имен. Например, класс Use для WinNT не имеет обязательных свойств, тогда как класс User в LDAP требует наличия свойств samAccountNarne в каждом объекте пользователя.
Не забывая об этих различиях, посмотрим на сам код. В целях экономии места пропустим большую часть проверок ошибок, но рекомендуется запустить сценарий с ключом -w и добавить в текст программы примерно такие строки:
die "Ошибка OLE: ".Win32: :OLE->LastError()
if Win32: :OLE->Lar,tError():
Работа с пользователями через ADSI
Для получения списка пользователей домена применяется следующее:
use Win32::OLE 'in':
SADsPath = "WinNT://DomainName/PDCName,computer":
$c = Win32::OLE->GetObject($ADsPath) or
die "Невозможно получить $ADsPath\n":
foreach $adsobj (in $c){
print $adsobj->{Nane}."\n" if ($adsobj->{Class} eq "User"): }
Для создания нового пользователя и установки его полного имени (свойство Full Name):
use Win32::OLE;
$ADsPatn="WinNT://Domain Name/ComputerName,computer":
$c = Win32::OLE->GetOaject($ADsPatn) or
die "Невозможно получить SADsPath г":
tt создаем и возвращаем объект User $u = $c->Create("jser".$usernaTie);
Su->SetInfc(): tt н,жно создать лолазона^еля. чеойд тем как меня~^ з^а-е-/-
Name тедогус^.'м $u->{Fjll'Janei = З'Л'.г.;: ,;.-. $bj->3eil';fo()
Если Cciroutf rfJri! r - это первичный контроллер домена (Primary Domain Controller), то мы создали пользователя домена. Если нет, этот пользователь будет локальным для данной машины.
Эквивалентная программа создания глобального пользователя (при помощи LDAP нельзя создавать локальных пользователей) в Active Directory выглядит так:
use Win32::OLE;
SADsPath = "LDAP://ldapserver,CN=Users,dc=example.dc=coro";
$c = Win32: :OLE->GetObject($ADsPath) or die "Невозможно получить SADsPafAr"
# создаем и возвращаем объект User $u=$c->Create("user","cn=".Scommonname);
Su->{samAccountName} = Susername;
# нужно сначала создать пользователя в каталоге, а потом менять значения $u->Set!nfo();
# пробел между "Full" и "Name" требуется при работе с пространством имен LDAP:
$u->{'Full Name'} = $fullname; $u->Set!nfo();
Для удаления пользователя нужно внести лишь небольшие изменения:
use Win32::OLE;
SADsPath = "WinNT://DomainName/ComputerName,computer";
$c = Win32::OLE->GetObject($ADsPath) or die "Невозможно получить $ADsPath\n";
# удаляем обьект User, заметьте, мы в границах контейнера
$c->Delete("user",Susername);
$u->Set!nfo();
Изменить пароль пользователя можно при помощи единственного метода:
use Win32::OLE;
SADsPath = "WinNT://DomainName/ComputerName/".Susername;
$u = Win32::OLE->GetObject($ADsPath) or
die "Невозможно получить $ADsPath\n":
$u->ChangePasssword($oldpassword,Snewpassword): $u->Set!nfo();
Работа с группами через ADSI
Для перечисления доступных групп достаточно лишь немного подправить программу, выводящую список пользователей. Меняется только такая строка:
print $adsobj->{Name},"\n"
if ($adsobj->{Class) eq "Group"):
Создание и удаление групп выполняется при помощи тех же методов CreateO и Oelete(), которые применялись для создания и удаления учетных записей. Единственное различие - первый аргумент нужно изменить на «group». Вот так:
$д = $с->Сгеа Sgro.ip- ary):
Для добавления пользователя в группу (определяемую при помощи GroupName) после ее создания используется следующее:
use Win32::OLE:
SADsPath = "WinNT://DomainNane ''GroupNcine gro,,c"
$g = Win32: :OLE->GetOuject($ADsPatii) or
Здесь действуют те же правила относительно локальных пользователей и пользователей домена (глобальных), которые мы рассмотрели выше. Для того чтобы добавить пользователя домена в группу, $ийо<"-ADsPath должна указывать на пользователя на PDC для этого домена.
Для удаления пользователя из группы применяйте:
$c->Reniove($userADsPath);
Работа с разделяемыми ресурсами через ADSI
Теперь займемся более интересными задачами ADSI, адресованными посвященным. Можно применять ADSI, чтобы предоставить к совместное пользование часть локального дискового пространства на машине:
use Win32::OLE;
SADsPath = "WinNT://ComputerName/1 anira'iserver":
$c = Win32: :ClEI->GotObjecT
$s = $c->Create("filesnare".Sshareramo): $s->{path} = 'C:\directory1:
Перед тем как перейти к другим задачам, хочу воспользоваться случаем и напомнить вам о необходимости обратиться к документации SDK перед работой с каким-либо из этих ADSI-объектов. Кое-какие не<>;ки-данности могут оказаться полезными. Если вы заглянете и раздел Acti
ve Directory Service Interfaces 2.5—>ADSI Reference—> ADSI Interfaces—> Persistent Object Interfaces—> lADsFileShare
файла помощи ADSI 2.5, то увидите, что объект f liesnare имеет свойство CurrentUser-Count, которое соответствует количеству пользователей, подсоединенных в настоящее время к разделяемому ресурсу. Этот нюанс может очень сильно пригодиться.
Работа с очередями и заданиями печати через ADSI
Вот как можно определить названия очередей на определенном сервере и модели принтеров, используемых для обслуживания этих очередей:
use Win32::OLE 'in';
$ADsPatn="WinNT://DomainName/PrintServerName, computer";
$c = Win32::OLE->GetObject($ADsPath) or die "Невозможно получить $ADsPath\n";
foreach Sadsobj (in $c){
print $adsobj->{Name}.":".Sadsobj->{Model}."\n"
if ($adsobj->{Class} eq "PrintQueue"); }
После того как стало известно название очереди печати, можно напрямую связаться с ней для запросов и управления:
use Win32::OLE 'in';
# таблица получена из раздела
# 'Active Directory Service Interfaces 2.5->ADSI Referen.ce->
# ADSI Interfaces->Dynamic Object Interfaces->IADsPrintQueueOperations->
lADsPrintOueueOperations Property Methods' (уф!) из ADSI 2.5 SDK
%status =
(0x00000001 => 'PAUSED', 0x00000002 => 'PENDING_DELETION',
0x00000003 => 'ERROR' , 0x00000004 => 'PAPER_JAM',
0x00000005 => 'PAPER_OUT', 0x00000006 => 'MANUAL_FEED',
0x00000007 => 'PAPER_PROBLEM', 0x00000008 => 'OFFLINE',
0x00000100 => 'IO_ACTIVE', 0x00000200 => 'BUSY',
0x00000400 => 'PRINTING', 0x00000800 => 'OUTPUT_BIN_FULL',
0x00001000 => 'NOTJWAILABLE'. 0x00002000 => 'WAITING',
0x00004000 => 'PROCESSING', 0x00008000 => 'INITIALIZING'.
0x00010000 => 'WARMING_UP'. 0x00020000 => 'TONER_LOW.
0x00040000 => TJO_TONER'. 0x00030000 => 'PAGE_PUNT'.
0x00100000 => 'USER_INTERVENTION', 0x00200000 => 'OUT_OF_MEMORY'.
0x00400000 => 'DOOR_OPEFJ', 0x00800000 => 'SEflVERJNKNOWN'.
0x01000000 => 'POWER_SAVE'):
SADsPath = "^inNT' //PrintServerNafre/PrintQ-jeueNatne":
$p = tvin32: :OLE->GetOoject($ADsPath) or aie "Невозможно полу-п-ть $ADsPat'.\^".
print "Состояние принтера " . $c->{Name}
((exists $p--"i;jt au.s}; ? $status<$c-'{atatub) t . Nji ACilVL )
Объект Printoutip имеет несколько методов для контроля очереди печати: PausoO. Это позволяет управлять действиями самой очереди. А что если мы захотим изучить или обработать конкретные задачи из очереди?
Для того чтобы добраться до самих заданий, необходимо вызвать метод PnntJobs() объекта возвращает коллекцию, состоящую из объектов PrintJob, каждый из которых имеет ряд свойств и методов. Например, вот как можно показать список заданий из определенной очереди:
use Win32::OLE
и таблица получена из раздела
и Active Directory Service Interfaces 2.5->ADSI Reference
Я ADSI Interfaces->Dynamic Object Interfaces-MADsPrintJotiOporai зспз->
fl lAOsPrintJobOperations Property Methods' (двойное уф) в ADSI 2.5 SDK
%status = (0x00000001 => 'PAUSED', 0x00000002 => 'ERROR'.
0x00000004 => 'DELETING',0x00000010 => 'PRINTING',
0x00000020 => 'OFFLINE', 0x00000040 ^> 'PAPEROUT'.
0x00000080 => 'PRINTED1, 0x00000100 -> 'DELETED1):
SADsPath = "WinNT://PrintServerName/PrintQueueNamc":
$p = Win32: :GLE->GetObject($AQsPath) or
die "Невозможно лолучип, SADsPaV'\'i
$jobs = $p->PrintJobs(); foreach $job (in $jobs){ print $]ob->{User( .
$status{$job->{status}} . "\n"; }
Каждое задание можно приостановить (Раиse()) и продолжить.
Работа со службами NT/2000 через ADSI
В последнем наборе примеров рассмотрим, как находить, запускать и останавливать службы на машине с NT/2000. Как и другие примеры из этой главы, эти короткие программки необходимо запускать с достаточными привилегиями для осуществления выполняемых действий.
Для получения списка служб на машине и их состояний можно использовать такую программу:
us о w.l п32 : . ol& 1 г П эта таблица получена из раздела
Я 'Active Directory Service Interfaces 2.5->ADSI fieference->
И ADSI Interfaces->Dynamic Object Inter 'acec
» IADsServiceOperatio:iS Proper*.> Methods' AC/SI
%status =
(0x00000001 => 'STOPPED'. 0x00000002= START_PEMDIHG',
0x00000003 => 'STGP_PENDING'. 0x00000004=:- RUNNING',
0x00000005 => 'CONTINUE_PENDING' .0x00000006 => ' PAUSE_PEtJDING '.
0x00000007 => 'PAUSED'. 0x00000008 => 'ERROR'):
SADsPath = "W]nNT://DomainNarr,e/ConiputerNa!ne, computer":
$C = Win32: :OLE->GctObj'oct($ADsPath) or die "Невозможно получить $ADsPatn\;i":
foreach $adsobj (in $c){ print Sadsobj^tDisplayNarr.
if ($adsobj->{Class) eq "Service"); }
Для запуска, остановки, приостановки или продолжения работы службы вызываются очевидные методы (Star t(), Stop() и т. д.). Вот как можно запустить службу Network Time на машине с Windows 2000, если ранее она была остановлена:
use Win32::OLE;
SADsPath = "WinNT://DomainName/ComputerName/W32Tirne, service";
$s = Win32::OLE->GetObject($ADsPath) or die "Невозможно получить $ADsPath\n";
$s->Start();
tf можно в этом месте проверять в цикле состояние
пока служба не будет запущена
Во избежание потенциальных конфликтов имен пользователей и компьютеров, можно переписать предыдущий пример:
use Win32::OLE;
$d = Win32: :OLE->GetObject("WinNT://Do(r'ain");
$c = $d->GetObiect( "Computer". Scornputername):
$s = $c->GetOb]ect("Service". "W32Tiiie"):
$s->Start():
Эти примеры должны подсказать вам, какой контроль над системой можно получить при помощи ADSI из Perl. Службы каталогов и их интерфейсы могут быть весьма могущественной частью вашей компьютерной инфраструктуры.
Что такое каталог?
В главе 7 «Администрирование баз данных SQL» мною сделано предположение, согласно которому мир системного администрирования представляет собой базу данных. Каталоги - хороший пример такой оценки. Отметим некоторые явные характеристики каталогов, чтобы в дальнейшем разговоре различать «базы данных» и «каталоги»:
Использование сети
Каталоги практически всегда связаны сетью. В отличие от некоторых баз данных, расположенных на той же машине, что и их клиенты (как хорошо известный файл /etc/passwd), службы каталогов
обычно предоставляются по сетям.
Простое взаимодействие/манипуляция данными
Базы данных зачастую имеют сложные языки запросов для получения и обработки данных. Самый распространенный из них - SQL, ему уделено внимание в главе 7 и приложении D «Пятнадцатиминутное руководство по SQL». Взаимодействие с каталогами значительно проще. Клиент каталогов обычно выполняет только элементарные операции и не использует для работы с сервером никакого специального языка.
Иерархичность
Современные службы каталогов поддерживают древоподобные информационные структуры, в то время как базы данных в целом - нет.
Читаем много, пишем мало
Современные службы каталогов оптимизированы под очень специфичную передачу данных. При обычном использовании количество операций чтения/запросов к службе каталогов во много раз превосходит количество операций записи/обновлений.
Если вам встретится нечто, похожее на базу данных, но обладающее приведенными выше характеристиками, то, скорее всего, вы имеете дело с каталогом. Во всех четырех службах каталогов, которые мы рассмотрим, эти характеристики несложно заметить.
Finger: простая служба каталогов
Finger и WHOIS - отличные примеры простых служб каталогов. Finger, в особенности, предоставляет доступную только для чтения информацию о пользователях на машине (впрочем, скоро мы увидим более творческий подход к его применению). Более поздние версии Finger, такие как сервер GNU Finger и его производные, имеют расширенную разновидность этой функциональности, они позволяют обращаться к одной машине и получать информацию от всех машин из вашей сети.
Finger был одной из первых широко используемых служб каталогов. Когда-то очень давно при необходимости выяснить адрес электронной почты пользователя на другом узле или даже на вашем собственном лучшим решением было применение команды finger. Команда finger harry@hogwarts.edu сообщала адрес электронной почты Гарри, будь он harry, hpotter или даже что-то менее очевидное (правда, эта команда выводила список всех остальных учащихся школы с именем Гарри). И
хотя finger применяется до сих пор, популярность команды со временем уменьшилась, т. к. вошли в обиход домашние страницы и свободное получение информации о пользователе стало делом проблематичным.
Использование протокола Finger из Perl - еще один хороший пример правила TMTOWTDI. Когда я первый раз искал на CPAN хоть что-то, выполняющее операции с Finger, мне не удалось найти ни одного такого модуля. Сейчас-то там можно натолкнуться на модуль Net: : Finger Дениса Тейлора (Dennis Taylor), который появился спустя примерно шесть месяцев после моего первого посещения. Скоро вы с ним познакомитесь, а пока предположим, что его не существует, и не упустим
случая выяснить, как применять более общий модуль, позволяющий «связываться» по специфическому протоколу.
Протокол Finger - это очень простой текстовый протокол на базе TCP/IP. В соответствии с определением в RFC1288, он ожидает стандартное TCP-соединение на порту 79. После установки соединения клиент передает простую строку, завершающуюся последовательностью CRLF. В этой строке запрашивается информация либо о конкретном пользователе, либо обо всех пользователях на машине, если строка пустая. Сервер отвечает на запрос и закрывает соединение после передачи потока данных. Можно увидеть, как это происходит, если подсоединиться к порту Finger на удаленной машине напрямую при помощи telnet:
$ telnet kantine.diku.dk 79
Trying 192.38.109.142 . . .
Connected to kantine.diku.dk.
Escape character is '"]'.
cola<CR><LF>
Login: cola Name: RHS Linux User
Directory: /home/cola Shell: /bin/noshell
Never logged in.
No mail.
Plan:
Current state of the coke machine at DIKU
This file is updated every 5 seconds
At the moment, it's necessary to use correct change.
This has been the case the last 19 hours and 17 minutes
Column 1 is currently «empty*.
It's been 14 hours and 59 minutes since it became empty.
31 items were sold from this column before it became empty. Column 2 contains some cokes.
It's been 2 days, 17 hours, and 43 minutes since it was filled.
Meanwhile, 30 items have been sold from this column. Column 3 contains some cokes.
It's been 2 days, 17 hours, and 41 minutes since it was filled.
Meanwhile, 11 items have been sold from this column. Column 4 contains some cokes.
It's been 5 days, 15 hours, and 28 minutes since it was filled.
Возврат каретки + перевод строки, т. е. символы с ASCII-кодами 13 и!0.
Finger: простая служба каталогов 207
Meanwhile, 26 items have been sold from this column. Column 5 contains some cokes.
It's been 5 days, 15 hours, and 29 minutes since it was filled.
Meanwhile. 18 items have been sold from this column Column 6 contains some coke-lights.
It's been 5 days, 15 hours, and 30 minutes since it «as fillec.
Meanwhile, 16 items have been sold from this column.
Connection closed by foreign host. $
В данном примере мы напрямую соединились с портом Finger, набрали имя пользователя «cola», и сервер вернул требуемую информацию.
Я выбрал этот узел и этого пользователя только затем, чтобы показать, какие чудеса творились на заре появления Интернета. Серверы Finger превратились в службы для задач различного вида. В этом случае кто угодно может посмотреть, заполнен ли автомат газированных напитков, расположенный на факультете компьютерных наук в университете Копенгагена. Примеры странных устройств, подключенных к серверам Finger, можно найти на страницах Беннета Йи (Bennet Yee) в
«Internet Accessible Coke Machines» и «Internet Accessible Machines» H&http://www.cs.ucsd.edu/~bsy/fun.html.
Перенесем сетевое соединение, установленное с помощью telnet, в мир Perl. Средствами Perl можно открыть сокет и общаться через него. Вместо того чтобы применять низкоуровневые команды сокета, воспользуемся модулем Net::Telnet Джея Роджера (Jay Roger) и познакомимся с семейством модулей, работающих с сетевыми соединениями.
Другие модули этого семейства (некоторые из них будут применяться в иных главах) включают Comm.pl Эрика Арнольда (Eric Arnold), Expect.pm Остина Шатца (Austin Schutz) и хорошо известный, но устаревший и непереносимый модуль chat2.pl Рэндала Л. Шварца (Randal L. Schwartz).
Net::Telnet устанавливает соединение и обеспечивает четкий интерфейс для отправки и получения данных через это соединение. Кроме того, Net::Telnet предоставляет удобные механизмы сканирования шаблонов, позволяющие программам наблюдать за определенными
ответами от другого сервера, но в примере подобные свойства использоваться не будут.
Вот Net: :Telnet-версия простого Finger-клиента. Эта программа принимает аргумент в виде user@finger_server. Если имя пользователя пропущено, будет возвращен список всех активных пользователей с сервера. Если пропущено имя узла, будет запрашиваться локальный
узел:
use Net::Telnet;
($username.$host) = solit(/W, $ARGV[0]);
Shost = Shost ? Shost : 'localhost';
создаем новое соединение
$cn = new Net::Telnet(Host => $host,
Port => ' finger');
посылаем имя пользователя
unless ($cn->print( "$username")){ tt может быть "/W Susernarre"
$cn->close:
die "Невозможно отправить строку: ".$cn->errmg."\n";
собираем все полученные данные, останавливаясь при завершении соединения while (defined ($ret = Scn^get)1) {
$data .= $ret; }
# закрываем соединение $cn->close;
# отображаем полученные данные print $data;
В RFC1288 определено, что перед именем пользователя, отправляемым на сервер, можно добавить ключ /W для «вывода подробной информации о пользователе», поэтому в программу добавлен комментарий об этом ключе.
Если нужно соединиться, используя помимо Finger другой текстовый протокол на основе TCP, можно применить очень похожую программу. Например, для соединения с сервером Daytime (который выводит локальное время) используется очень похожая программа:
use Net::Telnet;
$host = $ARGV[0] ? $ARGV[0] : 'localhost';
Sen = new Net::Telnet(Host => Shost,
Port => 'daytime');
while (defined ($ret = $cn->get)2) {
Sdata .= $ret; } $cn->close:
print Sdata;
Теперь читатель имеет представление о том, насколько легко создавать типовые сетевые клиенты на основе TCP. Если кто-то уже потратил время и написал модуль, специально созданный для работы с протоколом, все окажется еще проще. В случае с Finger можно воспользоваться модулем Net Finger и заменить все вызовом одной функции:
use Net::Finger;
# fingerO принимает строку useriahost и возвращает полученные дачные print finger($ARGV[0]);
Желая показать все варианты, упомянем о возможности вызвать внешнюю программу (если она существует):
($username,$host) = split('@',$ARGV[0]); $host = $host 7 $host : 'localhost';
местоположение команды finger executable, пользователи MacOS
этим методом воспользоваться не могут
Sfingerex = ($"0 eq "MSWin32") ?
$ENV{'SYSTEMROOT'}."\\System32\\finger" : "/usr/ucb/finger"; # (также может быть и /usr/bin/finger)
print 'Sfingerex ${username}\@${host}'
Вы познакомились с тремя различными способами выполнения Finger-запросов. Третий метод, вероятно, самый неудачный, т. к. в нем порождается другой процесс. Net:. Finger обрабатывает простые Finger-запросы; все остальное может очень хорошо выполнить Net: : Tel net
или родственные ему модули.
Информация о модулях из этой главы
Название |
Идентификатор на CPAN |
Версия |
|
Net:: Telnet | JROGERS | 3.01 | |
Net : : Finger | FIMM | 1.05 | |
Net: :Whois | DHUDES | 1.9 | |
Net: :LDAP | GBARR | 0.20 | |
Mozilla: :LDAP | LEIFHED | 1.4 | |
Sys : : Hostname (входит в состав Perl) | |||
Win32 : : OLE (входит в состав ActiveState Perl) | JOB | 1.11 |
LDAP: сложная служба каталогов
Службы LDAP (Lightweight Directory Access Protocol, облегченный протокол доступа к каталогам) и ADSI гораздо богаче и более сложны в обращении. В настоящее время существуют две популярные версии протокола LDAP (версия 2 и версия 3; при необходимости номер версии будет указываться). Этот протокол быстро стал промышленным стандартом для доступа к каталогам. Системные администраторы воспользовались протоколом LDAP, т. к. он предлагал способ централизовать и сделать доступной всю информацию об инфраструктуре. Помимо стандартного «каталога компании» существуют такие примеры приложений:
Шлюзы NIS-K-LDAP
Шлюзы Finger-K-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();
Несколько замечаний к этому примеру:
При желании можно объединить два оператора чтения LDIF в одну строку:
gentries = new Net::LDAP::LOIF($LDIFflie."r")->read;
Если попытка add() не удалась, следует запросить десятичный код ошибки. Например, возможно такое сообщение:
Ошибка при добавлении 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 предлагает для этого две подпрограммы.
68 в десятичной системе счисления - ото 44 в шестнадцатеричной.
Теперь нам известно, что мы пытались добавить элемент из файла 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.
Рекомендуемая дополнительная литература
Finger
«RFCl 288:The Finger User Information Protocol»,
D. Zimmerman, 1991.
WHOIS
ftp://sipb.mit.edu/pub/whois/whois-servers.list
- это список наиболее крупных WHOIS-серверов.
«RFC954:NICNAME/WHOIS»,
К. Harrenstien, M. Stahl, and E. Fein ler, 1985.
LDAP
«An Internet Approach to Directories»,
Netscape, 1997 - отличное введение в LDAP (http://developer.netscape.com/docs/manuulsildap ldap.html).
«An LDAP Roadmap & FAQ»,
Jeff Hodges, 1999 (http://www.kingsmo-untain.com/idapRoadmap.shtmi).
http://www.ogre.com/ldap/
n http://www.Unc-dev.com, - домашние страницы соавторов PerLDAP.
http://www.openLdap.org/ -
свободно распространяемый LDAP-repwu. находится в стадии активной разработки.
http://www.umich.edu/~dlrsvcs/ldap/index.html - домашняя страница «прародителя» служб каталогов OpenLDAP и Netscape. Некоторая документация представляет интерес до сих пор.
«Implementing LDAP»,
Mark Wilcox (Wrox Press, 1999).
«LDAP-HOWTO»,
Mark Grennan, 1999 (http://www.grennan.com/ldap-HOWTO.html).
«LDAP Overview Presentation»,
Bruce Greenblatt, 1999 (http://www.di-rectory-applications.com/presentation/).
«LDAPProgramming Directory-Enabled Applications With Lightweight Directory Access Protocol»,
Tim Howes and Mark Smith (Macmillan Technical Publishing, 1997).
Netscape Directory Server Administrator's/Installation/Deployment Guides and SDK documentation (http://developer.netscape.com/docs/ manuals/directory.html).
«RFC1823:The LDAP Application Program Interface»,
T. Howes, M. Smith, 1995.
«RFC2222:Simple Authentication and Security Layer (SASL)», J.
Myers, 1997.
«RFC2251 .-Lightweight Directory Access Protocol (v3)»,M.
Wahl, T. Howes, S.Kille, 1997.
«RFC2252:Lightweight Directory Access Protocol (v3)Attribute Syntax Definitions»,
M.Wahl, A. Coulbeck, T. Howes, S. Kille, 1997.
«RFC2254:The String Representation of LDAP Search Filters»,
T. Howes, 1997.
«RFC2255:The LDAP URL Format»,
T. Howes, M. Smith, 1997.
«RFC2256.-A Summary of the X.500(96) User Schema for use with LDAPuS»,
M. Wahl, 1997.
« The LDAP Data Interchange Format (LDIF)-Technical Specification»
(в состоянии разработки), Gordon Good, 1999 (можно найти на http:// search.ietf.org/internet-drafts/draft-good-ldap-ldif-OX.txt, где X - это номер текущей версии).
«Understanding and Deploying Ldap Directory Services»,
Tim Howes, Mark Smith, Gordon Good (Macmillan Technical Publishing, 1998).
«UnderstandingLDAP»,
Heinz Jonner, Larry Brown, Franz-Stefan Hin-ner, Wolfgang Reis, Johan Westman, 1998. Превосходное введение в LDAP (http://www.redbooks.ibm.com/ abstracts/sg244986.html).
ADSI
http://cwashington.netreach.net/ -
еще один хороший сайт (посвящен не только Perl) по созданию сценариев для ADSI и других технологий от Microsoft.
http://www.microsoft.com/adsi -
канонический источник информации по ADSI; обязательно загрузите отсюда ADSI SDK.
http://opensource.activestate.com/authors/tobyeverett
- содержит коллекцию документации по использованию ADSI из Perl Тоби Эверета.
http://www.15seconds.com-
еще один хороший сайт (посвящен не только Perl) по созданию сценариев для ADSI и других технологий от Microsoft.
«Windows 2000 Active Directory»,
by Alistair G. Lowe-Norris (O'Reilly, 1999).
Служба каталогов WHOIS
WHOIS - это еще одна полезная служба каталогов, предоставляющая доступную только для чтения информацию. WHOIS обеспечивает услуги, подобные телефонному справочнику для машин, сетей и людей. Некоторые крупные организации, такие как IBM, UC Berkeley и MIT, предоставляют услуги WHOIS, но самые известные WHOIS-серверы принадлежат InterNIC и другим компаниям, занимающимся вопросами регистрации в Интернете, в том числе RIPE (имеет дело с европей-
скими IP-адресами) и APNIC (Asia/Pacific, Азиатские/Тихоокеанские адреса).
При необходимости связаться с системным администратором какого-либо узла, чтобы сообщить ему о подозрительных действиях в сетях, следует использовать службу WHOIS для получения контактной информации. В большинстве операционных систем для выполнения WHOlS-запросов существуют как GUI-инструменты, так и инструменты, запускаемые из командной строки. Типичный запрос в Unix выглядит так:
% whois -h whois.networksolutions.com brandeis.edu
<large legal paragraph omitted>
Registrant;
Brandeis University (BRANDEIS-DOM)
Information Technology Services
Waltham, MA 02454-9110
US
Domain Name: BRANDEIS.EDU
Administrative Contact:
Koskovich, Bob (BK138) user@BRANDEIS.EDU
+1-781-555-1212 (FAX) +1-781-555-1212 Technical Contact, Zone Contact:
Hostmaster, Brandeis С (RCG51) hostmaster@BRANDEIS.EDU
+1-781-555-1212 (FAX) +1-781-555-1212 Billing Contact:
Koskovich, Bob (BK138) user@BRANDEIS.EDU
+1-781-555-1212 (FAX) +1-781-555-1212
Record last updated on l3-0ct-1999.
Record created on 27-May-1987.
Database last updated on 19-Dec-1999 17:42:19 EST.
Domain servers in listed order:
LILITH.UNET.BRANDEIS.EDU 129.64.99.12
FRASIER.UNET.BRANDEIS.EDU 129. 64.99.11
DIAMOND. CS.BRANDEIS.EDU 129. 64. 2. 3
DNSAUTH1.SYS.GTEI.NET 4.2.49.2
DNSAUTH2.SYS.GTEI.NET 4.2.49.3
Если же нужно выяснить владельца определенного диапазона IP-адресов, то и тут поможет WHOIS:
% whois -h whois.arin.net 129.64.2
Brandeis University (NET-BRANDEIS) 415 South Street Waltham, MA 02254
Netname: BRANDEIS Netnumber: 129.64.0.0
Coordinator:
Koskovich, Bob (BK138-ARIN) user@BRANDEIS.EDU 617-555-1212 Служба каталогов WHOIS 211
Domain System inverse mapping provided by:
BINAH.CC.BRANQEIS.EDU 129.64.1.3 NIC.NEAR.NET 192.52.71,4
NOC.CERF.NET 192.153.156.22
Record last updated on 10-Jul-97.
Database last updated on 9-Oct-98 16:10:44 EOT
The ARIN Registration Services Host contains ONLY Internet Network Information: Networks, ASN's, and related POC's. Please use the whois server at rs.internic.net for DOMAIN related Information and nic.mil for NIPRNET Information.
В предыдущем примере применялся WHOIS-клиент из Unix, работающий в командной строке. В Windows NT и MacOS подобные клиенты не входят, тем не менее, это не должно остановить пользователей данных систем от получения доступа к нужной информации. Существует
много условно бесплатных клиентов, но не так трудно с помощью модуля Net: : Whois создать на Perl очень простой клиент (модуль Net: : Whois первоначально был написан Чипом Салзенбергом (Chip Salzenberg), а теперь поддерживается Даной Хьюдес (Dana Hudes)). Следующий
код - это лишь несколько измененная версия примера из документации, поставляемой вместе с модулем:
use Net::Whois;
fl запрашиваем сервер, возвращая объект с результатами my $w = new Net::Whois::Domain $ARGV[0] or
die "Невозможно соединиться с сервером Whois\n", die "Никакой информации о домене $ARGV[0] не найдено\п " unless ($w->ok),
и выводим части этого объекта
print "Домен: ", $w->domain, "\n";
print "Имя: ", $w->name, "\n";
print "Тег: ", $w->tag, "\n";
print "Адрес:\n", map { " $_\n" } $w->address;
print "Страна: ", $w->country, "\n";
print "Запись создана: ".$w->record_created."\n";
print "Запись обновлена: ",$w->record_updated."\n";
и выводим серверы имен ($w->servers returns a list of lists)
print "Серверы именДп", map { " $$_[0] ($$_[1])\n" } @{$w->servers;
tt выводим список контактов ($w->contacts returrs a nasn of lists)
my($c,$t).
if ($c = $w->contacts) {
print "Contacts :\n";
for $t (sort keys %$c) { print " St.\n": 212 Глава 6. Службы каталогов
print map { "\t$_\n" } @{$$c{$t}}; } }
Запрос WHOIS сервера InterNIC/Network Solutions - это простой процесс. Для возвращения результата применяется мо«дуль Net::Whois: : Domain. Методы этого объекта, названные в соответствии с полями, которые получает WHOIS-запрос, обеспечивают доступ к данным.
WHOIS предстоит сыграть значительную роль в главе 8 «Электронная почта», а сейчас перейдем к более сложным службам каталогов. Мы уже начали этот переход, переключаясь со службы Finger на WHOIS. Между рассмотренными способами использования Finger и WHOIS существует важное различие - структура.
Вывод Finger отличается от реализации к реализации. И хотя существуют некоторые соглашения, форму он имеет свободную. WHOIS-cepвер InterNIC/Network Solutions возвращает данные более постоянной структуры. Можно рассчитывать на то, что у каждой записи будут, по крайней мере, поля Name, Address и Domain. Модуль Net: :Whois полагается на эту структуру и анализирует результаты, разбивая их на поля. Существует еще один модуль Випула Вед Пракаша (Vipul Ved Prakash) -
Net: :Xwhois, который делает шаг вперед, обеспечивая интерфейс для анализа информации, по-разному отформатированной различными WHOIS-серверами.
И хотя в протоколе WHOIS нет никакого упоминания о полях, вызываемые нами модули начинают полагаться на структуру информации. Службы каталогов, о которых пойдет речь, более серьезно относятся к этой структуре.