dcsimg

Using Perl and LDAP

A walkthrus of the basics: making connections, creating and modifying entries, and searching.

So, you have your LDAP server all set up and purring along happily — great! You can if you want perform all your interactions with LDAP — adding, deleting, and modifying records — by writing LDIF files and using the command line commands. And sometimes that’s fine. But it can also be very useful to be able to script those interactions: for example, to create a script which will take arguments and then add a new user.

What you need at this point is perl-ldap — a collection of Perl modules providing an OO interface to LDAP servers. Unlike some other perl/LDAP implementations, perl-ldap doesn’t require a C compiler, but runs as straight perl, making it more cross-platform compatible.

It is under active development, and a reasonably recent version is available packaged for most distros (e.g. libnet-ldap-perl for Debian/Ubuntu). Or you can install it from CPAN with the command perl -m CPAN -e "install perl-ldap".

In the following, I’ll construct a script to add a new user. I’ll assume that you’re familiar with basic perl programming and with perl OO — there are plenty of resources available online if not. I’m also not going to give the command-line parsing parts — assume that any undeclared variables have been taken from the command line! I also haven’t tackled the business of authentication, which may be needed for the parts of this which go beyond just searching.

Making a connection

Net::LDAP and Net::LDAPS are the modules which handle connecting and talking to the server. Net::LDAP deals with regular LDAP connections, and Net::LDAPS with LDAPS (secure) connections (although Net::LDAP also has options to force an ldaps:/// connection if you prefer).

The following code connects to an LDAP server, makes an anonymous bind, performs a search to look for our new username (to see if it already exists), and takes the session down:

use strict;

# Use whichever module matches your server
#use Net::LDAP;
use Net::LDAPS;

# Plain LDAP version if you prefer this to the LDAPS version
# my $ldap = Net::LDAP->new( 'ldapserver.example.com <http://ldapserver.example.com>');

my $ldap = Net::LDAPS->new( 'ldapserver.example.com <http://ldapserver.example.com>'
                             verify => 'optional',
                             cafile => '/etc/ldap/cacert.pem' ) or die $@;
my $mesg = $ldap->bind;

$mesg = $ldap->search( base    => "ou=people,dc=example,dc=com",
                       filter  => "(uid=$username)",
        );
$mesg->code && die $mesg->error;

# Currently we're not doing anything with the returned search values - we'll
# look at that later.

$mesg = $ldap->unbind;

If using ldaps:/// you will need the CA certificate for the CA which signed your server’s certificate. For a proper CA (as opposed to self-signed), you should be able to use the capath (rather than cafile) attribute to set the directory where CA certificates live rather than needing to give a specific file.

The above does an anonymous bind. If you want to bind with a specific DN (for example, to authenticate as your admin user, you can give extra options:

$mesg = $ldap->bind( "cn=root,dc=example,dc=com", password => "mypasswd" );

You can also use a SASL mechanism by giving an Authen::SASL object as an argument:

$mesg = $ldap->bind( "cn=root,dc=example,dc=com", sasl => $sasl );

If you’re using Kerberos for authentication, you’ll need to kinit as the appropriate user before running the script, or prompt for this during the script.

When searching you can specify various attributes — as you can when using ldapsearch. e.g.:

$mesg = $ldap->search(  filter => "(uid=jkemp)",
                        base   =>"ou=people,dc=example,dc=com",
                        attrs  => ['uid', 'cn', 'homeDirectory'] );

would return the uid, cn, and homeDirectory of the user jkemp.

Remember to take down the session when you’re finished, with the unbind operation. The further pieces of code I’m going to look at would all need to be put before that unbind line.

Searching the directory

Net::LDAP::Search provides a container for the results of an LDAP search, and Net::LDAP::Entry is the object that holds each individual result. You can retrieve an entry by number from the Net::LDAP::Search container; sort the entries; pop off an entry at a time; return an array of all the Net::LDAP::Entry objects found by the search; and use various other methods.

In the case of our example above, when finding out if a username already exists, all you actually need is to find the number of entries returned. If it’s 0, we’re in business. Thus:

$mesg = $ldap->search( base    => "ou=people,dc=example,dc=com",
                       filter  => "(uid=$username)",
        );
$mesg->code && die $mesg->error;

my $searchResults = $mesg->count;
die "Error! Username already exists!" unless $searchResults == 0;

will do the trick. The count method gives the number of entries returned. $mesg here is a Net::LDAP::Search object.

To look at the entries returned, you use the Net::LDAP::Entries methods. In the case of our script, we want to find the next free user ID:

$mesg = $ldap->search ( base   => "ou=people,dc=example,dc=com",
                        attrs  => [ 'uidNumber' ],
                       );
my @entries = $mesg->sorted('uidNumber');
my $entry = pop @entries;
my $newuid = $entry->get_value( 'uidNumber' );
$newuid++;

Here we get all the users, sort them by uidNumber, and then pop the highest one off the stack. Our next free uidNumber is the value for that entry, plus one.

There are also methods to return the Nth entry from a search; but in this case we don’t use them because the order of our results matters (we want to get the highest existing userID and then add one).

Adding entries and using LDIF

The $ldap->add method is the most straightforward for adding an entry.

$result = $ldap->add("uid=$userid,ou=people,dc=example,dc=com",
    attr => [ 'cn'            => $realname,
     'uid'           => $userid,
     'uidNumber'     => $newuid,
     'mail'          => '$userid@example.com <http://example.com>',
     'homeDirectory' => '/home/$userid',
     'objectclass'   => [ 'person', 'inetOrgPerson',
     'posixAccount' ]
           ]
   );

This would add a user with attributes set as above. Attributes with multiple values (as with objectclass above) should use a list.

Alternatively, you could use the Net::LDAP::Entry object to write directly to the LDAP directory:

my $entry = Net::LDAP::Entry->new();

# set DN
$entry->dn("uid=$userid,ou=people,dc=example,dc=com");

# You can add attributes all at once, or in as many operations as you like
$entry->add (
  'cn'            => $realname,
  'uid'           => $userid,
  'uidNumber'     => $newuid,
  'mail'          => '$userid@example.com <http://example.com>',
  'homeDirectory' => '/home/$userid',
  'objectclass'   => [ 'person', 'inetOrgPerson',
      'posixAccount' ]
 );

# Then update the LDAP server
$entry->update( $ldap );

Modifying entries

Net::LDAP also enables you to modify entries. For example, let’s say that all the company email addresses have just changed from userid@example.com <mailto:userid@example.com> to userid@mail.example.com <mailto:userid@mail.example.com> (although moving in the other direction would probably be more sensible!).

There are two ways of doing this: with the Net::LDAP->replace method, or with Net::LDAP::Entry. Let’s look at both.

This script could find all the users with email addresses listed, and alter them all. (NB this doesn’t check that the existing email address is of the standard format, which in practice you would almost certainly want to do.).

my $mesg = $ldap->search( base    => "ou=people,dc=example,dc=com",
       filter  => "mail=*",
       attrs   => [ 'uid', 'mail' ],
                        );
my @entries = $mesg->entries;

# This uses basic Net::LDAP methods.
# You can also specify the DN if you prefer, rather than using a
# Net::LDAP::Entry object

foreach my $entry ( @entries ) {
	$mesg = $ldap->modify( $entry, replace => { 'mail' => '$uid@mail.example.com <http://mail.example.com>' } );
}

# Alternatively, this uses the Net::LDAP::Entry replace method.

foreach my $entry ( @entries ) {
	$entry->replace(
        mail => "$uid\@mail.example.com <http://mail.example.com>",
	);
	$entry->update( $ldap );
}

Note that for the last piece of code, you need to call the update method — without this, changes remain local and aren’t actually passed to the server.

Deleting entries

In the same way, you can delete an entry by DN. So either of these two snippets would work:

$mesg = $ldap->delete("uid=$userid,ou=people,dc=example,dc=com");

my $entry = Net::LDAP::Entry->new;
# Need to specify the DN of the entry
$entry->dn("uid=$userid,ou=people,dc=example,dc=com");
$entry->delete;
$entry->update( $ldap );

In fact, this is one situation in which you may wish to use LDIF. For example, you might want to read in from an LDIF file a list of entries to be deleted. Net::LDAP::LDIF will read and write LDIF files, using Net::LDAP::Entry objects and converting in either direction. This snippet reads in entries from a file, checks for errors in the reading process, and then deletes the entries:

use Net::LDAP::LDIF;

# Open a file for reading from - the r option
my $ldif = Net::LDAP::LDIF->new( "file.ldif", "r");

while( not $ldif->eof ( ) ) {
   $entry = $ldif->read_entry ( );
   if ( $ldif->error ( ) ) {
       print "Error msg: ", $ldif->error ( ), "\n";
       print "Error lines:\n", $ldif->error_lines ( ), "\n";
   } else {
	   $mesg = $ldap->delete($entry);
   }
 }

You can also write entries, if you want to export to an LDIF file. So, for another approach to our new user creation script:

use Net::LDAP::LDIF;

# Open a file for writing to - the w option
my $ldif = Net::LDAP::LDIF->new( "file.ldif", "w");

my $entry = Net::LDAP::Entry->new();

# set DN
$entry->dn("uid=$userid,ou=people,dc=example,dc=com");

# You can add attributes all at once, or in as many operations as you like
$entry->add (
    'cn'            => $realname,
	'uid'           => $userid,
    'uidNumber'     => $newuid,
    'mail'          => '$userid@example.com <http://example.com>',
    'homeDirectory' => '/home/$userid',
    'objectclass'   => [ 'person', 'inetOrgPerson',
                         'posixAccount' ]
 );

# Write the entry to the LDIF file
$ldif->write_entry($entry);

After this, you could use ldapmodify to alter the LDAP directory, or move it to another LDAP directory.

Summary

This is a brief introduction to the options available for and uses of this set of perl modules — the documentation available on the project Web site gives full details and also has further examples available.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62