Using libldap, the LDAP Client Library
by Rory Winston08/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
|
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:
- Set up your top-level organization container (the directory root).
- Set up your organizational elements, such as departments and roles (branch nodes).
- Set up your superusers.
- Set up your users (leaf nodes).
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.
Pages: 1, 2 |


