Bill Bodine Senior Engineer Novell, Inc
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
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 C –
ldapSessionHandle = ldap_init( ldapServerAddress, ldapServerPortNumber
); Java – lc.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 C – ldap_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:
- 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.
- 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.
- 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.
- 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.
- 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
#include
#include
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
", 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.
|