You can do a lot with LDAP once you get all the glue right.
Note: this article assumes a basic knowledge of COM programming, and familiarity with the concept of scripting.
Introduction
LDAP (Lightweight Directory Access Protocol) is a protocol for accessing specialized databases known as directory services. A directory service serves as a central repository for searching, adding, deleting, and modifying resources. User information is an example of managed resources that can benefit from using directory services. One of the best examples of a popular directory service is the Active Directory that ships with Windows 2000.
By implementing the LDAP API a client can perform several operations, such as connection to the directory service, authentication, query, and data reads and writes. If such operations are packaged in a COM component, directory services become accessible to browser-based clients as well.
This article shows how to package an existing LDAP C SDK into such a COM component. It also introduces the peculiar features of directory services and the information model used by the LDAP protocol, and briefly describes existing LDAP software libraries. Finally, I present a simple ASP (Active Server Pages [1]) Javascript page that uses my LDAP COM API, and I show a browser-based user interface for operating on directory service data.
Basics of Directory Services
As stated previously, a directory service serves as a central repository for managing resources. The typical scenario that would be better handled via a directory service is one where user information is stored in multiple data stores, such as Windows NTDS, Windows registry, email servers, and MS SQL servers. When such information is spread across heterogeneous data stores, it is prone to spurious duplication and inconsistent updating. A directory service solves these problems by locating user resources in a network-accessible database that serves as a central repository for the organization.
The basic features of a directory service can be summarized as follows [2]:
- central resource management resources can be distributed geographically or within the confines of an organization.
- data replication resources can be replicated for performance and redundancy.
- flexibility the format of the stored information and the data schema can be extended.
- interoperability different vendor-specific directory services can share information.
- open standards there is no proprietary ownership or control of the directory protocols.
- server referral servers can refer to other servers to fulfill requests for resources not found locally.
- security directory services provide security on the client-server communication channel as well as on the access of resources.
- scalability directory services can scale from a small business with few entries to a large corporation with 100,000 employees.
Directory services use special databases that are optimized for many reads and few writes. The number of read operations generally exceeds the number of write operations by at least an order of magnitude.
The LDAP Protocol
LDAP is the industry standard protocol that defines how to access a directory service through TCP/IP [2]. It is a simplification of the X.500 DAP (Directory Access Protocol) that was designed for the seven-layer OSI protocol stack. LDAP is not a string-based protocol like HTTP; commands are not sent to the client in string format. In LDAP, all data types, such as integers and strings, and data structures (sets and sequences) are encoded in system-independent fashion using BER (Binary Encoding Rules) and ASN (Abstract Syntax Notation). The name LDAP is a misnomer since the standard includes more than a protocol it also defines an information model. The information model includes the data model that defines how LDAP entries are stored in the directory service and a functional model that defines basic operations on directory service data.
The Information Model
In the LDAP information model, data is organized as entries that belong to DITs (Directory Information Trees). Each entry belongs to a namespace and can be distinguished from other entries through a DN (Distinguished Name). The schema of the directory defines the allowed entries and attributes.
The entry is the atomic data entity of LDAP. An entry is an object that holds persistent information about a resource, such as the name of a device that belongs to a company, and its location. Each entry has a unique name known as its distinguished name. An entry has a collection of attributes, such as email address, IP address, etc. For each attribute there is an associated type that can hold one or more values. Most value types are string types, but binary types are also allowed for some attributes. For example, a jpegPhoto type attribute of a person entry may contain a picture in binary JPEG/GIF format.
Here is an example of a person entry in a directory service. The entry holds information about the real person Bryan Jensen. It has the following attributes-value pairs:
cn: Bryan Jensen o: Ace Inc. ou: Engineering mail: bjensen@ace.net telephoneNumber: 770-555-1212Attributes have names such as cn for common name, o for organization, ou for organization unit, etc. In addition to its name, each attribute has a data type that defines its syntax (e.g., case ignore string) and its multiplicity (e.g., single-valued or multi-valued). The set of attributes that characterizes an entry is defined in the directory service schema. The schema defines an objectClass attribute for each entry. The objectclass defines the parent class, as well as which attributes are required and which are optional. The LDAP schema definition is vendor specific and depends on the LDAP vendor's implementation. Novell, Microsoft, Sun/Netscape, HP, and IBM/Lotus all provide directory services that have different schemas. Different types are used to describe similar entries by different directory services vendors. For example, the iphost entry in the Netscape DS schema corresponds roughly to a computer entry in Microsoft Active Directory schema.
In an LDAP directory, entries are organized in hierarchy tree structures. The tree structure can conform to the geographical view of the data, the organizational view of the company, or some user-defined functional boundaries.
An example of a hierarchy tree structure with three entries, one root and two leaf nodes, appears in Figure 1.
Every entry has a unique location in the tree. The DN is used to uniquely locate the entry. Entries with the same relative name (e.g. Bryan Jensen) can be located in different parts of the tree. This is similar to the way in which a file path uniquely identifies the location of a file in a directory tree. At every level of the tree it is possible to identify the position of the entry relative to its parent. This position is the entry's Relative Distinguished Name, or RDN. By concatenating the RDNs of all the parent entries up to the root of the tree we get the distinguished name for the entry. For example, the DN for the entry Bryan Jensen in Ace's engineering department is: cn=Bryan Jensen, ou=Engineering, o=Ace.net
Client/Server Model
To facilitate its role as a global repository, a directory service can store redundant copies of data in different directory servers. Every LDAP client can use a nearby server to locate data distributed across the company network. Replication keeps data consistent across servers, though temporary inconsistencies can exist.
The LDAP directory service is based on a client/server model. LDAP supports basic operations for the client such as connecting to the server, querying the directory server for an entry, and updating the directory entry (adding, deleting, and modifying). In the case of search operations, search filters can be used to selectively search for entries that match some search criteria specified by the filter. LDAP provides methods for the client to authenticate or to provide user credentials for the connection to the server. A secure connection can be established using SSL (Secure Socket Layer). Besides authentication, each LDAP server implements vendor-specific access control associated with the entries in the directory to determine who has access to what information. This is typically done through a mechanism similar to Windows NT's Access Control Lists.
Creating a COM LDAP API
A programmer who wants to develop a web-based LDAP client has three choices of common implementations:
- Microsoft ADSI and Active Server Pages
- Java LDAP SDK and Java Applets
- CGI application built with a C API, the Perl API, or shell commands
A fourth choice is to package an existing LDAP C SDK, such as the Netscape SDK or the University of Michigan LDAP SDK, into a COM software component and use ASP. This is the approach presented in this article. (For a description of Microsoft's ADSI, the Java LDAP SDK, and the Netscape SDK, see the sidebar.)
The COM Dual Interface
As stated previously, I assume that readers of this article have a basic knowledge of COM. However, before presenting the implementation, I give a quick review of dual interfaces in COM. (If you want to read up on COM, I encourage you to consult the previous CUJ articles [3, 4] or Rogerson's book [5]. Of particular significance, the article by Brill [4] explains the concept of dual interfaces.)
COM supports two kinds of interfaces Automation-based interfaces, and vtable-based interfaces. An interface that supports Automation is derived from the type IDispatch. An object that implements the IDispatch interface can have all its methods invoked by the client through the name of the method at run time. The client need not know the name of the method, or the types of its parameters, at compile time.
In a vtable-based interface, the client calls the interface methods through a vtable, which is an array of method pointers associated with the interface. In C++ this is accomplished by deriving the interface from an abstract class. Unlike the Automation interface, clients that invoke methods on the vtable-based interface must know the method names and parameter types at compile time.
In the COM LDAP component presented here, the interface functionality is similar to that of the LDAP API documented in the LDAP specification (RFC 1823 [6]). The type of interface required (Automation or vtable) depends on the type of client that needs to communicate with the component. Since the web client for this example uses scripting, it requires access to the COM component through an Automation interface. All the data passed through Automation interfaces use a self-describing data type called the VARIANT [7].
The LDAP API interface presented here, ILDAPConnection, inherits from a dual interface, which is an interface that provides both the Automation and vtable methods of invocation. This interface and its methods are described in an IDL (Interface Description Language) file, which is not shown here but is available on the CUJ website (www.cuj.com/code).
I made the following architectural design choices in implementing the COM LDAP API:
1) Method calls have the same names and return the same types as those in a twin COM API written in Java using the Netscape Java LDAP SDK.
2) The design uses a simple COM model that consists of one component and one interface.
Although I don't show the Java version here, I use similar error handling logic in both the Java and C++ versions of the LDAP COM API. For the C++ COM implementation, I could have designed the methods to return value as HRESULTs, which are predefined constants that indicate success, failure, and various error conditions. Since this would not be possible with Java-based COM objects, the C++ implementation returns error flags as boolean output parameters (of type VARIANT_BOOL).
The packaging of the C API into the COM API also put other constraints on the coding. All parameter types must be converted from COM types such as BSTR to ANSI C types such as char *. I avoided use of array types, since Javascript treats arrays differently than VBScript. With Javascript, an array declared in ASP is passed to COM like an object that implements the interface IDispatch, whereas VBScript passes a special COM type called SAFEARRAY. For this reason, I construct arrays in the LDAP COM API as a concatenated string of input parameters.
I built the LDAP COM API using Microsoft ATL (Abstract Template Library [8]) in a DLL. The DLL is dynamically linked with the Netscape DLL nsldapssl32v30.DLL, which exports the LDAP functionality defined in the header LDAP.h. The class is generated using the ATL Object Wizard provided by the Microsoft Visual C++ Integrated Development Environment.
The CLDAPConnection Class
The LDAP COM component consists of the class CLDAP Connection (see Figure 2), which inherits from three ATL-generated template classes:
- CcomObjectRootEx handles object reference count management.
- CcomCoClass defines the class factory and aggregation model for the component.
- IdispatchImpl implements the IDispatch portion of the dual interface.
The class definition also contains the following macros:
- DECLARE_REGISTRY_RESOURCEID implements script-based registry support.
- BEGIN_COM_MAP, END_COM_MAP define the COM interface map. A client that has a reference to the object map can get its COM interfaces using the QueryInterface method.
The class also defines the following member variables:
- LDAP *ld the handle to the LDAP session, as returned by Netscape SDK's ldap_init and ldap_open functions
- LDAPMessage *result An LDAP operation result, as returned by Netscape SDK function ldap_search_s
The methods of the interface are declared using the macro STDMETHOD, which wraps up the stdcall calling convention, the virtual keyword, and the HRESULT return type (which is not used).
API and Local Methods
The CLDAPConnection class provides two different sets of methods. The first set is the API methods that implement the ILDAPConnection interface to be exposed by the LDAP COM object. These methods are exposed to ASP and provide the LDAP API functionality. They are defined both in IDL and in the class definition. The second set is a set of local methods. They do not implement the interface; they belong only to the class. The list of the methods and the tasks they perform is shown in Table 1.
The methods exposed by the LDAP COM API are implemented by wrapping the Netscape LDAP SDK C API functions. An example appears in Figure 3, which shows the connectAnonymously method. The purpose of this method is to establish an anonymous connection to the LDAP directory by calling the LDAP SDK API ldap_init, which initializes the LDAP connection with the server. The function parameter types are converted from double-byte character type BSTR (a Microsoft-specific type similar to Unicode) to a single-byte type char * before to passing them to the C API.
In the case of an SSL connection to the server, the certificate credentials are authenticated by the initialization function ldapssl_init. The server certificate credentials are stored in the file Cert7.db, which is located by the GetCert7dbPath local function. The Netscape API returns the LDAP connection handle, which is a pointer to an LDAP structure containing information about the connection to the LDAP server. The connection handle identifies the LDAP session and is used by other methods of the LDAPConnection class to perform LDAP operations such as search, creation, deletion, and modification of entries. The connection handle is released by calling the disconnect method once the LDAP session is finished.
Another example of LDAP API wrapping appears in Figure 4. The method createEntry takes the parameters of the distinguished name of the entry to be created and the attribute-value pair string.
The format of attribute-value pair string is as follows (broken to fit column):
attr1=value1<,>value2<,>value3<; >attr2=value<;>...In the above string, <;> is shown as the delimiter between attribute-value pairs, and <,> is shown as the delimiter between the individual values of a multi-valued attribute.
After the createEntry function converts input string values from BSTR to char * and parses the attribute entry-value pairs, it calls the method allocateMods to allocate memory for an LDAPMods structure (used for creating and modifying entries in the Netscape LDAP SDK). createEntry initializes the elements of the LDAPMods structure by calling the local method initializeMods and and then calls the Netscape LDAP SDK function ldap_add_ext_s to modify the entry. The createEntry method returns a true value if the entry creation/modification succeeded and false otherwise.
An ASP Application
Figure 5 shows the ASP page with the basic LDAP COM API calls using Javascript. The sequence of LDAP API operations consists of 1) opening the LDAP connection, 2) searching for the entry of interest, 3) creation of an entry, 4) modification, 5) deletion of the entry, and 6) disconnection from the LDAP directory.
A Web-Based LDAP Browser
The LDAP COM API can be used to build web applications that perform basic LDAP operations. In Figure 6, the user interface for selecting the LDAP operation is shown on the left panel while the form with the required LDAP search input data is shown on the right panel. The LDAP outcome for the search is shown in Figure 7.
Conclusions and Recommendations
Enterprise wide corporations can manage user resources more efficiently by deploying directory services and LDAP enabled web clients. Directory services and LDAP are still relatively new in commercial software but will became more popular with usage of Windows 2000 Active Directory. Several APIs are already available to application developers who want to build client applications that use LDAP. In the case of LDAP-enabled web clients, the LDAP COM API implementation described here requires less code overhead than Microsoft's ADSI COM [9], which is designed to support multiple namespaces (e.g. WinNT and LDAP). I have successfully tested the LDAP COM API using LDAP from both Netscape and Microsoft browsers and by accessing data from both Microsoft's Active Directory (e.g., Windows 2000 RC 2) and Netscape Directory servers.
The current LDAP COM API provides basic LDAP functionality that could be improved in the following ways:
- Use of Collections and Enumerators objects to parse the search data [8]. The reduction in required scripting is noteworthy when using collections and enumerators in ASP [11].
- Use of ATL smart types such as CComBSTR and CComVARIANT [10] to encapsulate BSTR and VARIANT types to provide memory management similar to C++ smart pointers [11].
- Use of ComException interfaces to package C/C++ exceptions into COM exceptions [7] to provide more error message information.
- Design of a LDAP COM API based on multiple components (i.e. DLLs) to provide ASP with COM objects that are instantiated only when they are needed.
Acknowledgements
I would like to thank Dr. John Hammer of Internet Security Systems for supporting the writing of this article, Mr. Bryan Williams for which I share credit for the API software design architecture, and Mr. Steve Millar for the development of the LDAP browser and his mentoring of COM.
Notes & References
[1] Alex Federov, et al. ASP 2.0 Programmer's Reference (Wrox Press Ltd., 1999).
[2] Timothy A. Howes, et al. Understanding And Deploying LDAP Directory Services (Macmillan Technical Publishing, 1999).
[3] Gregory Brill. "An Introduction to COM," C/C++ Users Journal, January 1998.
[4] Gregory Brill. "Writing COM Clients with Late and Early Binding," C/C++ Users Journal, October 1998.
[5] Dale Rogerson. Inside COM (Microsoft Press, 1997).
[6] Timothy A. Howes and Mark C. Smith. LDAP Programming Directory-Enabled Applications with Lightweight Directory Access Protocol (Macmillan Technical Publishing, 1997).
[7] Don Box. Essential COM (Addison-Wesley, 1998).
[8] Dr. Richard Grimes and Alex Stockton. Beginning ATL COM Programming (Wrox Press Ltd., 1998).
[9] Steven Hahn. ADSI ASP Programmer's Reference (Wrox Press Ltd., 1998). For an overview of Microsoft ADSI you can also visit Microsoft's ADSI URL: http://www.microsoft.com/adsi.
[10] Jim Springfield, Brent Rector, and Chris Sells. ATL Internals (Addison-Wesley, 1999).
[11] Stan Lippman and Josée Lajoie. C++Primer, Third Edition (Addison-Wesley, 1998).
Marco Morana works as a software engineer for Internet Security Systems Inc. in Atlanta, Georgia, specializing in the development of enterprise applications that enable use of computers with intrusion detection capabilities. In more than 10 years of software development experience as an application programmer, he has contributed to the design of commercial software applications for the aerospace, insurance, real estate, and data security industry. Marco was honored with the NASA Space Act Award in 1999 for developing NASA's Secure E-mailing Plugin. He holds a BSME from University of Padova, Italy and MSCE from Northwestern Polytechnic University. He is a member of IEEE and the Computer Society.