ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


Using libldap, the LDAP Client Library
Pages: 1, 2

Compiling the Example

To compile the code, you may need to tell gcc where the LDAP header and library files are.



You will also need to link with the LDAP and BER libraries:

$ gcc -o ldap_add ldap_add.c -I/usr/local/include/ -L/usr/local/lib -lldap -llber

Run the example with:

$ ./ldap_add

If the program compiles and links but does not run, you may need to add /use/local/lib to your $LD_LIBRARY_PATH, or else pass -Wl,rpath,/usr/local/lib to the gcc command line. Thanks to Halvard Furuseth for pointing this out.

Verifying the Add Operation

To verify that the user has been added into the directory, we can use the ldapsearch utility:

$ ldapsearch -h localhost -x -LL -b 'dc=example,dc=com' '(objectClass=inetOrgPerson)'

dn: cn=Rory Winston,ou=Developers,dc=example,dc=com
cn: Rory Winston
sn: Winston
givenName: Rory
uid: rwinston
title: Internet Developer
objectClass: inetOrgPerson
ou: Development

The -b flag means "search using the base DN dc=example,dc=com." The -LL flag means "display the results in LDIF format, without comments." The -x flag signifies that we will be using simple authentication.

We could also use a graphical LDAP browser, such as the Java-based LDAP Browser shown in Figure 1. LDAP Explorer Tool is another good open source LDAP explorer.

a graphical LDAP Browser
Figure 1. A graphical LDAP Browser

Searching the Directory

There are four basic operations we can invoke on an LDAP directory server: add, delete, search, and modify. We'll tackle searching next. From our point of view, the libldap API provides three basic methods of searching: synchronous, synchronous with timeout, and asynchronous. For simplicity, we'll use the synchronous search method provided by ldap_search_s.

The ldap_search_s function is defined as follows:

int ldap_search_s(LDAP* ld, char* base, int scope, char* filter,
    char* attrs[], int attrsonly, LDAPMessage** res)

where base is the base DN to search on, and scope specifies the depth of the search. scope can be LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, or LDAP_SCOPE_SUBTREE, as we saw with the ldapsearch utility. The filter paramter is the actual search filter; e.g., (objectClass=*). The attrs parameter is a NULL-terminated array of attribute types to return from any matching entries that are returned by the search filter. If NULL is specified here, then all attributes are returned. The attrsonly parameter can be 0 or 1. A value of 1 signifies that we want to return attribute types only, where a value of 0 means that we want to return both attribute types and attribute values. The final parameter is an LDAPMessage**, a pointer to a structure that contains details about an individual LDAP directory entry. The LDAP library will allocate memory for these structures itself. It is up to the caller to free the memory when they are finished.

Actually performing the search is simple:

/* search from this point */
char* base="ou=developers,dc=example,dc=com";

/* return everything */
char* filter = "(objectClass=*)";
     
if (ldap_search_s(ld, base, LDAP_SCOPE_SUBTREE, filter, NULL, 0, &msg)
   != LDAP_SUCCESS) {
    ldap_perror( ld, "ldap_add_s" );
}

Processing LDAP Search Results

A successful search of the directory will return one or more LDAPMessage objects. The number of LDAPMessage objects returned can be obtained using the ldap_count_entries function, as shown below:

int num_entries_returned = ldap_count_entries(ld, msg);

In order to process the returned entries, the standard idiom is to use a for loop:

for (entry = ldap_first_entry(ld, msg); entry != NULL;
    entry = ldap_next_entry(ld, entry)) {
    /* process entry here ... */
}

The first entry returned is given by ldap_first_entry(). Each subsequent entry is given by ldap_next_entry, and we keep looping until we encounter a NULL entry, which signifies the end of the result set.

The Distinguished Name (DN) of an entry can be extracted using the ldap_get_dn() function.

We can extract the attributes from a directory entry using a similar mechanism that we use to get the entries themselves, using the ldap_first_attribute and ldap_next_attribute functions. A sample loop to process all of the attributes for an LDAP entry resembles:

for( attr = ldap_first_attribute(ld, entry, &ber); attr != NULL;
    attr = ldap_next_attribute(ld, entry, ber)) 
    {
    /* process attributes here ... */
}

The ldap_first_attribute() and ldap_next_attribute() functions take an extra parameter: an object of type BerElement**. A BERElement represents data encoded using the Basic Encoding Rules (BER), a binary transfer syntax used by LDAP. The LDAP library uses a BerElement object internally to keep track of where it is in the attribute list. Even though the LDAP library will allocate memory for the BerElement, it is up to you to free the memory using ber_free().

Finally, we need to retrieve the actual attribute values themselves. This is done by calling the ldap_get_values() function. This will initialize a NULL-terminated array of values that we can use, like so:

if ((vals = ldap_get_values(ld, entry, attr)) != NULL)  {
    for(i = 0; vals[i] != NULL; i++) {
        /* process the current value */
        printf("%s:%s\n", attr, vals[i]);
    }
}

In our trivial example program, we just print out the current attribute name and value. It is important to remember to clean up the values array when you are finished, using ldap_value_free(), and also the attribute itself, using ldap_memfree(). The full sample code can be found here.

Modifying Entries

The next major operation that we need to cover is the modify operation. The modify operation is conceptually very similar to the add operation -- define the DN to be modified, define the attribute(s) to be modified, added, or deleted within the entry, and then call ldap_modify_s() with the relevant parameters. We'll modify the title attribute. Here are the relevant code snippets:

/* DN to be modified */
char *user_dn = "cn=Rory Winston, ou=Developers, dc=example, dc=com";

/* Replacement value for title attribute */ 
char *title_values[] = {"Internet Development Manager", NULL};

LDAPMod title;
LDAPMod *mods[2];

/* Initialize the attribute, specifying 'REPLACE' as the operation */
title.mod_op     = LDAP_MOD_REPLACE;
title.mod_type   = "title";
title.mod_values = title_values;

/* Fill the attributes array (remember it must be NULL-terminated) */
mods[0] = &title;
mods[1] = NULL;

/* ....initialize connection, etc. */

if (ldap_modify_s(ld, user_dn, mods) != LDAP_SUCCESS) {
    ldap_perror( ld, "ldap_modify_s" );
}

See the code for more information.

Deleting Entries

Deleting entries is trivial -- simply provide the DN of the object to be deleted to the ldap_delete_s() call for a synchronous delete, or to ldap_delete() for asynchronous operation. The ldap_delete() function takes two parameters, the LDAP connection context and a DN. A simple delete is:

LDAP* ld;
char* dn = "cn=Rory Winston,ou=Developers,dc=example,dc=com";

/* ...LDAP connection and initialization */

if (ldap_delete_s(ld, dn) != LDAP_SUCCESS) {
    ldap_perror( ld, "ldap_delete" );
}

It's as simple as that. See the accompanying code for a full listing.

Asynchronous Operations

So far we have been using synchronous directory operations, for simplicity. Now we will examine an asynchronous search operation using ldap_search(). In libldap, whenever an asynchronous operation is commenced, the library returns to the caller a cookie, or message ID, that uniquely identifies that operation throughout the client session. This ID can later be passed to the ldap_result() function to get the status of the operation and any result(s) that may have been returned. Figure 2 shows the basic flow:

asynchronous operation flow
Figure 2. Asynchronous operation flow

Let's take a simple search operation as an example. The prototype for ldap_search is as follows:

int ldap_search(LDAP* ld, char* base, int scope,
    char* filter, char* attrs[], int attrsonly);

This is very similar to the ldap_search_s() function, except we don't pass in any LDAPMessage structures to be initialized with the search results. After all, this is an asynchronous operation, and we will retrieve the results later. The return value of ldap_search(), if successful, is a unique ID for the operation that we may use later to retrieve any results that have been returned.

The ldap_result() function prototype looks like:

int ldap_result(LDAP* ld, int msgid, int all, struct timeval* timeout,
    LDAPMessage** result)

msgid is an ID that we have previously retrieved from a call to an asynchronous operation. result is an LDAPMessage** parameter that will be allocated by the LDAP library if there are any results to return, exactly as in ldap_search_s().

The timeout parameter is a pointer to a struct timeval. This specifies whether the call to ldap_result() should block or not, and if it does, how long to block. Passing a NULL pointer here will tell ldap_result() to block indefinitely until the specified asynchronous operation identified by msgid has completed. Otherwise, we can fill in the timeval structure with values specifying how long we want to wait. This behavior is related to the fact that ldap_result() uses select() as its underlying asynchronous notification mechanism. For more details, see man select and man ldap_result. For our example, we will just pass a NULL pointer for the timeout parameter, telling ldap_result() to block until the search operation has completed.

The last parameter is all. This flag only has meaning for search operations. It allows you to retrieve multiple search entries from a search query via multiple calls to ldap_result(). Each call to ldap_result() will retrieve a single entry. In our example, we set all to 0, signifying that we want ldap_result() to return the entire result set when the search operation has completed.

Here is a short asynchronous search example:

/* 
 *
 * Perform the asynchronous search
 * ldap_search() returns -1 if there is an error, otherwise the msgid
 *
 */
if ((msgid = ldap_search(ld, base, LDAP_SCOPE_SUBTREE, filter, NULL, 0)) == -1) 
{
    ldap_perror( ld, "ldap_search" );
}

/* ... some time later */

result = ldap_result(ld, msgid, 1, NULL, &msg);

switch(result)
{
    case(-1):
        ldap_perror(ld, "ldap_result");
        break;
    case(0):
        printf("Timeout exceeded in ldap_result()");
        break;
    case(LDAP_RES_SEARCH_RESULT):
        printf("Search result returned\n");
        break;
    default:
        printf("result : %x\n", result);
        break;
}

In case of an error, ldap_result() returns -1. If we have exceeded the timeout specified in the timeval parameter, ldap_result() returns 0. In our case, we are looking for a return result of LDAP_RES_SEARCH_RESULT, which means that our search operation has completed. The results are contained in the msg parameter.

Wrapping Up

This brings us to the end of our quick introduction to libldap. As you can see, once you learn a few basic operations, it is quite simple to use. Our examples were written in C, but there are LDAP client libraries for many languages, including Java and Perl. Using C gives us power and speed, at the cost of manual memory management and added complexity.

Acknowledgments

Thanks to Hallvard Furuseth for his invaluable help and editorial and technical comments while reviewing this article.

Related Links

Rory Winston is a solutions architect with Checkfree i-Solutions, one of the world's leading EBPP providers. He specializes in J2EE technologies on Linux and BSD platforms .


Return to the Linux DevCenter.





Sponsored by: