Novell Home

A Beginner's Guide to LDAP Development

Novell Cool Solutions: Feature
By Bill Bodine

Digg This - Slashdot This

Updated: 21 Dec 2005
 

Bill Bodine
Senior Engineer
Novell, Inc

Update - Cool Solutions reader Jeff Porter sent us an improved Java code sample, to replace the Java LDAP Search example below.

Introduction

There have been many articles and books written on the subject of LDAP which makes it very easy to find in depth information on the subject. This article will try to be a bit different than most since it is intended to help someone who is brand new to LDAP learn specifically what they should know if, in addition to being new to LDAP development, they have been tasked with building a "Directory-enabled" application. I hope to make this a bit of a "CliffNotes" type article and I will leave many of the details and less used aspects of LDAP development to the books and other articles. Naturally, an overview of LDAP will be given here to ensure that a firm foundation is established. From there I will briefly touch on the points that are critical to know for development. Since it is helpful to understand at least some of the maintenance pieces of LDAP, I will discuss this as well, using Novell's eDirectory as the sample platform.

Overview of LDAP

The Lightweight Directory Access Protocol (LDAP) has been around now for many years as an Internet protocol for accessing data stored in a network directory. In the beginning it was meant to be a 'lightweight' alternative to the OSI X.500 Directory Access Protocol (DAP). A few of the things that made it relatively lightweight as compared to X.500 were 1)it used TCP/IP, a protocol that would typically already exist on most workstations, rather than the more cumbersome protocol used by X.500 DAP. 2) the creators of LDAP determined that DAP had many little-used and even redundant features that didn't need to be included in LDAP, and 3) the messaging API used by LDAP is much simpler than the API employed by DAP. So in some ways lightweight refers to the protocol having a smaller footprint on the computer and in other ways it refers to the fact that it is less complex to develop to and use than DAP.

Architecturally, LDAP is really quite simple. It consists of one or more servers and one or more clients. The clients make requests of the server using a messaging format defined by the LDAP team on top of TCP/IP and the server sends the response back. So it just uses a request/response paradigm. Many clients can connect to a single server at a one time and it is the LDAP server that manages these connections. As was mentioned, there can also be multiple servers in an LDAP configuration. Each server essentially represents a different database. The databases that an LDAP server accesses are different than your typical relational database. LDAP directories have a hierarchical relationship with their data that can be represented diagrammatically by an upside down tree. Data is stored as objects that have a familial (parent, child, etc.) relationship with the other objects in the tree. For example, companies will commonly organize their trees either geographically or departmentally. The following ConsoleOne (a utility commonly used for eDirectory administration) screen shot depicts, in a very small and basic way, both of these organizational types.


Illustration 1: ConsoleOne Screen shot of eDirectory

After reviewing the screen shot it should become clear that directories are typically used to represent various types of entities that can exist in a company. In the example above, two companies (ABC, Acme) are represented as objects at the top of the tree. They are identified by a name, a type (known as the 'objectClass' in LDAP) and other attributes that are pertinent. The company is represented by an objectClass known as 'Organization' and may contain other interesting attributes, such as a company address, a facsmile telephone number, an email address, etc. In our diagram the Acme company also has three divisions that are represented in the directory, they will each have a number of attributes that describe important things about the division (location, address, phone number, etc.) and in LDAP terms are called 'Organizational Unit's. Each division in the company has many departments (also represented as an 'Organizational Unit' in LDAP) and each department in our case finally represents the critical data of employees in the company. The LDAP objectClass name for these employee objects is 'inetOrgPerson'.

There you go, now you have seen the basic structure of a an LDAP tree. Even though most LDAP applications are interested in the employee (inetOrgPerson) objects themselves, there are many other object classes that are stored in the directory that may be of interest to the application. Other objects of interest could be, computer, server, printer, device, and even objects representing applications. Actually the possibility for different object classes is limitless since the directory schema is extensible thus, allowing the directory administrator to create objects of any type that is needed. Developers that are new to LDAP will want to spend a few minutes and become comfortable with the directory schema; most particularly looking at the objectClasses that are in the schema by default and reviewing the various attributes that are available with the different objectclasses (inetOrgPerson, for example, has attributes like, givenName, surname, telphoneNumber, ldapPhoto, secretary, password, etc.).

eDirectory utilities like iManager and ConsoleOne can be used to browse and even extend the directory schema for testing or development purposes. The following screen shot shows the User (what eDirectory calls User, LDAP calls inetOrgPerson) object schema. Note the following about this class:

  • Effective – Meaning that it is a class that can be instantiated. Some object classes can not be instantiated. They are only used as parent objects to other classes that can be created.
  • Non-removable – Even though LDAP has an extensible schema, there are some objects that cannot be removed. User is one of these objects.
  • Can be contained by: – When creating a User object it can, hierarchically speaking, only be a child of these objects: Organization, Organizational Unit, domain
  • Attributes – These are only some of the attributes that are associated with a User object. The list of attributes is much larger than can be represented in this screen shot.

Illustration 2: User Schema as seen from ConsoleOne

While the reasons applications need the directory may vary dramatically. The most common two purposes an application needs to access inetOrgPerson data are 1) to retrieve authentication and/or authorization information about the object. In other words, when someone has connected to the network and is requesting information, are they in fact, who they say they are (authentication) and do they have sufficient rights to perform the operation in the directory (authorization). 2) The other common reason is to obtain 'identity' information about the user. For example, when a user hits an internet portal, the portal may want to access known information about the user to display the appropriate pages and information on those pages based on demographics known about the user (location, sites last touched, age, gender, etc.).

The last bit of overview information that might be important to understand is how objects in LDAP are referenced. In a relational database, each column of data has a 'key' field that contains an identifier that is unique to the database. Utilities and programs can then supply this key and retrieve the data stored in the database table. Objects in an LDAP directory are uniquely identified by their hierarchical relationship to the other objects in the directory. To understand this naming relationship, we will see how an employee named jsmith who works in the Marketing department in the Brick division of Acme Inc. would be named. The name starts with the specific object that is being referenced, in this case, the inetOrgPerson object called jsmith. The LDAP schema specifies that all inetOrgPerson objects are named by an attribute called 'cn' (cn stands for 'c'ommon 'n'ame). Since we said the employees name was jsmith, then this portion of the name for this object would be "cn=jsmith".

To continue building the complete or fully-distinguished name (FDN) of this employee we look at the parent object in the directory which is an organizational unit called 'Marketing'. The schema says that organizational units should be named by the 'ou' (ou stands for 'o'rganizational 'u'nit) attribute. So adding this onto the name we already have yields the name of "cn=jsmith,ou=Marketing". Note that the delimiter used for separating names in the FDN is the comma ',' character. We are not done building the name of this object yet since we have not reached the top of the tree. If we continue with the same algorithm for generating the object name, the FDN that will be used by any LDAP utility or program accessing this object will be: "cn=jsmith,ou=Marketing,ou=Brick,o=Acme". As you can see the organization object uses the 'o' attribute to name its objects. The following screen shot taken from an LDAP utility called LDAPSnoop shows how objects are referenced in LDAP. The "Selected Object" section references the object and its container is illustrated in the "Name Context" section.


Illustration 3: LDAPSnoop Utility

For more information on LDAPSnoop, see:
http://developer.novell.com/wiki/index.php/LDAPSnoop

Configuration

Now that you are armed with a basic knowledge of LDAP you might be curious about any installation and configuration issues with LDAP. In reality there is not very much that needs to be configured. When eDirectory is installed, one of the questions that must be answered is whether Transport Layer Security (TLS) will be used or not. Often times developers find it easiest to leave this off initially as they are learning LDAP development to avoid the use of certificates. Of course it means that all data transmitted in the LDAP session will be in clear text, but that is usually not a problem in a test environment. The other installation questions deal with the TCP/IP ports that should be used. Typically the only reason to change from the defaults of 636 (for secure SSL) connections) and 389 (for non-SSL connections) is if an LDAP server from another vendor is already installed on the machine. After installing eDirectory, LDAP will be ready to use without any additional configuration. However, if the LDAP server does need to be configured for some reason, to change session timeouts, modify the TLS security requirement, etc., it can be done by modifying either of two objects that are created during the eDirectory install, the LDAP Server and the LDAP Group objects. The following tables show some of the things that can be configured with these two objects.

LDAP Server LDAP Group
Search Timeout Limit     Proxy Username
Bind Limit TLS Requirement flag
Idle Timeout LDAP Server List
TCP Port Numbers Attribute mapping table    
Debugging Options Class mapping table

One of the nice things about the LDAP Group object is that by setting its attribute values, any server that is included in its LDAP Server List will received those same values.

Development

At this point you have been introduced to and should have a reasonable understanding of LDAP and its administration. Armed with this knowledge you are ready to write your first LDAP application. When building an LDAP application, the developer has many language and tool options to select from. Libraries are available in C/C++, Java, C#, Visual Basic and more. Other options for working with the directory are the Directory Service Markup Language (DSML), LDAP Data Interchange Format (LDIF), ODBC connectors and others. DSML and LDIF options are great options for accessing the directory since they allow text documents formatted in a specific way (XML in the case of DSML, and a special text format for LDIF) to be used as the interface to the directory without any specific coding needs. The ODBC driver is also powerful, since it allows a developer accustomed to database development to access the directory using the SQL statements that they are familiar with with. These are great interfaces, but this article is going to go one step lower and show a few examples of accessing the directory using a procedural language like Java and C/C++.

It was mentioned earlier that with LDAP, clients communicate with the LDAP server using messages. In reality there are not very many messages that a developer would need to be comfortable with in order to develop to LDAP. The basic messages (verbs) are Bind, Search, Add, Modify, Delete. There are others, but once a developer knows how to authenticate (bind) and use these other verbs they will have most if not all the knowledge they need to complete their application. Since the objective of this paper is to arm the developer with the essential knowledge needed to write an LDAP application, the next topic of discussion will be a few of the various SDK's that are available and then we'll finish off with some examples that illustrate the use of these verbs.

The remainder of this article assumes that you are using a procedural language like Java, C, or C# to develop your application, so the SDK's discussed will be related to these languages. The Novell developer home page contains links to each of the following.

LDAP Libraries for C – http://developer.novell.com/wiki/index.php/Cldap
LDAP Classes for Java – http://developer.novell.com/wiki/index.php/Jldap
LDAP Libraries for C# – http://developer.novell.com/wiki/index.php/Ldapcsharp
JNDI Classes – Available in JDK's shipped from Sun

The LDAP libraries for C, Java, and C# are very similar. In fact, the example we'll give here will actually be three examples. Since the most common operation performed on an LDAP directory is a Search, we'll show how this can be done using each of these three SDK's. Not a lot of explanation will be given with these examples for a few reasons. One, because they are simple enough that an explanation might just make things seem more complicated than they really are, and two because I want to keep the document short and sweet. For more complete and in-depth information see the documentation or other articles that focus on specific pieces of the protocol.

One thing we will do before showing the examples, however, is follow the typical flow an application must take to interface with the the LDAP server and show how each of the three SDK's handle the specific task.

Initialize a connection/session with the LDAP Server
CldapSessionHandle = ldap_init( ldapServerAddress, ldapServerPortNumber );
Javalc.connect( ldapServerAddress, ldapServerPortNumber );
C#lc.Connect( ldapServerAddress, ldapServerPortNumber );
Note: the Java and C# libraries are much more object oriented than is the C library. The lc object would have been instantiated prior to connecting to the LDAP Server in both cases and certain session properties, like network timeout, would have been set on the connection object itself. Since the C libraries are not object oriented, a call, ldap_set_option( linkIdentifier, option, newValue), is used to set session properties.

Authenticate to the LDAP Server
Cldap_simple_bind_s( ldapSessionHandle, loginDistinguishedName, loginPassword );
Java lc.bind( ldapVersion, loginDistinguishedName, loginPassword );
C#lc.bind( loginDistinguishedName, loginPassword);

Search the Directory
C – returnCode = ldap_search_ext_s ( ldapSessionHandle, searchBase, searchScope, searchFilter, attributesToReturn, attributesOnlyFlag, serverControls, clientControls, timeOut, sizeLimit, &searchResult );
Java searchResults = lc.search( searchBase, searchScope, searchFilter, attributesToReturn, attributesOnlyFlag );
C# searchResults = lc.Search( searchBase, searchScope, searchFilter, atttributesToReturn, attributesOnlyFlag );

Note: Client and server controls are not used all that often, however, when they are needed for a Java or C# application, a different call is used. Every LDAP search will specify, at least:

  1. search base – in the hierarchical tree this is the place where the search will begin. Then depending on the scope of the search, it will flow down through objects that reside below this base object in the tree.
  2. search scope – once the search base is identified, this variable specifies how far reaching the search will be. If the scope is "Base", then only the object identified by searchBase will be read. If the scope is "One Level", then the search base object and any objects it contains will be read. If the scope is "Subtree", then the LDAP Server recursively looks at the search base object and every object that resides below it in the tree.
  3. search filter – any container that is 'searched' with this operation may contain many different types of objects: Server objects, computer objects, other container objects (organizational unit), user objects (inetOrgPerson) etc. The searchFilter lets you identify only those objects that are of interest to this particular search.
  4. attributes to return – this is an array of strings, that identifies those attributes that are of interest when reading objects. For example, the application might only be interested in reading telephone numbers and surnames for a given search.
  5. attributes only flag – there are times when a search only cares about the existence of an object and it values, but really doesn't care about the values themselves. When this is the case, this will be set to true.
Looping Through the Search Results
C – 
for ( dirEntry = ldap_first_entry( .. ); 
dirEntry != null; 
dirEntry = ldap_next_entry(...) ) {
    for ( attribute = ldap_first_attribute(...); 
    attribute != null; attribute = ldap_next_attribute(...) {
        values = get_ldap_values(...)
    }
 }
Java – while ( searchResults.hasMore() ) {
        nextDirEntry = searchResults.next();
        attributeSet = nextDirEntry.getAttributeSet();
        allAttributes = attributeSet.iterator();
        while ( allAttributes.hasNext() ) {
            attribute = allAttributes.next();
            values = attribute.getStringValues();
        }
    }
C# - while ( searchResults.hasMore() ) {
        nextDirEntry = searchResults.next();
        attributeSet = nextDirEntry.getAttributeSet();
        allAttributes = attributeSet.GetEnumerator();
        while ( allAttributes.MoveNext() ) {
            attribute = allAttributes.Current;
            value = attribute.StringValue;
        }
 }

Now for the code itself. The following three examples are very basic and yet functional code snippets that can be used to search for an object in an LDAP directory.

#include 
#include <stdlib.h>
#include <string.h>
#include <ldap.h>

int main( int argc, char *argv[] ) {
    int version = LDAP_VERSION3, rc, i;
    LDAP *ld;
    LDAPMessage *searchResult, *entry;
    char *attribute, **values;
    BerElement *ber;
    struct timeval timeOut = {10,0}; // 10 second connecion/search timeout

    ldap_set_option( NULL, LDAP_OPT_PROTOCOL_VERSION, &version );
    if ( ( ld = ldap_init( "www.SomeLDAPServer.com", 389 ) ) == NULL ) return ( 1 );
    rc = ldap_simple_bind_s( ld, "cn=admin,o=acme", "acmeadminpassword" );
    if ( rc != LDAP_SUCCESS ) return ( 2 );
    rc = ldap_search_ext_s( 
            ld,		// LDAP session handle
            "o=Acme",	// Search Base
            LDAP_SCOPE_SUBTREE,	// Search Scope – everything below o=Acme
            "(objectClass=inetOrgPerson)", // Search Filter – only inetOrgPerson objects
            NULL,	// returnAllAttributes – NULL means Yes
            0,		// attributesOnly – False means we want values
            NULL,	// Server controls – There are none
            NULL,	// Client controls – There are none
            &timeOut,	// search Timeout
            LDAP_NO_LIMIT,	// no size limit
            &searchResult );
    if ( rc !- LDAP_SUCCESS ) return ( 3 );
    for ( entry = ldap_first_entry( ld, searchResult );
        entry != NULL;
        entry = ldap_next_entry( ld, entry ) ) {
        for ( attribute = ldap_first_attribue( ld, entry, &ber );
            attribute != NULL;
            attribute = ldap_next_attribute( ld, entry, ber ) ) {
            for ( i=0; values[i] != NULL; i++ )
                printf("   %s: %s\n", attribute, values[i] );
            ldap_memfree( attribute );
        }
        ber_free( ber, 0 );
    }
    ldap_msgfree( searchResult );

    return( 0 );
}
C - LDAP Search
import com.novell.ldap.*;
import java.util.Enumeration;
import java.util.Iterator;
public class Search
{
    public static void main( String[] args ) {
        String loginDN = "cn=admin,o=Acme";
        String password = "acmeadminpassword";
        String searchBase = "o=Acme"; searchFilter = "(objectClass=inetOrgPerson)";
        int searchScope = LDAPConnection.SCOPE_SUB;

        LDAPConnection lc = new LDAPConnection();
        try {
            lc.connect( "www.SomeLDAPServer.com", 389 );
            lc.bind( LDAPConnection.LDAP_V3, loginDN,  password.getBytes("UTF8"));
            LDAPSearchResults searchResults = lc.search(
                searchBase,
                searchScope,
                searchFilter,
                null,
                false );
            while ( searchResuts.hasMore() ) {
                LDAPEntry nextEntry = null;
                try {
                    nextEntry = searchResults.next();
                } catch(LDAPException e ) {
                    System.out.println("Error: " + e.toString();
                    continue;
                }
                LDAPAttributeSet attributeSet = nextEntry.getAttributeSet();
                Iterator allAttributes = attributeSet.iterator();
                while ( allAttributes.hasNext() ) {
                    LDAPAttribute attribute = (LDAPAttribue)allAttributes.next();
                    String attributeName = attribute.getName();
                    Enumeration allValues = attribue.getStringValues();
                    if ( allValues != null ) {
                        while ( allValues.hasMoreElements() ) {
                            String value = (String)allValues.nextElement();
                            System.out.println(attributeName + ":  " + value );
                        }
                    }
                }
            }
        } catch( LDAPException e ) {
            System.out.println("Error " + e.toString() );
        }
    }
}
Java - LDAP Search
using System;
using Novell.Directory.Ldap;
using Novell.Directory.Ldap.UtilClass;
using System.Collections.IEnumerator;

namespace simpleNamespace {
    class Search {
        public static void Main( string[] args ) {
            string loginDN = "cn=admin,o=Acme";
            string password = "acmeadminpassword";
            string searchBase = "o=Acme", searchFilter = "(objectClass=inetOrgPerson)";
            int searchScope = LdapConnection.SCOPE_SUB;
            try {
                LdapConnection lc = newe LdapConnection();
                lc.Connect( "www.SomeLDAPServer.com", 389 );
                lc.Bind( loginDN, password );
                LdapSearchResults searchResults = lc.Search (
                    searchBase,
                    searchScope,			
                    searchFilter,
                    null,
                    false );
                while ( searchResults.hasMore() ) {
                    LdapEntry nextEntry = null;
                    try {
                        nextEntry = searchResults.next();
                   } catch ( LdapException e ) {
                       Console.WriteLine("Error " + e.LdapErrorMessage );
                       continue;
                   }
                   LdapAttributeSet attributeSet = nextEntry.getAttributeSet();
                   Ienumerator allAttributes = attribueSet.GetEnumerator();
                   while ( allAttributes.MoveNext() ) {
                       LdapAttribute attribute = (LdapAttribute)allAttributes.Current;
                       string attributeName = attribute.Name;
                       string attributeVal = attribute.StringValue;
                       Console.WriteLine( attributeName + ": " + attributeVal );
                    }
                }
            } catch ( LdapException e ) {
                Console.WriteLine( "Error: " + e.LdapErrorMessage );
                return;
            } catch ( Exception e ) {
                Console.WriteLine( "Error: " + e.Message );
            }
        }
    }
}
C# - LDAP Search

Conclusion

With this information you are now armed with enough knowledge to create an LDAP application. There are other LDAP SDK's not covered here like JNDI for Java and the System.Directory library for .NET that offer a different look into LDAP development than do these SDK's. We might cover those interfaces in another article. For right now, however, you have enough information to get started. In a subsequent article, we will finish this discussion by looking at a few of the other messages that are commonly used in LDAP (Add, Modify, Delete). We also essentially ignored the whole secure connection topic here, so we will also cover the subject of LDAP development using SSL in a future article.

For reference purposes the following are some books and URLs that will have more complete information on LDAP development.

Novell's LDAP Developers Guide – ISBN – 0-7645-4720-8
LDAP Programming Directory-Enabled Applications with Lightweight Directory Access Protocol – ISBN -1-57870-000-0
Understanding and Deploying LDAP Directory Services – ISBN – 0-672-32316—8
www.ietf.org – rfc's 2251 – 2256
www.directory-applications.com/ldap3_files/frame.htm


Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

© 2014 Novell