OpenLDAP and Perl
I recently deployed a slapd(8) instance, and immediately recognized the need for a robust directory entry management utility. After doing some reading and experimentation, I discovered that Perl - and a few accompanying modules - handle the job nicely. What I'm sharing here is a brief, rather disjointed synopsis and some code samples.
In order to understand and use the samples, you should be comfortable with Perl and the installation of modules. And you'll certainly want a good fundamental understanding of LDAPv3 itself. (Both are outside the scope of this post.)
References
Useful Lingo
Before we get started, there are some acronyms and other terminology that you should become familiar with. This list is very limited, and is no substitute for a strong understanding of the object class you will be working with (e.g. inetOrgPerson).
Connecting to your LDAP service with Perl
The first example utilizes Net::LDAP to connect to a localhost instance. Note the bind->code check; this is generally important for error handling after Net::LDAP operations.
Connecting to the same localhost instance over TLS (assuming it's configured and available) is only slightly different. We simply use Net::LDAPS instead.
Performing a search using Perl
In order to ensure that we're writing proper and efficient search criteria, a quick look at search scope first.
In the code example that follows, search scope is specified in the Net::LDAP->search( scope => ... ) value.
Some points to note about the code above: The Beverly Xin cn is a child of ou=users, so we use the "one" scope. We are able to unbind() after $ldap_conn->search(), because the results (if any) are stored in the $search object.
Adding a new entry using Perl
This example provides a way to add users without using LDIF entries. Note the error checking via Net::LDAP->code.
Deleting an entry using Perl
This example deletes the user we just created - referring to the entry by dn.
Modifying entry attributes using Perl
Modifying entries is slightly more complicated. At least three types of modifications can be made:
The following example provides a look at all three operations.
This approach is not the only (or necessarily the best) way to modify entries. The Net::LDAP module also provides an excellent interface through Net::LDAP::Entry.
In order to understand and use the samples, you should be comfortable with Perl and the installation of modules. And you'll certainly want a good fundamental understanding of LDAPv3 itself. (Both are outside the scope of this post.)
References
- LDAP System Administration, by Gerald Carter, 2003 O'Reilly
- An Introduction to perl-ldap
- Net::LDAP examples
Useful Lingo
Before we get started, there are some acronyms and other terminology that you should become familiar with. This list is very limited, and is no substitute for a strong understanding of the object class you will be working with (e.g. inetOrgPerson).
Code:
| | TERM | DESCRIPTION | EXAMPLE -------------------------------------------------------------------------------------- | Distinguished name of an LDAP entry. | dn: dn | (Uniquely identifies it.) | uid=tia,ou=users,dc=shushu,dc=local | | -------------------------------------------------------------------------------------- | Relative distinguished name of an LDAP | rdn: rdn | entry. (Think of 'rdn' as relative name, | uid=tia | vs. 'dn' as fully-qualified name.) | -------------------------------------------------------------------------------------- | Domain component. For instance, in the | dc: dc | root DN "dc=shushu,dc=local", the first | shushu | domain component is "shushu". | -------------------------------------------------------------------------------------- | Common name. An entry attribute that, | cn: cn | for user entries, is often first and | Tia Sato | last name. | -------------------------------------------------------------------------------------- | User account ID. Another entry attribute | uid: uid | often used to identify users. | t.sato | |
The first example utilizes Net::LDAP to connect to a localhost instance. Note the bind->code check; this is generally important for error handling after Net::LDAP operations.
Code:
#!/usr/bin/perl use warnings ; use strict ; use Net::LDAP ; my $auth_account = qq/cn=Mr Admin,dc=shushu,dc=local/ ; my $auth_pass = qq/good.nice.password/ ; # Connect to the OpenLDAP daemon my $ldap_conn = Net::LDAP->new("localhost", port => 389, timeout => 15) or die "Unable to connect to LDAP server: $@" ; # Login (aka "bind") to the directory my $bind = $ldap_conn->bind($auth_account, password => $auth_pass ) ; # Check code for success or failure (0 is success) if($bind->code) { die $bind->error_name . ': ' . $bind->error_text ; }
Code:
... use Net::LDAPS ; ... # Connect to the OpenLDAP daemon my $ldap_conn = Net::LDAPS->new("localhost", port => 636, timeout => 15) or die "Unable to connect to LDAP server: $@" ;
In order to ensure that we're writing proper and efficient search criteria, a quick look at search scope first.
- sub -- Recursively search all subtrees / entries below the specified entity
- one -- Search only the immediate children of the specified entity
- base -- Search only the specified entity itself
In the code example that follows, search scope is specified in the Net::LDAP->search( scope => ... ) value.
Code:
# Code assumes active, authenticated Net::LDAP session in $ldap_conn.. my $cn = 'Beverly Xin' ; my $search = $ldap_conn->search( base => "ou=users,dc=shushu,dc=local", scope => "one", filter => "cn=$cn") ; # Unbind when finished with the query $ldap_conn->unbind() ; print $search->error_name . ': ' . $search->error_text . "\n" ; foreach($search->entries) { print $_->ldif() ; }
Adding a new entry using Perl
This example provides a way to add users without using LDIF entries. Note the error checking via Net::LDAP->code.
Code:
# Code assumes active, authenticated Net::LDAP session in $ldap_conn.. my $dn = 'cn=Sasha Lee,ou=users,shushu,dc=local' ; my $cn = 'Sasha lee' ; my $sn = 'Lee' ; my $uid = 's.lee' ; my $mail = 'sasha.lee@shushu.local' ; my @dept = ['Info Tech', 'Financial'] ; my $objclass = 'inetOrgPerson' ; my $add = $ldap_conn->add( $dn, attr => [ 'cn' => $cn, 'sn' => $sn, 'uid' => $uid, 'mail' => $mail, 'departmentNumber' => @dept, 'objectClass' => $objclass ] ) ; # add() failure checking ('code' is 0 upon success) if($add->code) { die $add->error_name . ': ' . $add->error_text ; }
This example deletes the user we just created - referring to the entry by dn.
Code:
# Code assumes active, authenticated Net::LDAP session in $ldap_conn.. my $dn = 'cn=Sasha Lee,ou=users,shushu,dc=local' ; my $delete = $ldap_conn->delete($dn) ; # delete() failure checking ('code' is 0 upon success) if($delete->code) { die $delete->error_name . ': ' . $delete->error_text ; }
Modifying entries is slightly more complicated. At least three types of modifications can be made:
- Attributes can be added
- Attributes can be deleted
- Attributes can be changed
The following example provides a look at all three operations.
Code:
# Code assumes active, authenticated Net::LDAP session in $ldap_conn.. my $dn = 'cn=Crystal Zhao,ou=users,dc=shushu,dc=local' ; # Replace attribute (i.e. change an existing value to a new value) my $mod = $ldap_conn->modify($dn, replace => { 'mail' => 'hmm@localhost' } ) ; # Status checking ('code' is 0 upon success) if($mod->code) { die $mod->error_name . ': ' . $mod->error_text ; } # ------------------------------------------------------------------- # # Add attribute values $mod = $ldap_conn->modify($dn, add => { 'departmentNumber' => ['Unknown', 'Payroll'] } ) ; # Status checking ('code' is 0 upon success) and commit if($mod->code) { die $mod->error_name . ': ' . $mod->error_text ; } # ------------------------------------------------------------------- # # Delete attribute by value $mod = $ldap_conn->modify($dn, delete => { 'departmentNumber' => 'Unknown' } ) ; # Status checking ('code' is 0 upon success) and commit if($mod->code) { die $mod->error_name . ': ' . $mod->error_text ; } # Alternatively, we can delete all attribute values $mod = $ldap_conn->modify($dn, delete => [ 'departmentNumber' ] ) ; # Status checking ('code' is 0 upon success) and commit if($mod->code) { die $mod->error_name . ': ' . $mod->error_text ; }