|Problems? Is your data what you think it is?|
LDAP - a relatively low-profile buzzword, but becoming more and more popular. "What is it?" and "How do we do it with Perl?" are question I'll attempt to tackle in this article.
I won't really cover how to deisgn an LDAP directory- I'm concentrating on showing the bits of Perl you use to search/update one.
What is it?
(Skip this bit if you want to go right to the Net::LDAP bit)
LDAP is a way of providing a hierarchial directory service. It's actually somewhat similar to DNS and/or NIS/NIS+ in some ways. The comparison is by no means exact, but it might help you get a grasp of what it's for and give you some ideas on how it can be used. In fact, LDAP can be used as an alternative to NIS+ as a naming/authentication service using nss-ldap and pam-ldap.
So, just as a DNS domain is a collection of domain names, an LDAP directory is a collection of entries, each indentified by a Distinguished Name (DN).
A DN specfies a sequence of elements, each having a type and value. For example: "o=Example, c=GB" means "Organisation called \"Example\" in Country called \"GB\"". Like DNs, the hierarchy is written right-to-left as you descend from the root.
Any particular entry in an LDAP directory is uniquely identified by a DN. The entry that has a list of attributes, each of which has a list of values. So the LDAP entry for a particular user may have an attribute "userPassword", which a single value containing their encrypted password.
There are a bunch of standards that LDAP is built on that are handy to know about, but not necessary for basic use. e.g. ASN.1, BER (Basic Encoding Rules). This is where a lot of the terminology and conventions come from.
How to use it
In order to access an LDAP directory, you have to connect to an LDAP server that has the directory you want. A connection to an LDAP server is identified by a Net::LDAP object- the connection is made like this:
Basic operations: bind
"Binding" means identifying yourself to the LDAP server. This may be necessary in order to get any data from the server, or may only be necessary to update the server.
You bind my calling the "bind" method of your Net::LDAP object:
This is an example of an "anonymous" bind- no identification. This is a "guest logon", and often give you read-only access with perhaps some attributes elided (e.g. passwords, confidential information).
In order to actually do some identification, you supply parameters to the bind() method:
Notice that you identify yourself using a DN. In this case, the DN is read as: "User id is \"jblow\", in the \"People\" organisational unit, in the \"example.com\" LDAP directory".
LDAP result codes
What is the result of the bind() call, $result ? The connection to the LDAP server is by default asynchronous. Instead of waiting for the bind operation to complete, the bind() method merely sends the request to the server. The return value is an object that identifies the message you sent (an LDAP::Message object).
So how can you tell whether the bind was successful or not if we haven't got a response from the server yet? Well, if you call the "code" method on the LDAP::Message object, then Net::LDAP will block until the reply for that message has been received from the server. So you do this:
BTW the code for "success" is zero, so checking for a non-zero value of "code" will check for an error.
Of course, in the above example, we don't print a very helpful error message if something goes wrong. There is a module to help out here: Net::LDAP::Util contains functions for converting an error code into a message. So try this:
Often, you will want to check every call for an error as soon as you can, so you can save yourself some code duplication by writting a wrapper subroutine to check the result code of an ldap call and die()ing as applicable:
This routine returns the LDAP::Message object, so you can still get any actual data that is returned as part of the message.
Basic operations: search
The most common operation, after binding, is searching. This is done with the "search" method of the Net::LDAP object. There are four parameters for a search:
The filter is crucial. It can be either a string or a Net::LDAP::Filter object. The format for writing LDAP filters as strings is described in RFC-2254. Briefly, you write "(attribute=value)" to search for one of the values of "attribute" being "value". You then combine these expressions together in prefix form rather like LISP:
When matching a value you can replace "=" with "~=" to mean "approximately equal", or with ">=" or "<=" for greater-equal or less-than-eqal respectively.
You can also prepend or append "*" characters to the value to represent intial, final or substring matches.
Some examples now:
So how, does the search work? You just feed the parameters to the "search()" method and get an LDAP::Search (which is a subclass of LDAP::Message) back.
By default, the scope will be "sub", and all attributes of matching entries will be returned. You can find other parameters describe in the Net::LDAP(3) manpage.
Of course, since the result is an LDAP::Message, you can wrap the search call with ldapassert().
Now, to get the entries that matched your search, you have several options. Perhaps the simplest is to use the "entries" method of the Net::LDAP::Search object, which returns an array of Net::LDAP::Entry objects:
With the Net::LDAP::Entry object you have these basic methods for getting their data:
So here's a full example of how I could print out everyone's name ("cn" is a standard attribute for Common Name- i.e. someone's real name):
Modifying an entry
Net::LDAP::Entry has methods for modifying the data of an entry. For example, if I wanted to change someones name:
replace() takes a hash of "attribute => value" pairs. The value may be an array reference to multiple values.
You can also call the "add()" method to add a single value to an existing list of values for a particular attribute, which takes parameters in the same format. And there is the "delete()" method which takes a single attribute name as a parameter and deletes it.
Note that all these methods only affect the representation of the entry kept by the Perl script. In order to update the entry on the directory server, you must call the "update()" method and pass it the Net::LDAP object to tell it which connection to update on. e.g.:
add() is different to replace(). This could have been written:
But as well as being more messy this would introduce a race condition- if someone else modified the entry between your search and modify commands, you would lose.
I strongly recommend reading the Net::LDAP(3), Net::LDAP::Search(3) and Net::LDAP::Entry(3) manpages.
also, try the OpenLDAP website (although Net::LDAP is purely in Perl, and doesn't use the OpenLDAP library)
Other links to good online LDAP resources should go here, but I don't really know any.
In reply to Using Net::LDAP to access and update LDAP directories by araqnid