10.2 Connecting, Binding, and Searching
To get started with the Net::LDAP module, we
will write a basic LDAP query script named
search.pl. This script illustrates the methods
used to connect to a directory server and retrieve information. It
begins by importing the Net::LDAP symbols via the
use pragma:
#!/usr/bin/perl
use Net::LDAP;
After the module has been included, you can create a new instance of
a Net::LDAP object. To create a new
Net::LDAP instance, you need the
hostname of the LDAP server to which the script should connect. The
constructor allows several optional arguments, of which the most
common and useful are:
- port
-
The TCP port on which the directory server is listening. If this
parameter is not defined, it defaults to the well-known LDAP port
(389).
- version
-
The LDAP version to be used when connecting to the server. The
default is Version 2 in the 0.26 release. However, this is likely to
change in the future. Always explicitly set the version parameter if
your Perl program replies with LDAPv3 features (such as SASL or
referrals).
- timeout
-
The time in seconds that the module should wait when contacting the
directory server. The default value of 120 seconds is sufficient for
most situations, but for more complex searches or when communicating
with a very large directory, it may be necessary to increase this
value.
The next line of code establishes a connection to the host
ldap.plainjoe.org on port 389
using Version 3 of the protocol. The returned value is a handle to a
Net::LDAP object that can be used to retrieve and modify data in the
directory.
$ldap = Net::LDAP->new ("ldap.plainjoe.org", port =>389,
version => 3 );
The script can bind to the directory after it obtains a handle to the
LDAP server. By default, Net::LDAP uses an implicit anonymous bind,
but it supports all the standard binds defined by the LDAPv3 RFCs
(anonymous, simple, and SASL). For now, we only examine how to use a
simple bind.
However, before binding to the server, call start_tls( ) to
encrypt the connection; you don't want to send the
user DN and password across the network in clear text. In its
simplest form, the start_tls( ) method requires no
parameters and appears as:
$ldap->start_tls( );
Most of the
Net::LDAP
methods return an object with two methods for obtaining the
function's return status. The code(
) method retrieves the integer
return value from the method call that created the object, and the
error(
) method returns a descriptive
character string associated with the numeric code. The constants for
the various LDAP errors are contained in the
Net::LDAP::Constant
module. Specific error codes can be included in your code by adding a
line similar to the following one:
use Net::LDAP::Constant qw(LDAP_SUCCESS);
The following code tests for an error condition after some arbitrary
LDAP call:
if ($result->code( ) != LDAP_SUCCESS) {
die $result->error( );
}
Because most methods indicate success with a return code of zero,
this error check can be shortened to:
die $result->error( ) if $result->code( );
The Net::LDAP::Util module contains a few
extra functions for obtaining more error information. The
ldap_error_text function returns the descriptive
POD text for the error code passed in as a parameter, and
ldap_error_name returns the constant name for an
integer (for example, if it is passed the integer 0, it returns the
string LDAP_SUCCESS).
|
It is a good idea to check for errors after attempting to establish a
secure communication channel; if start_tls( )
fails, and the script continues blindly, it might inadvertently
transmit sensitive account information in the clear. To do so, save
the result object returned by start_tls( ), and
then use the code( ) method to find out whether
start_tls( ) succeeded:
$result = $ldap->start_tls( );
die $result->error( ) if $result->code( );
If the script tries to establish transport layer security with a
server that does not support this extended operation, the error check
displays an error message and exits:
Operations error at ./search.pl line XXX.
The actual error from Net::LDAP::Constant is
LDAP_OPERATIONS_ERROR.
Now you can safely send the sensitive data to the server. A simple
authenticated bind requires only a DN and a password. If neither are
provided, the call attempts to establish an explicit anonymous
binding (as opposed to the implicit bind used when bind(
) is not called at all). The following line seeks to bind
your client to the directory as the entry
cn=Gerald
Carter,ou=people,dc=plainjoe,dc=org using the
password hello. Once again, you use
error( ) and code( ) to check
the return status:
$result = $ldap->bind("cn=Gerald Carter,ou=people,dc=plainjoe,dc=org",
password => "hello");
die $result->error( ) if $result->code( );
If there is no error, the script is free to search the directory. The
search(
) method accepts the standard
parameters that are expected from an LDAP query tool. At this point,
we're interested only in the
base, scope, and
filter parameters. To make the script more
flexible, use the first argument passed in from the command line
(i.e., search.pl "Gerald Carter") to build a
filter string that searches for the user's common
name (cn):
$msg = $ldap->search(
base => "ou=people,dc=plainjoe,dc=org",
scope => "sub",
filter => "(cn=$ARGV[0])" );
The return value of the search is an instance of the
Net::LDAP::Search object. You can manipulate this object to retrieve
any entries that matched the search. This object has a
count( ) method that returns the number of
entries, and an all_entries( ) method that returns
the results as an array of Net::LDAP::Entry objects, each of which
represents information associated with a single directory node. You
can view the results of this query by dumping each entry from the
array:
if ( $msg->count( ) > 0 ) {
print $msg->count( ), " entries returned.\n";
foreach $entry ( $msg->all_entries( ) ) {
$entry->dump( );
}
}
The output for a single entry looks like this:
dn:cn=Gerald Carter,ou=people,dc=plainjoe,dc=org
objectClass: inetOrgPerson
cn: Gerald Carter
sn: Carter
givenName: Gerald
o: Hewlett-Packard
mobile: 256.555.5309
mail: jerry@plainjoe.org
postalAddress: 103 Somewhere Street
l: Some Town
st: AL
postalCode: 55555-5555
The dump(
) routine is not meant to generate
valid LDIF output, as can be seen from the extra whitespace added to
center the attribute/value pairs; another module, aptly named
Net::LDAP::LDIF, handles that feature. We'll discuss
working with LDIF files later in this chapter. For now, just printing
the attribute/value pairs in any form is good enough.
What if you're interested only in a
person's email address? Some entries contain many
attributes, and asking a user to look through all this output in
search of an email address could qualify as cruel and unusual
punishment. How can you modify the script so that it displays only
the attributes you want? The search( ) function
has an optional parameter that allows the caller to define an array
of attribute names. The search returns only the values of attributes
that match names in the list. Here's how to modify
the script so that it retrieves only the mail and
cn attributes:
$msg = $ldap->search(
base => "ou=people,dc=plainjoe,dc=org",
scope => "sub",
filter => "(cn=$ARGV[0])",
attrs => [ "cn", "mail" ] );
And here's what you get when you dump the entry
returned by the modified query:
dn:cn=Gerald Carter,ou=people,dc=plainjoe,dc=org
cn: Gerald Carter
mail: jerry@plainjoe.org
The last line of the script invokes the unbind(
) method to disconnect from the
directory:
$ldap->unbind( );
This routine effectively destroys the connection. The most portable
means to rebind to an LDAP server using a new set of credentials is
to call bind( ) again with the new DN and password
(but only when using LDAPv3). Once the unbind( )
subroutine has been invoked, the connection should be thrown away and
a new one created if needed.
The following listing shows the
search.pl
script in its entirety:
#!/usr/bin/perl
##
## Usage: ./search.pl name
##
## Author: Gerald Carter <jerry@plainjoe.org>
##
use Net::LDAP;
## Connect and bind to the server.
$ldap = Net::LDAP->new ("ldap.plainjoe.org", port =>389,
version => 3 )
or die $!;
$result = $ldap->start_tls( );
die $result->error( ) if $result->code( );
$result = $ldap->bind(
"cn=Gerald Carter,ou=people,dc=plainjoe,dc=org",
password => "hello");
die $result->error( ) if $result->code( );
## Query for the cn and mail attributes.
$msg = $ldap->search(
base => "ou=people,dc=plainjoe,dc=org",
scope => "sub",
filter => "(cn=$ARGV[0])",
attrs => [ "cn", "mail" ] );
## Print resulting entries to standard output.
if ( $msg->count( ) > 0 ) {
print $msg->count( ), " entries returned.\n";
foreach $entry ( $msg->all_entries( ) ) {
$entry->dump( );
}
}
## Unbind and exit.
$ldap->unbind( );
|