Quantcast
Username/Email:  Password: 

An Introduction to perl-ldap

A beginner's guide to using Net::LDAP

As systems get larger and the number of
users they support increases, it becomes more difficult to manage
systems using only the old-fashioned UNIX /etc/passwd file. A
common solution to this problem is to use a Lightweight Directory
Access Protocol (LDAP) server. The use of an LDAP server presents a
problem to the system administrator, however, in that the contents
of the database are no longer available in an easy to read or
modify format. Hence, new tools must be written that allow
standard, everyday tasks, such as adding or deleting users, to be
performed.This is where perl-ldap
comes in. perl-ldap provides the Net::LDAP perl module, which
enables easy access to the data contained in LDAP directories from
Perl scripts. This makes the module a useful tool for system
administrators and Web developers alike. The perl-ldap home page is
located at
http://perl-ldap.sourceforge.net/.For this article, I assume you have a reasonable knowledge of
LDAP and are a competent Perl programmer. If not, plenty of
published material is available on the Internet covering both of
these topics.InstallationIf you're running one of the popular Linux distributions,
chances are perl-ldap already has been packaged for you, which
makes installation simple. Under Debian Linux, perl-ldap is found
in the libnet-ldap-perl package. Assuming that your
/etc/apt/sources.list file contains an up-to-date Debian server,
the following commands should install perl-ldap:

apt-get update 
apt-get install libnet-ldap-perl

Mandrake users will find what they need in the perl-ldap
package; for Mandrake 9.1, the specific package is
perl-ldap-0.27.01-1mdk.noarch.rpm. If you have urpmi configured
correctly, you can install perl-ldap simply by entering:

urpmi perl-ldap

This command also installs the perl-Authen-SASL and
perl-XML-Parser packages, which are perl-ldap dependencies in
Mandrake.Red Hat does not appear to provide a perl-ldap package, so
users of this distribution either have to obtain it from another
RPM-based distribution or install it from the tar.gz package as
described below.If a pre-built package isn't available for your system, you
have to download the tar.gz package from CPAN and install it
yourself. As the LDAP protocol uses ASN1 encodings, you also need
the Convert::ASN1 library. Although you probably can install
perl-ldap without it, perl-ldap certainly won't run unless this
library available. Both of these libraries are easy to
install:

perl Makefile.PL
make
make test
su root
make install            

Basic UsageAs with other Perl libraries, perl-ldap is invoked with the
use statement:

use Net::LDAP 

A new LDAP connection is opened using the new() function
call. In the following example, we open a connection to a machine
with hostname ldapserver.domain.name:

$ldap = Net::LDAP->new("ldapserver.domain.name");

Because we haven't specified which port number to use,
perl-ldap assumes a default of port 389, the well-known LDAP port.
If we want to use a different port, say 1389, we need to pass the
port parameter:

$ldap = Net::LDAP->new("ldapserver.domain.name", port=>1389);

If the server is not reachable, the above function calls
return an error after 120 seconds. You can use the timeout
parameter to alter this:

$ldap = Net::LDAP->new("ldapserver.domain.name", timeout=>30);

After the connection has been initiated, you no longer need
to refer explicitly to the Net::LDAP package. All of the perl-ldap
functions are accessed as methods of the reference returned from
the new() call. The most commonly used methods provided by
perl-ldap are as follows:

$ldap->add();           # Add an entry to the server
$ldap->bind();          # Bind to a directory server
$ldap->delete();        # Delete an entry from the server
$ldap->moddn();         # Modify an entry's Distinguished Name (DN)
$ldap->modify();        # Modify the contents of an entry
$ldap->search();        # Perform a search on a directory
$ldap->unbind();        # Unbind from a server

These are described in detail below.Binding to the Server - bind()For this example, we assume that I have an LDAP directory
with the following contents:

dn: dc=leapster,dc=org
|
-- dn: cn=admin,dc=leapster,dc=org
|
-- dn: ou=People,dc=leapster,dc=org
   |
   -- dn: uid=paul,ou=People,dc=leapster,dc=org
   |
   -- dn: uid=mike,ou=People,dc=leapster,dc=org

Put simply, my LDAP base DN is dc=leapster,dc=org. The
administrative user of the system (the entry that has superuser
control) is cn=admin,dc=leapster,dc=org. It also contains two user
entries, uid=paul and uid=mike.Once you have created a connection to an LDAP server, you
need to bind to it. If you're writing a program to talk to public
LDAP directories, chances are you need to use only an anonymous
bind:

$mesg = $ldap->bind;

However, if you're writing scripts to manage the directory of
a server used for storing the account information of local users or
customers, you are likely to allow only write access to specific,
high-privilege users. In this case, you need to give the DN of the
LDAP entry which has these privileges, as well as the password. For
example:

$mesg = $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret");

In this case, I use the following privileged user on my
system: cn=admin,dc=leapster,dc=org. If I'd bound to one of the
unprivileged users (for example, uid=paul,dc=leapster,dc=org), I
may not have had any access to read or write options on the system
at all, depending on how the server was configured.The return value, which we store in $mesg, is an object of
class New::LDAP::Message. It is discussed later in this
article.If you wish to close a connection, you must unbind from
it:

$ldap->unbind;

Adding Entries - add()If you have a large number of users on your system, it's
likely you do not want to add each new user to the system one by
one, through a GUI. So, one of the first things you write is a
script that quickly adds lots of users in bulk. Or, perhaps you'll
write a Web-based system where users can enter their personal
information for themselves, with LDAP entries being added
automatically. The add() method is used for adding an entry to the
database:

$result = $ldap->add("uid=john,ou=People,dc=leapster,dc=org", 
                attr => [ 'cn' => 'John Smith',
                          'sn' => 'Smith',
                          'uid' => 'john',
                          'givenName' => 'John',
                          'homePhone' => '555-2020',
                          'mail' => 'john@domain.name',
                          'objectclass' => [ 'person', 'inetOrgPerson']
                        ]
           );

The above snippet of code adds a user named John Smith to our
database. As you can see, the attributes are provided as a list to
the attr parameter. Any attribute to which you wish to give
multiple values should have them supplied as a list (in our example
above, objectclass is such an attribute).We're now at the point where we can write a simple script to
add our large number of users in bulk, a script I've called
ldap_addusers.

#!/usr/bin/perl
use Net::LDAP;
$ldap = Net::LDAP->new("localhost");
$ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret");
while(<>) {
        chomp $_;
        ($uid,$givenName,$sn,$mail) = split(/:/,$_);
        $cn="$givenName $sn";
        $dn="uid=$uid,ou=People,dc=leapster,dc=org";
        $result = $ldap->add($dn,
                attr => [ 'uid' => $uid,
                          'cn' => $cn,
                          'sn' => $sn,
                          'mail' => $mail,
                          'givenName' => $givenName,
                          'objectclass' => [ 'person', 'inetOrgPerson']
                        ]
           );
        $result->code && warn "error: ", $result->error;
}

The above script takes a colon-separated file of users, one
per line, on stdin:

tom:Tom:Jones:tom@domain.name
dick:Dick:Tracy:dick@domain.name
harry:Harry:Windsor:harry@domain.name

So, if these names are stored in a file called userlist, we
can enter them into our LDAP database with the ldap_addusers
script, as follows:

cat userlist | ./ldap_addusers

There's one line in the ldap_addusers script that we haven't
seen before:

$result->code && warn "error: ", $result->error;

As mentioned earlier, the add() method returns an object of
type Net::LDAP::Message. Here, this object is referenced by
$result. $result->code is the code value returned from the LDAP
server in the result message after a query (in this case, the
request to add an entry). Generally, when the request is
successful, a zero is returned. Hence, in our above statement, the
warning is issued only when $result->code is not zero.Some other useful methods in Net::LDAP::Message are:

$result->dn        The DN contained in the result message
$result->error     The error message in the result (only if there was an error)
$result->done      True if the request was completed
$result->is_error  True if the particular result is an error for the operation

For a full description of the other methods, see the perldoc
manual for Net::LDAP::Message.perl-ldap also provides the delete() method for deleting
entries, when given their DN:

$dn="uid=paul,ou=People,dc=leapster,dc=org";
$ldap->delete($dn);

This would, for example, allow you to write a script to do a
bulk delete of expired users from your system:

#!/usr/bin/perl
use Net::LDAP;
$ldap = Net::LDAP->new("localhost");
$ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret");
while(<>) {
        chomp $_;
        $dn="uid=$_,ou=People,dc=leapster,dc=org";
        $ldap->delete($dn);
}

This script deletes all users whose uids are fed to it on
standard input, one per line. Be careful when using this script on
production servers; if you accidentally feed it the wrong file, you
may find yourself with no users left in your directory.Searching for Entries - search()It wouldn't be much use only to be able to write to our LDAP
server; we need to be able to read from it also. The perl-ldap
search command is used to perform lookups on an LDAP server.

$mesg = $ldap->search(filter=>"(uid=paul)", base=>"dc=leapster,dc=org");

The base parameter specifies the base object entry from which
the search will be made. In the example above, it searches the
entire LDAP tree. The search could be confined to only the
ou=People branch with:

$mesg = $ldap->search(  filter=>"(uid=paul)", 
                        base=>"ou=People,dc=leapster,dc=org");

A number of other optional parameters are available to the
search() method:

  • scope : This can be one of the following:

  • base: Search the base object only
  • one: Search only the entries one level below the
    base
  • sub: Search the entire subtree below the
    base

If I knew no subtrees were below ou=People--or if there were,
but I didn't want results returned from them--I could use:

$mesg = $ldap->search(  filter=>"(uid=paul)", 
                        base=>"ou=People,dc=leapster,dc=org"
                        scope=>"one");
  • timelimit: Set a limit in seconds on the amount of
    time that a request may take. The default is 0, which signals that
    the time is unlimited.
  • attrs: Set the attributes (as a reference to an
    array) that should be returned in the search. If not provided, the
    search returns all of the attributes. For example, the following
    search returns only the uid, sn and givenName attributes:
    $mesg = $ldap->search(  filter=>"(uid=paul)", 
                            base=>"ou=People,dc=leapster,dc=org",
                            attrs=> ['uid', 'sn', 'givenName'] );
    
  • filter: The filter may be a string, in standard
    LDAP filter format (see the ldap_search(3) man page for a
    description of this), or it may be a Net::LDAP::Filter object (see
    the Net::LDAP:Filter man page for further information).
  • The search() method returns Net::LDAP::Search objects. The
    easiest way to get at the contents of this object is to use its
    entries() method, which returns an array of Net::LDAP::Entry
    objects (see below):

    @entries = $mesg->entries;
    

    The Net::LDAP::Search object also has a number of other
    useful methods:

    $mesg->count;           The number of entries returned in the search
    $mesg->entry(n);        Returns the n'th entry (initial entry is 0)
    $mesg->sorted([list])   Returns a list of entry objects sorted by attr list
    

    Now, we can write a small script to list every entry in the
    directory:

    #!/usr/bin/perl
    use Net::LDAP;
    $ldap = Net::LDAP->new("localhost");
    $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret");
    $mesg = $ldap->search(filter=>"(objectClass=*)", base=>"dc=leapster,dc=org");
    @entries = $mesg->entries;
    foreach $entry (@entries) {
            $entry->dump;
    }
    

    I've cheated a little in the above script and used the dump()
    method of Net::LDAP::Entry, in order to make things clearer. dump()
    primarily is used for debugging; it merely dumps the DN and
    contents of an entry straight to standard output, without allowing
    for any manipulation of the results.The most commonly used methods of the Net::LDAP::Entry object
    are:

    • attributes: returns the list of attributes
      contained in this entry.

    @attrs = $entry->attributes();
    
  • dn: Returns the DN of the current entry. If given
    with a parameter, it sets the DN of the entry:
    $dn = "uid=pbd,ou=Users,dc=leapster,dc=org";
    $entry->dn($dn);
    
  • get_value: obtains the value or values for the
    attribute, given as a parameter. If this method is used to assign
    to a scalar variable, it returns only the first value for the
    attribute; if used with an array, it returns all of the attributes.
    $phone = $entry->get_value("homePhone");  # returns only one phone number
    @phone = $entry->get_value("homePhone");  # returns all phone numbers for entry
    
  • add, delete, modify: These methods allow changes to
    be made to the entry and are discussed further in the next
    section.
  • update: pushes any changes made to the entry to the
    LDAP server (whose object is given as a parameter):
    $entry->add(homePhone => "555 3034");
    $entry->update($ldap);
    
  • Now that we've examined the Net::LDAP::Entry object, we can
    expand the above script further. We can write out the contents of
    the entries ourselves:

    #!/usr/bin/perl
    use Net::LDAP;
    $ldap = Net::LDAP->new("localhost");
    $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret");
    $mesg = $ldap->search(filter=>"(objectClass=*)", base=>"dc=leapster,dc=org");
    @entries = $mesg->entries;
    foreach $entry (@entries) {
            print "dn: " . $entry->dn() . "\n";
            @attrs = $entry->attributes();
            foreach $attr (@attrs) {
                    printf("\t%s: %s\n", $attr, $entry->get_value($attr));
            }
    }
    

    Modifying Entries - modify()It would be unusual for entries in a directory to be static.
    Various attributes probably change over time, as users change phone
    numbers, addresses or even names. At the very least, you would hope
    your users change their passwords regularly. perl-ldap provides the
    modify() method to handle such changes.The three main modification actions that can be performed
    upon an LDAP entry are:

    • add: add one on more attributes to an entry
    • delete: delete one or more attributes from an
      entry
    • replace: replace one or more attributes with
      different values.

    Examples:

    $dn = "uid=paul,ou=People,dc=leapster,dc=org";
    # add a 'homePhone' attribute and a 'mail' attribute
    $mesg = $ldap->modify($dn, add => { "homePhone" => "555 3030",
                                        "mail" => "paul\@mail.home"} );
    # add two more 'homePhone' attributes
    $mesg = $ldap->modify($dn, add => { "homePhone" => ["555 3031", "555 3032"] });
    # delete the mobile and pager attributes
    $mesg = $ldap->modify($dn, delete => [ 'mobile', 'pager' ] );
    # change the mail attribute to 'paul@domain.name'
    $mesg = $ldap->modify($dn, replace => { "mail" => "paul\@domain.name" } );
    

    If you have an attribute with multiple values and wish to
    delete only one of those values, you can give delete a specific
    attribute/value hash to delete:

    $mesg = $ldap->modify($dn, delete => { 'homePhone' => "555 3031" } );
    

    If you wish to do a number of changes at once, modify also
    provides the changes parameter, which takes a list of add, delete
    and replace operations:

    # Add an employeenumber and delete 
    $mesg = $ldap->modify($dn, changes => [
                                           add => [ employeeNumber => "4321" ],
                                           delete => [ mail => [] ]
                                          ]);
    

    As with most other perl-ldap methods, modify() returns a
    Net::LDAP::Message object. Therefore, you can use $mesg-code to
    check whether an error was returned.It's also possible to modify local copies of LDAP entries
    directly and then push the changes through to the server
    afterwards. Net::LDAP::Entry has a number of methods for doing
    this. Each method takes a list of attribute/value hashes (delete
    also accepts a simple list of attribute names):

    • add: adds one or more attributes to an
      entry.
    • delete: deletes one or more attributes from an
      entry.
    • replace: replaces one or more attributes in an
      entry.

    None of these changes are propagated to the directory server
    until the update() method is called.

    $base = "ou=People,dc=leapster,dc=org";
    $mesg = $ldap->search(  filter=>"(uid=paul)", base=>$base);
    $entry = $mesg->entry(0);
    $entry->add(homePhone => "555 3035", pager => "555 4040");
    $entry->delete("suburb");
    $entry->replace(fax => "555 5050");
    $entry->update($ldap);
    

    SummaryTo summarise, perl-ldap is a convenient and straightforward
    library for accessing LDAP servers with Perl scripts. Thus it
    provides a simple method for a system administrator to perform
    maintenance on systems serving large numbers of users, in much the
    same manner as they have been doing on existing flat-file
    /etc/passwd systems. Last winter, I used perl-ldap in scripts to
    help transfer 1.2 million users from our old Netscape Messaging
    Server system to our new, custom-built QmailLDAP servers. perl-ldap
    continues to prove invaluable for day-to-day maintenance of the
    same system.

    email: paul@dwerryhouse.com.au

    ______________________

    Comments

    Comment viewing options

    Select your preferred way to display the comments and click "Save settings" to activate your changes.

    UID

    Anonymous's picture

    Cannot figure what the value of UID should be? I can dump all the contents with filter set as (objectclass=*) and get around 200 entries ; But, when I try to search for a particular UId, I do not get any results back. can anyone tell me what value should be put in for UID to search? I tried with filter=>"(uid=vinda)" filter=>"(uid=vinda norman)" filter=>"(uid=norman)" but no luck.

    dn: CN=vinda Norman,OU=Users,OU=SysStaff,OU=SBCS,DC=ad,DC=cs,DC=sunysb,DC=edu
    objectClass: top
    cn: Vinda Norman
    sn: Norman
    givenName: Vinda
    distinguishedName: CN=Vinda Norman,OU=Users,OU=SysStaff,OU=SBCS,DC=ad,DC=cs,DC=sunysb,DC=edu
    instanceType: 4
    whenCreated: 20080904132500.0Z
    whenChanged: 20090906023318.0Z
    displayName: Vinda norman
    uSNCreated: 9019
    memberOf: CN=System Staff Users,CN=Users,DC=ad,DC=cs,DC=sunysb,DC=edu
    uSNChanged: 778762
    name: Vinda Norman
    objectGUID: *nÄsM¹lN_Aö
    userAccountControl: 512
    badPwdCount: 0
    codePage: 0
    countryCode: 0
    badPasswordTime: 128975058670944212
    lastLogoff: 0
    lastLogon: 128975753602883244
    pwdLastSet: 128965394154875698
    primaryGroupID: 513
    objectSid: ªl²p!Xù{®MZ
    accountExpires: 9223372036854775807
    logonCount: 267
    sAMAccountName: vinda
    sAMAccountType: 805306368
    userPrincipalName: vinda@ad.cs.sunysb.edu

    What happens if bind fails

    Am's picture

    $mesg = $ldap->bind("cn=admin,dc=leapster,dc=org", password=>"secret");

    I tried this, but even with a wrong password, this line did not give an error.

    you mean it still connects

    Anonymous's picture

    you mean it still connects to LDAP?

    This tutorial has helped me

    Ldap_guy's picture

    This tutorial has helped me created a whole project in one week. This was exactly what i needed! Good job

    use warnings; use strict;

    Anonymous's picture

    use warnings;
    use strict;

    perl-ldap vs. PerLDAP

    Anonymous's picture

    Not be be confused with another project, PerLDAP, which started back when Netscape was king. I had used PerLDAP for years, before perl-ldap even existed. It's now part of the Mozilla Foundation and is available here: http://www.mozilla.org/directory/perldap.html

    The one downside is that it requires the Netscape Directory SDK. But it's free and available for almost any platform.

    PerLDAP came after perl-ldap

    Anonymous's picture

    PerLDAP was originally Net::LDAPapi by Clayton Donley. Netscape announced taking over the module and renaming it PerLDAP at the second perl conference, which was held in San Jose in August 1998.

    perl-ldap (Net::LDAP) and Net::LDAPapi projects were both started in 1997 about the same time.

    PerLDAP can be difficult to get working though!

    Anonymous's picture

    I've been trying to get Bugzilla to work with PerLDAP, and while I can successfully compile the Netscape directory code, I can't get PerLDAP itself to compile - too many miconfigurations it appears, maybe some problems with versioning b/w PerLDAP & Netscape's SDK. Anyway, I gave up, and I now use Paul's patch to bugzilla which allows bugzilla to work with Net::LDAP. Cheers Paul.

    Post new comment

    • Allowed HTML tags: <a> <em> <strong> <cite> <code> <pre> <ul> <ol> <li> <dl> <dt> <dd> <i> <b>
    • Lines and paragraphs break automatically.
    • Use to create page breaks.

    More information about formatting options