| < Day Day Up > |
|
The Perl language has grown in scope and popularity over the last ten years. The first Perl manual, "Programming Perl," was written by its creators Larry Wall and Randal L. Schwartz in 1991. Since then, Perl has developed object-oriented features and has advanced from version 4.0 to version 5.8. You can download it from CPAN (Comprehensive Perl Archive Network). CPAN provides an overwhelming number of libraries for this extremely powerful programming language. The invaluable advantage of Perl is its pattern-matching functions. These functions allow you to write in one line of code what would require ten lines in the C language. The language is interpreted, [1] and this makes it a rapid prototype development tool. Note that there is also a Perl-to-C compiler available.
As with all open-source software, Perl too has a library for LDAP. Actually, it has multiple libraries from which you can choose. If you search on the Internet it is easy to get confused, because sometimes the same software is known under different names. There are substantially three main threads:
Net::LDAPapi from Clayton Donley, outdated (only LDAP [v2]), but still heavily used
PerlLDAP available from http://www.mozilla.org/directory/perldap.html
Net::LDAP bundle from Graham Barr, also available from Source-ForgeNet with the name "perl-ldap," at http://perl-ldap.source-forge.net
In this section, we will use the third option, Net::LDAP. The first two options rely on C, but Net::LDAP is completely native Perl code, so you need only Perl to make it work. However, the differences between the different libraries are not so great, so if you have some idea of the Perl language you should be able to use a different Perl library. Actually, the term "library" is not technically correct because most of these "libraries" use the object-oriented aspects of Perl. The term we should use for all the examples in this section on Perl is "package." If we use these terms interchangeably in this chapter, bear in mind that "package" is the correct term.
There are some interesting libraries worth checking out if you are interested in LDAP programming with Perl. One is the Tie-LDAP library from Taisuke Yamada, which allows you to treat an LDAP directory just as an associative array. The other is the DBD-LDAP library from Jim Turner. This library allows you to access a directory as if it were a database by using the DBI (database interface) and speaking SQL (structured query language).
Like every other API, the Perl API follows also the afore-mentioned procedure, i.e.:
Connecting to the LDAP server
Binding to the server
Doing some work
Unbinding to release the resources
As with the other APIs, let us first look at a real-life program, this time written in Perl. Exhibit 29 shows a short Perl program using the Net::LDAP module. If you have read the previous section covering PHP and LDAP, you will see the same concepts presented here.
01 #!/usr/local/bin/perl -w 02 use Net::LDAP ; 03 04 $User = $ARGV[0] ; 05 print "Searching: $User \n" ; 06 07 local $Base = "ldap_abc.de" ; 08 local $Filter = "uid=$User" ; 09 local $Host = "localhost" ; 10 11 $ldap = Net:: LDAP->new($Host) or die "$@" ; 12 $ldap->bind | | die "Could not bind to $Host" ; 13 $result = $ldap->search (base => $Base, filter => $Filter); 14 $result->code && die $result->error ; 15 if ($result->count == 0) { 16 print "NO ROW FOUND \n" ; 17 } 18 foreach $entry ($result->all_entries) { 19 $entry->dump; 20 } 21 $ldap->unbind ;
First you create an LDAP object specifying the host you will connect to. This allocates a structure in memory for you. With this LDAP object, you bind to the LDAP server. In this case, we bind anonymously. The search method delivers a result object. In the case of errors (the code () method has a return value not equal to 0) methods unequal 0), the method "error()" describes what went wrong. You can use a number of methods on the result object: "count()" delivers the number of entries satisfying the search, and "all_entries" delivers the whole array of entries. The "dump" method applied on a single entry dumps out all its attributes, including the distinguished name. In the rest of this section, we will review the most important objects in the Perl API and its methods.
There are six objects you will use in a Perl program. These objects are:
ldap: The base object that executes nearly all operations on the directory. You will find all the methods described in the functional model in Chapter 3. This object also provides access to the directory schema and the root_dse.
search: The search method on behalf the LDAP object returns a search object. This object is a container object for the search result. It offers methods such as the count on the total number of found entries and fetch methods to get single entries from the results set.
entry: This object lets you access the single attributes of an entry. An entry object can be created in two ways: first as the result of an explicit creation via the new operator, second via one of the fetch methods using the search object
message: The message objects lets you analyze errors reported by LDAP operations. It holds the error number and the error message in a human-friendly format. It also has the sync method that allows you to run asynchronous requests on the server.
reference: The reference object has only one method, "references," that displays a list of references returned by the directory server.
schema: The schema object allows you to explore the schema.
First of all, we have to create an LDAP object, and this happens via the new constructor. (Most object-oriented languages use a special method to construct a new object. This method is called "construction.") The constructor must specify the host you wish to connect to. You can furthermore specify a number of options.
$ldap = new Net::LDAP(<Host>, port => <Port>, timeout => <TimeOut>, async => (0,1), version => (2,3) );
Host, port, and timeout are straightforward, and the default value for timeout is 120 s. "Async 1" means that all the actions have to be executed asynchronously. With "version," you can specify whether you wish to use LDAP (v2), the default value, or LDAP (v3). There are still more parameters. For more information, refer to the manual pages of the Net::LDAP package.
The LDAP object recognizes the methods identified in the functional model plus some special ones.
Authentication/control are achieved by the bind, unbind, and abandon methods. The bind can be anonymous or authenticated via the user's distinguished name and password.
$Message = $ldap->bind([DN], password => <password>, control => <Control>, callback => <Callback>, sasl => <Sasl> );
We have already seen an anonymous bind, so let us see in Exhibit 30 an example of authentication using a distinguished name and password. The bind method has further arguments, which can be found in the manual pages of the Net::LDAP package. Some additional authentication types include:
no authentication: Indicates an anonymous connection, in which case you do not have to supply the DN
password: User supplies DN and the password
sasl: User binds using the SASL mechanism. You can obtain the argument via the Authen::SASL object. (See the Perl documentation for more details.)
$host = "ldap.AbcLdap.org" ; $port = 389 ; $DN = "cn=admin, o=LdapAbc.org" ; $pasword = "password1" ; $ldap = new Net::LDAP($host,port => $port); $ldap->bind($DN, password => $password, version => 3, ) || die "Could not bind to server"; . . . $ldap->unbind();
The unbind() method takes no argument at all. The abandon operation takes the messageID of a long-running command. This must be an asynchronous command because synchronous commands do not return a result until the command has been completed. The following example shows a typical abandon in action.
$message = $ldap->search(@SearchArgs); $ldap->abandon($message);
You could also write this abandon as a method acting upon the message object:
$message = $ldap->search(@SearchArgs); $message->abandon();
Here we have the usual search and compare methods. "Search" takes as arguments the search base, the scope, and the filter parameters. You can also specify the attributes to be returned.
$Message = $ldap->search (base => <base>, scope => <scope>, sizelimit => <sizelimit>, timelimit => <timelimit>, typesonly => (true|false), filter => <filter>, attrs => <attrs>, control => <control>, callback => <callback>, );
The relevant arguments are:
base: Search base
scope: (base, one, and sub)
filter: Search filter
timelimit: Limit in seconds; 0 means no time limit
sizelimit: Limit in entries to be returned; 0 means no limit
For the other parameters, see the online documentation.
Exhibit 31 shows an example of the search method. The other interrogation method is "compare()." It reports "true" if the entry identified with DN has the specified attribute with the specified value. Here is an example:
$DN = "uid=JParker, ou=IT, o=LdapAbc.org" ; if ($ldap->compare($DN, attr=>givenName, value=>"James") { printf("Found correct Person\n"); }
$base = "ou=IT, o=LdapAbc.org" ; $filter = "(sn=Parker)" ; $attributes = ["sn","cn","uid","mail"] ; $Message = $ldap->search (base => $base, filter => $filter, attrs => $attributes, ); $Message->code() && die "$Message->error()" ; foreach $entry ($mesg->all_entries) { $entry->dump; }
The update methods are add(), delete(), modify(), and moddn().
Add an Entry — There are two ways to add an entry:
Using the Net::LDAP::Entry object, as depicted in Exhibit 32.
use Net::LDAP::Entry ; $entry = new Net::LDAP::Entry(); . . . (see entry object later) $ldap->add($entry);
Specifying the attributes in the add() method, as seen in Exhibit 33.
$Msg = $ldap->add($DN, attrs => [ objectClass => ["top", "person", "organizationalPerson", "inetOrgPerson", ], sn => "Voglmaier", givenName => "Reinhard", cn => "Reinhard E. Voglmaier", uid => "RVoglmaier", mail => Rvoglmaier@LdapAbc.org", ], ); $Msg->code() && die "$Msg->error( )" ;
Delete an Entry — The delete method is straightforward. You need only the DN. See Exhibit 34 for an example.
$DN = "uid=JParker, ou=Marketing, o=LdapAbc.org" ; $Msg = $ldap->delete($DN); $Msg->code() && die "$Msg->error()" ;
Modify an Entry — Exhibit 35 is an example showing how to modify an entry in a directory. Some comments on the code:
The add and replace key needs a pointer to an associative array.
The delete key needs a pointer to an associative array if you wish to delete only an attribute with a specified value. This is used mostly for multivalue attributes. For example, the e-mail address could have multiple values. Here we delete the value at ldapabc.de.
$host = "ldap.AbcLdap.org" ; $port = 389 ; $BindDN = "cn=admin, o=LdapAbc.org" ; $BindPW = "password1" ; $ldap = new Net::LDAP($host,port => $port); $ldap->bind($BindDN, password => $BindPW, version => 3, ) || die "Could not bind to server"; $dn = "uid=JKahn,ou=Marketing,o=LdapABc.org" ; $ldap->modify($dn, add => [ telephonenumber => '428' ], ) ; $Msg->code() && die "$Msg->error()" ; $ldap->modify($dn, delete => [mail => 'JKahn\@ldapabc.de'], ); $Msg->code() && die "$Msg->error()" ; $ldap->modify($dn, delete => [telexnumber], ); $Msg->code() && die "$Msg->error( )" ; $ldap->modify($dn, replace =>[ fax => '827' ], ); $Msg->code() && die "$Msg->error( )" ; $ldap->unbind( ) ;
If the whole update refers to one distinguished name, you can use the shortcut "changes" as shown in Exhibit 36.
$Msg = ldap->modify($dn, changes => [ add => [ telephonenumber => '428' ], delete => [mail => 'JKahn\@ldapabc.de'], delete => [telexnumber], replace =>[ fax => '827' ], ], ); $ldap->unbind();
Modify Distinguished Name — To modify the distinguished name, you can use the moddn() method. As discussed in Chapter 3 when we discussed the functional model and in Chapter 4 when we saw the LDIF format, there are a number of possibilities for changing the distinguished name. You can change the distinguished name while keeping the old distinguished name. Otherwise, you have to specify "deleteoldrn." In LDAP (v3) you can move the entry in the directory by specifying "newsuperior." In our new example in Exhibit 37, Mr. Kahn moves from Marketing to Human Resources.
$dn = "uid=JKahn,ou=Marketing,o=LdapABc.org" ; $Msg = $ldap->moddn($dn, newrdn => "uid=JKahn", deleteoldrn => true, newsuperior => "ou=HR,o=LdapAbc.org", );
One of the new properties of LDAP (v3) is that it stores information about itself in the directory. That permits the client to obtain useful information about the directory and to adapt its actions on the directory structure it discovered. For example, can the client find out which version of LDAP the server uses (v2 or v3). The Net::LDAP object gives you two methods of retrieving information about the directory: the schema() method and the root_dse() method.
root_dse() — You get the root DSE simply by calling the root_dse() method. As a search method, you can supply a reference to the list of attributes to be returned.
$ListPtr = ["subschemaSubentry","supportedExtension"] ; $root_dse = $ldap->root_dse(attrs => $ListPtr);
The attrs with the pointer is optional
$root_dse = $ldap->root_dse();
If you omit the attrs option, the method gives you information about following variables:
subschemaSubentry namingContexts altServer supportedExtension supportedControl supportedSASLMechanisms supportedLDAPVersion
Schema() — The schema() methods returns the schema object. You will learn more about the schema object in the corresponding paragraph later in this chapter. Exhibit 38 shows how to explore the schema using Perl LDAP objects.
$schema = $ldap->schema(); # print out all objectClasses: foreach $OC ($schema->objectclasses()) { printf("%s \n",$OC); } # print out all attributes: foreach $AT ( $schema->attributes()) { printf("%s \n",$AT); }
Most of the methods accept a callback option. The value for the callback option should be a reference to a subroutine. Exhibit 39 shows an example of code registering the callback; Exhibit 40 shows the definition of the callback itself.
$base = "ou=IT, o=LdapAbc.org" ; $filter = "(sn=Parker)" ; $attributes = ["sn","cn","uid","mail"] ; $Message = $ldap->search (base => $base, filter => $filter, attrs => $attributes, callback => \$SearchCB(), );
sub SearchCB { my $mesg = shift; my $obj = shift; if (!$obj) { # Search complete # } elsif ($obj->isa('Net::LDAP::Reference')) { my $ref; foreach $ref ($obj->references) { # process ref } } else { # Process Net::LDAP::Entry } }
Remember that LDAP is a message-oriented protocol. The client sends a request to the server and gets one or more responses to its request. The subroutine referenced with callback will be called each time one of these responses arrives at the client. When the subroutine is called, it gets a message object as its first argument. If the method was a search, the second argument is a search object. If the server instead sends back a reference, the second argument is a reference object.
The search object is a container object that simply holds the result list of a query. There are several methods to access the entries satisfying the query, such as:
entries(): Returns an array of entries returned by the search
entry (INDEX): Returns entry with index number INDEX
shift_entry/pop_entry: Shifts/pops an entry from the internal result list
sorted([attribute list]): Returns the entries sorted by the attribute list
An interesting method is count(), which returns the number of entries found and references the reference objects returned by the directory server. Most of these methods will be seen in the next section.
The entry object has two functions:
Construct a new entry
Access the individual attributes on an existing entry
Let us create an entry and add it to the directory. Exhibit 41 shows you the Perl code that does the job.
#!/usr/bin/perl -w use Net::LDAP ; use Net::LDAP::Entry ; #Define some values: local $host = "127.0.0.1" ; local $port = 389 ; local $DN = "cn=admin, o=LdapAbc.org" ; local $pass = "password1" ; $ldap = Net: :LDAP->new($host, port => $port) or die "$@" ; printf("Server %s contacted, connection OK\n",$host); $ldap->bind($DN, password => $pass, version => 3) || die "Could not bind" ; $ObjectClasses = ["top", "person", "organizationalPerson", "inetOrgPerson"] ; $entry = new Net::LDAP::Entry(); $entry->dn("uid=SPalmer, ou=Marketing, o=LdapAbc.org"); $entry->add(objectClass => $ObjectClasses , sn => "Palmer" , givenName => "Stephen", cn => "Stephen Palmer", uid => "SPalmer", mail => "SPalmer\@LdapAbc.org", ); $mesg = $entry->update($ldap); $mesg->code && die $mesg->error ; $ldap->unbind();
Next, we modify some existing entries found via the search object mentioned in the previous section. However, we print out only the values we retrieve via the "get method" call, as shown in Exhibit 42.
# as the previous example: printf("Server %s contacted, connection OK\n",$host); $mesg = $ldap->search(base => "o=LdapAbc.org", filter => "(sn=P*)", ); my $max = $mesg->count(); printf("Found %d entries\n",$max); for($i = 0 ; $i < $max ; $i++) my $entry = $mesg->entry($i); printf("Distinct Name: %s\n",$entry->dn()); foreach my $attr ($entry->attributes) { printf("%s: %s\n",$attr, $entry->get_value($attr)); } }
As seen in the example, you can access the individual attributes via the get_value() method. You also can set the attributes via the set_value() method. The access to the distinguished name is different; you get and set it via the dn() method. You set the distinguished name if you give it as parameter to the dn() method. See Exhibit 41.
The entry object also offers all the methods required to update entries, i.e., delete, add, or modify attributes. Again, refer to the documentation of your installation.
The most important methods of the message object are: the code() method that is set to "1" if an error occurs and the error() method that gives you the error message. We have seen these methods in action in the previous examples.
The message object has two methods of controlling asynchronous requests:
sync(), to wait for the execution of the request
done(), to ask if the request has been completed
We have nothing new to add to the previous discussion of the reference object. The reference object is a container object to hold the references returned by the directory server.
The schema object is interesting, inasmuch as it allows you to explore the schema. Exhibit 43 shows how to use the schema object. As you see from Exhibit 43, the schema object contains the complete schema information. In the example, we get a list of all object classes, attributes, matching rules, matching rules use, and syntaxes.
#!/usr/bin/perl -w use Net::LDAP ; use Net::LDAP::Entry ; #Define some values: local $host = "127.0.0.1" ; local $port = 389 ; $ldap = Net::LDAP->new($host, port => $port) or die "$@" ; $schema = $ldap->schema(); # print out all objectClasses: printf("ObjectClasses:\n"); foreach $OC ($schema->objectclasses()) { printf("%s oid: %s\n",$OC, name2oid($OC)) ; } printf("Attributes:\n"); foreach $AT ($schema->attributes()) { printf("%s oid: %s\n",$AT, name2oid($AT)) ; } printf("Matching Rules:\n"); foreach $MR ($schema->matchingrules()) { printf("%s oid: %s\n",$MR, name2oid($MR)) ; } printf("Matching Rules:\n"); foreach $MRU ($schema->matchingruleuse()) { printf("%s oid: %s\n",$MRU, name2oid($MRU)) ; } printf("Syntaxes:\n"); foreach $SY ($schema->syntaxes()) { printf("%s oid: %s\n",$SY,name2oid($SY)) ; }
There are also functions to test whether a given element (object class, attribute, etc.) is part of the given schema. The methods is_objectclass, is_attribute, is_syntax, and is_matchingrule report "true" if the element is part of the schema and "false" if it is not.
In Exhibit 43, the method "name2oid" was used. This method prints out the oid of the given element. If you want all information available in the schema, you can simply dump it out using the dump() method, as shown in Exhibit 44.
#! /usr/bin/perl -w use Net::LDAP ; use Net::LDAP::Entry ; #Define some values: local $host = "127.0.0.1" ; local $port = 389 ; $ldap = Net::LDAP->new($host, port => $port) or die "$@" ; $schema = $ldap->schema(); $schema->dump();
You can also explore single entries to understand which attributes are required (the must() method) and which are optional (the may() method). Look at the lines in Exhibit 45 that do this for the entry inetOrgPerson. Note, however, that the "may" and "must" methods do not print out attributes inherited from the parent. You can get the name of the parent with the method "superclass" and, in this way, construct a program that really does dump out all attributes.
#! /usr/bin/perl -w use Net::LDAP ; use Net::LDAP::Entry ; #Define some values: local $host = "127.0.0.1" ; local $port = 389 ; $ldap = Net::LDAP->new($host, port => $port) or die "$@" ; $schema = $ldap->schema(); for $attribute ($schema->must("inetOrgPerson")) { $MustList .= $attribute ; } for $attribute ($schema->may("inetOrgPerson")) { $MaytList .= $attribute ; } printf("Must: %s\n", $MustList); printf("May: %s\n", $MayList); printf("Parent: %s\n", $schema->superior());
In this section, we have seen the Perl library and learned that it provides a very comfortable interface to directory servers. Perl is often used in common gateway interface (CGI) scripts, which makes it easy to write gateways to access directory services via the HTTP protocol. Moreover, Perl is available on a wide variety of platforms, and many third-party tools offer Perl libraries, allowing you to connect directory services over a broad range of platforms and applications. Perl also has libraries to access RDBMS (Oracle, Informix, ODBC, DBM, and many others), so you can write connectors between your database applications and your directory server.
Like the LDAP protocol, the LDAP Perl interface has to be considered a work in progress. We could not include all methods available, as this would be beyond the scope of this book.
A last word of advice before closing this section: When you are developing LDAP software using the Perl libraries, it is a good idea to have a look at the CPAN Web site. There, under Net::LDAP, you can find a number of useful scripts that may be helpful in resolving problems you may encounter. The following section lists some examples. By the time you read this book, there may be many more, as the CPAN site is continuously evolving.
jpegDisplay.pl
A script to display a jpeg picture from the jpegPhoto attribute of an LDAP directory entry
Author: Clif Harden <"><charden@pobox.com>>
jpegLoad.pl
A script to load a jpeg picture into the jpegPhoto attribute of a directory entry
Author: Clif Harden <"><charden@pobox.com>>
ldifdiff.pl
Generates LDIF "change diff" between two sorted LDIF files
Author: Kartik Subbarao <"><subbarao@computer.org>>
ldifsort.pl
Sorts an LDIF file by the specified key attribute. The sorted version is written to standard output.
Author: Kartik Subbarao <"><subbarao@computer.org>>
ldifuniq.pl
Culls unique entries from a reference file with respect to a comparison file
Author: Kartik Subbarao <"><subbarao@computer.org>>
[1]An "interpreted" language as opposed to a "compiled" language is a language that does not need to be compiled in order to be executed. Because compilation takes time, the development of programs in interpreted language is faster. Execution, however, is slightly slower.
| < Day Day Up > |
|