Linux DevCenter    
 Published on Linux DevCenter (http://www.linuxdevcenter.com/)
 See this if you're having trouble printing code examples


Using libldap, the LDAP Client Library

by Rory Winston
08/14/2003

LDAP, the Lightweight Directory Access Protocol (see RFCs 1777 and 2253), has become the de-facto standard for enterprise directory services. Major enterprise directories such as NDS and Active Directory have LDAP interfaces. The libldap API is a library that supports LDAP functionality over TCP, SSL, or IPC -- see the OpenLDAP site for detailed documentation.

This short tutorial will demonstrate how to perform simple operations using libldap. We will use the freely available open source server OpenLDAP. For an excellent introduction to OpenLDAP, check out the documentation on the web site. For reference, the version of OpenLDAP that I am using for this article is 2.1.17, and the latest version at the time of writing was 2.1.22. This article assumes you know the basics of LDAP and some C programming. Luke A. Kanies' "Getting Started with LDAP" is a good introduction to the former.

Motivation

In this article, we have the task of creating an employee information database that contains such information as employee name, job title, and department. We will use LDAP to store this basic employee information for our company. Using an LDAP repository allows us to easily retrieve and change the data. We will write our data-lookup modules in C, in order to integrate with an existing application. Without further ado, let's set up our LDAP information store.

slapd Configuration

For the purposes of this tutorial, we will create an imaginary domain, example.com, for our LDAP directory. The relevant portions of slapd.conf are shown below:

# Include schema definition files 
# These define the built-in objectClasses and attributes we may use
include        /usr/local/etc/openldap/schema/core.schema
include        /usr/local/etc/openldap/schema/cosine.schema
include        /usr/local/etc/openldap/schema/inetorgperson.schema

schemacheck on

pidfile    /usr/local/var/slapd.pid
argsfile    /usr/local/var/slapd.args

#######################################################################
# Database definitions
#######################################################################

# Use Berkeley DB for the data store
database    bdb
suffix        "dc=example,dc=com"

# Set the root user details
rootdn        "cn=Manager,dc=example, dc=com"
rootpw        secret

# data directory path
directory    /usr/local/var/openldap-data

# Proper indexing is crucial to the server performance Here, we create an index
# on objectClass presence and equality, and uid and cn equality We also create
# an index on equality and a substring index for the ou attribute see the
# OpenLDAP documentation for details

index objectClass    pres,eq
index uid,cn        eq
index ou        eq,sub

For a fuller explanation of the configuration directives, consult the OpenLDAP documentation. Note that we have created indices for the uid, cn, ou, and objectClass attributes. Proper indexing is important to an LDAP server's performance.

A Word on Security

In the configuration file above, note that the root user's username and password are stored in plain text. Anybody who has access to this file could potentially snoop and discover the password. There are a few potential solutions to this problem. Firstly, we could store a hash of the password instead of the plaintext password. We can easily generate a password hash using the slappasswd utility. To generate an SHA-1 hash for the password "secret", simply run $ slappasswd -s secret and copy and paste the resulting hash into the slapd.conf file. The resulting directive should look something like this:

rootpw {SSHA}8AmGj1c0IQqilEqlvGyz2dYc7RB+goMN

Related Reading

LDAP System Administration
By Gerald Carter

However, this still has the disadvantage that the user password will need to be passed in plain text over the wire when authenticating to the server. This means that an eavesdropper could pick up the root Distinguished Name (DN) and password easily. To combat this, we need to use strong encryption. OpenLDAP supports strong encryption; in the interests of space, we will not look at it here, but we may examine it in a future article.

The last step is to start the server. The appropriate command is $ slapd -f /path/to/slapd/conf/file. If the server starts fine, but you get problems while authenticating or running queries, you can start the server in debug mode by passing the -d-1 parameter to slapd.

Getting Started

We will create an imaginary organization and create a department within the organization called Developers. Inside of this department, we shall place some entries for our developers, and then query the directory for these entries.

Adding Our Organizational Container

The general procedure when setting up LDAP directories is:

Note that we have already set up a directory superuser in our slapd.conf. With that in mind, let's create an LDIF file called org.ldif that defines our organization, as follows (based on the example in the OpenLDAP documentation):

#Organization for Example Corporation
dn: dc=example,dc=com
objectClass: dcObject
objectClass: organization
dc: example
o: Example Corporation
description: The Example Corporation

Now we can use the ldapadd utility that comes with the OpenLDAP distribution to connect as the root user and add this entry to the directory:

$ ldapadd -h localhost -x -D"cn=Manager,dc=example,dc=com" -f org.ldif -w secret
adding new entry "dc=example,dc=com"

You can add multiple -v switches to ldapadd to see the individual units it creates. Let's also add an organizational unit (ou) to the directory, and call it Developers. We will add entries for our developer employees inside this unit. Create a file called dev.ldif with the following content:

dn: ou=Developers,dc=example,dc=com
objectclass: organizationalUnit
ou: Developers

Add this entry to the directory by running:

$ ldapadd -h localhost -x -D"cn=Manager,dc=example,dc=com" -f dev.ldif -w secret
adding new entry "ou=Developers, dc=example, dc=com"

Watch out for leading and trailing spaces in your LDIF data -- they aren't stripped out automatically.

Using libldap

Let's look at a simple example using the libldap C API. Now that we have a root node set up, and a container, let's add a user. This time, we'll use the libldap API instead of LDIF and the command-line OpenLDAP tools. This new user has an objectClass of inetOrgPerson. An objectClass is like a template for a directory object -- it defines object properties, including the mandatory and optional attributes that an object may have. The objectClass schema for inetOrgPerson is defined in the file inetorgperson.schema. On my installation of OpenLDAP on FreeBSD 4.8, this file is in /usr/local/etc/openldap/schema/inetorgperson.schema. We'll then initialize our user with some basic attributes. Let's take the program piece by piece.

First, we'll set up some variables that we'll need to connect to the server:

LDAP *ld;
int  result;
int  auth_method    = LDAP_AUTH_SIMPLE;
int desired_version = LDAP_VERSION3;
char *ldap_host     = "localhost";
char *root_dn       = "cn=Manager,dc=example,dc=com";
char *root_pw       = "secret";

The LDAP object is a structure that holds our session information. It can handle connections to multiple servers, if LDAP referrals are used. The auth_method constant specifies that we are going to use basic authentication, though OpenLDAP supports many other types of authentication, including SASL and Kerberos. We are going to bind using the DN and password specified in root_dn and root_pw.

Next, we connect to the LDAP server and tell the server we wish to bind using LDAP version 3.

if ((ld = ldap_init(ldap_host, LDAP_PORT)) == NULL ) {
    perror( "ldap_init failed" );
    exit( EXIT_FAILURE );
}

if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &desired_version) != LDAP_OPT_SUCCESS)
{
    ldap_perror(ld, "ldap_set_option failed!");
    exit(EXIT_FAILURE);
}

