ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


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:

  • 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

Next Pagearrow





Sponsored by: