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.
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 ConfigurationFor 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.
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 |
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.
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.
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.
libldapLet'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 );
}
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.
|
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.
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.

Figure 1. A graphical LDAP Browser
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" );
}
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.
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 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.
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:

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.
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.
Thanks to Hallvard Furuseth for his invaluable help and editorial and technical comments while reviewing this article.
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.