The LDAP_PORT constant is #defined to be 389, the default LDAP port. We connect to the server, then use ldap_set_option to tell the server we would like to use LDAP version 3. While recent versions of OpenLDAP only allow LDAP v3 binds by default, the OpenLDAP library defaults to binding with LDAP version 2. Next, we actually bind, passing the user credentials to the server:

if (ldap_bind_s(ld, root_dn, root_pw, auth_method) != LDAP_SUCCESS ) {
    ldap_perror( ld, "ldap_bind" );
    exit( EXIT_FAILURE );
}

Initializing and Adding Object Attributes

The function to add a new entry into the directory is defined as:

int ldap_add_s(LDAP *ld, const char *dn, LDAPMod *attrs[]);

As parameters, it takes the LDAP connection context, an array of pointers to LDAPMod structures, and the DN of the object to be added. An LDAPMod structure is defined as:

typedef struct ldapmod {
    // flag that determines whether this attr is being added/replaced/deleted 
    int mod_op;

    // the attribute name
    char *mod_type;

    union {
        // the attribute value(s) (LDAP can have multi-valued atributes)
        char **modv_strvals;

        struct berval **modv_bvals;
    } mod_vals;

    // used internally by libldap
    struct ldapmod *mod_next;
} LDAPMod;

In order to add a new entry to the directory, we need to initialize some attribute structures, passing them into the ldap_add_s function. (The trailing _s at the end of the LDAP functions we have seen signifies a synchronous operation). Let's initialize our user attributes now:

/* the full user Distinguished Name */
char *user_dn = "cn=Rory Winston,ou=Developers,dc=example,dc=com";

/* The attribute values we are going to set for this user */
char *cn_values[]          = {"Rory Winston", NULL};
char *sn_values[]          = {"Winston", NULL};
char *givenName_values[]   = {"Rory", NULL};
char *uid_values[]         = {"rwinston", NULL};
char *title_values[]       = {"Internet Developer", NULL};
char *objectClass_values[] = {"inetOrgPerson", NULL};
char *ou_values[]          = {"Development", NULL};

Note that the char* arrays must be NULL-terminated. As we said earlier, LDAP attributes can be multi-valued, so we could (for example) have multiple values for the cn attribute. Next, we initialize our LDAPMod structures:

LDAPMod cn, sn, givenName, uid, title, objectClass, ou;

cn.mod_op     = LDAP_MOD_ADD;
cn.mod_type   = "cn";
cn.mod_values = cn_values;

sn.mod_op     = LDAP_MOD_ADD;
sn.mod_type   = "sn";
sn.mod_values = sn_values;

givenName.mod_op     = LDAP_MOD_ADD;
givenName.mod_type   = "givenName";
givenName.mod_values = givenName_values;

// ...etc.

Note that we are using the symbolic constant LDAP_MOD_ADD to signify that we are adding these new attribute values into the directory. Next, we insert pointers to the LDAPMod structures into an array and call ldap_add_s:

mods[0] = &cn;
mods[1] = &sn;
mods[2] = &givenName;
mods[3] = &uid;
mods[4] = &title;
mods[5] = &objectClass;
mods[6] = &ou;
mods[7] = NULL;        /* Note the last entry must be NULL */

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

The user should now be added into the directory. Next, we end our session with the server:

result = ldap_unbind_s(ld);

if (result != 0) {
    fprintf(stderr, "ldap_unbind_s: %s\n", ldap_err2string(result));
    exit( EXIT_FAILURE );
}

That's it. Note that the flow of this example is not exactly the same as the sample code, for simplicity. The full sample code can be found here.

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.

Copyright © 2009 O'Reilly Media, Inc.