Examining Microsoft's LDAP API

The Lightweight Directory Access Protocol is a TCP-based protocol that facilitates remote access to X.500-type directory services. Sven shows how you can use LDAP to access Microsoft's Exchange Server 5.x, then presents an LDAP DLL for programming an Exchange directory browser.


December 01, 1998
URL:http://www.drdobbs.com/architecture-and-design/examining-microsofts-ldap-api/184410752

Dec98: Examining Microsoft's LDAP API

Sven is a developer in Herzogenaurach, Germany. He can be contacted at sbs@ orgon.com.


The Lightweight Directory Access Protocol (LDAP) is a TCP-based protocol that facilitates remote access to X.500-type directory services. Many vendors support LDAP, including Microsoft, which started supporting LDAP with Version 5.0 of its Exchange Server directory. Incidentally, LDAP will become the core protocol of the Active Directory Services Interface (ADSI) of Windows NT 5.0.

In addition to LDAP, Exchange 5.5 supports the Network News Transfer Protocol (NNTP), Simple Mail Transfer Protocol (SMTP), Post Office Protocol v3 (POP3), and Interactive Mail Access Protocol v4 (IMAP4). All are based on open Internet standards and are defined in "Request for Comments" (RFC) documents. All RFCs are freely available at ftp://ds.internic.net/rfc/ or ftp://nic.ddn.mil/rfc/.

The abstract definition of LDAP Version 2 (LDAPv2) was published in 1995 in RFC 1777, and a companion API specification appeared shortly thereafter in RFC 1823. It wasn't until December 1997, however, that LDAPv3 was released (RFC 2251), adding lots of new features and further standardizing various protocol elements. News and documents on the progress of LDAP are available at Mark Wahl's web site (http:// www.critical-angle.com/ldapworld/). Wahl is the primary author of RFCs 2251-2253 and RFC 2256.

In this article, I'll provide an overview of how to use LDAP to access Microsoft's Exchange Server 5.x. To that end, I've included a general-purpose LDAP utility DLL that makes programming an Exchange directory browser straightforward, plus a demo program built upon that DLL (available electronically; see "Resource Center," page 3). There are a lot of details involving topics such as authentication, the X.500 Directory, parsing, server controls, paged results, version negotiation, and the like, that I do not address in this article. However, these topics (and others) are covered in a set of programmer's notes that are also available electronically.

The LDAP API

Writing an LDAP client does not necessarily involve banging directly on Windows sockets and packing/unpacking BER-encoded TCP data blocks. In RFC 1823, an API that hides all BER and WinSock minutiae beneath a simple surface is defined. The problem with RFC 1823, however, is that it is somewhat outdated. It relates to LDAPv2 and as such, it doesn't take into account the additional functionality introduced with LDAPv3/ RFC 2251.

At this writing, the official update to RFC 1823 is in the Internet-draft state (see ftp://ds.internic.net/internet-drafts/ draft-ietf-asid-ldap-c-api-00.txt). As the document name implies, the Access, Searching and Indexing of Directories (ASID) working group of the Internet Engineering Task Force (IETF) is in charge of this paper (see http://www.ietf.org/ for information about the task force, or http://www.ietf.org/ html.charters/asid-charter.html for a summary of ASID documents). Internet drafts are works in progress and should not be used as reference material. Thus, the LDAPv3 API should be regarded as tentative, and everything I say about it is subject to change.

A reference implementation of LDAP and the LDAP API is distributed by the University of Michigan -- this Umich Release is freely available at http://www .umich.edu/~dirsvcs/ldap/. At this writing, the current Version is 3.3 (ldap-3.3.tar.Z). A Windows 3.x/Win32 version for WinSock 1.1 is also available (winldap.zip).

Microsoft provides its implementation of RFC 1823 and the LDAPv3 API draft in the wldap32.dll system DLL. There are several versions around -- a file version number of 5.0.1515.0 or newer is okay. The necessary header and import library files are distributed as part of the Platform SDK, at http://www.microsoft.com/msdn/sdk/ platform.htm. The only files you will need are winldap.h and wldap32.lib. (A 16-bit version isn't available and probably never will be.) Get the files from the most-recent version of the SDK, since they might change due to ongoing LDAP enhancements.

Of course, issuing some calls into wldap32.dll isn't the whole story. The first problem you'll encounter is that documentation on Microsoft's LDAP implementation is practically nonexistent. This situation should change with forthcoming releases of DeveloperStudio, Platform SDK, and Back-Office Resource Kit (BORK). But don't expect too much clarification from RFC 1823 -- it's not aimed at a specific platform, so you can't expect much detail on the peculiarities of the Microsoft implementation.

Another problem is Exchange Server itself. Version 5.0 supports LDAPv2 only. Although the version-number leap from 5.0 to 5.5 looks small, the update is a huge step with respect to LDAP and other Internet protocols. Exchange Server 5.5 supports many recent LDAPv3 features, some of which are still under discussion -- search-result paging, for example (see draft-ietf-asid-ldapv3-simplepaged-02.txt and draft-ietf-asid-ldapv3-api-ext-00.txt in the ftp://ds.internic.net/internet-drafts/ directory of InterNIC for details on the respective protocol and API extensions). Thus, writing an LDAP client that works with both Exchange Servers 5.0 and 5.5 is tricky, because it must cope with two quite distinct LDAP versions and implementations.

As is customary with scarcely documented software, the best source of information is a C header file -- winldap.h in this case. The comments surrounding the definitions of data structures and API prototypes give insights, although they leave many questions unanswered. So the rest of the work remains dedicated to trial-and-error. To save you some time, the general-purpose LDAP utility DLL I present (LdapLib.dll) here makes programming an Exchange directory browser straightforward.

Basic Framework

The LDAP API library wldap32.dll uses the 32-bit WinSock DLLs wsock32.dll and ws2_32.dll shipping with Windows 95/NT to communicate with LDAP-capable servers. WinSock communication always requires a port where the server listens for incoming connection requests. In the case of LDAP, the default port number is 389. If the server supports the Secure Socket Layer (SSL), you should connect to port 636 instead. Exchange Server is SSL-capable. To LDAP enable an application, you must:

  1. Include winldap.h somewhere after #include <windows.h>.
  2. Add wldap32.lib to the project's list of import libraries.

The basic LDAP API calling sequence of a typical directory browser application is as follows:

  1. The application initiates an LDAP connection.
  2. If successful, it binds to the server, passing in authentication data.
  3. It then issues one or more search requests.
  4. When done, it unbinds from the server.

LDAPv2/RFC 1823 provided the APIs ldap_open() and ldap_*bind*() for the first two steps. (I use the wildcard character * to denote that multiple variants of a function are available.) The LDAPv3 API introduces the new API ldap_init(), while at the same time deprecating the use of ldap_open(). The difference between them is that ldap_open() immediately attempts to connect to the server, while ldap_init() doesn't. Since ldap_init() works perfectly with both Exchange Server versions, it seems safe to forget about ldap_open() altogether.

ldap_init() doesn't do much more than return a connection handle; that is, a pointer to a structure of type "LDAP" used to track the status of an LDAP connection. In the LDAPv2 specification, parts of this structure were officially documented (see page 3 of RFC 1823) and could be directly accessed by clients. LDAPv3 discourages direct access, defining the LDAP structure as opaque (although parts of it are still LDAPv2 compatible). To read from and write to selected members of this structure, LDAPv3 adds the ldap_get_option() and ldap_set_option() APIs.

The LDAPLIB Library

Although the LDAP API works on a high abstraction level and saves you from many scary implementation details of the LDAP protocol, some gaps still remain. To fill them, the LdapLib.dll LDAP library presented here hides nasty LDAP details such as version negotiation and server authentication, transparently handles paged search results if appropriate, and the like.

The complete set of LdapLib project files, plus those of a console-oriented LDAP Directory Information Tree (DIT) dump utility (LdapDump.exe) that demonstrates the use of many of the APIs exported by LdapLib.dll, is available electronically. Both projects can be built with Microsoft Visual C/C++ 5.0. No linker options need to be set manually; they are included as #pragma directives in the header files.

At the core of the browsing functions in LdapLib.c is LdapFindCollect() that tests for LDAPv3 and, if available, uses Microsoft's paged results APIs. Otherwise, it falls back to the LDAPv2 standard API ldap_search_st(). LdapFindCollect() dynamically allocates and fills a linked list of LDAP_FIND structures (see Listing One; located at the end of this article), where each list item corresponds to a result page. In the special case of LDAPv2, only a single list item is created.

This linked list approach enables other LdapLib.dll APIs, like LdapFindNext() and LdapFindCount(), to transparently handle search results with or without paging, by simply walking the LDAP_FIND list and evaluating all entries available from each list item. In this context, an LDAPv2 search result is equivalent to an LDAPv3 result that contains just a single page. To clean up the linked list, just call LdapFindCleanup() passing in its anchor (that is, the pointer to the first LDAP_FIND structure).

Since the source code of LdapLib.c is more than 1000 lines in length, it's impossible to reprint it here in its entirety. Listing Two demonstrates another essential excerpt that handles LDAP initialization and binding (server authentication). LdapConnect() receives and initializes an LDAP_CONN structure, defined in Listing One.

The arguments ptServer, ptAccount, and ptPassword are used in the authentication step (see the ldap_bind_s() call in Listing Two). ptServer is the DNS name or IP address of the LDAP server. ptAccount specifies the ID of the user that should be logged in, and ptPassword is an optional password. If the latter is not NULL, "simple authentication" (LDAP_AUTH_SIMPLE) is used. Otherwise, the server is directed to use NT LAN Manager authentication (LDAP_AUTH_NTLM). ptAccount is either a single NT account name, or a domain\name pair. LdapConnect() calls the LdapLib.dll API LdapNameFromAccount() to convert the specified string to the DN format required by the LDAP protocol (cn=<name> or dc=<domain>,cn =<name>).

The remaining arguments -- dPort, dVersion, dPage, and dTimeout -- are connection options that may affect the behavior of subsequent LDAP requests. The easiest way to set them correctly is to pass in the corresponding LDAP_PRESET_* constants defined in LdapLib.h. This will select reasonable default values. dPort is the TCP port used to connect to the server, and its default value is 389. dVersion specifies the required LDAP version, with a default value of 2, corresponding to LDAPv2.

dPage indicates the maximum page size to be used in LDAPv3 search requests. To avoid a return code of LDAP_ADMIN_ LIMIT_EXCEEDED, this value should always be selected less or equal to the value entered for the "Maximum number of search results" option by the Exchange admin. It defaults to 100, which is the preset used by the Exchange Server setup and hence should be okay in most real-life scenarios. If dPage is set to 0, paged results are disabled, and the LdapFindCollect() API refrains from using paged results, even if it detects a server version of LDAPv3 or higher.

The dTimeout argument indicates the number of milliseconds the client is willing to wait on remote operations. It is used on the LDAP API calls ldap_connect(), ldap_search_st(), and ldap_get_ next_page_s(). Its default value is 10,000. This is just enough to avoid timing out on lengthy search operations on slow servers. The LDAP API requires an LDAP_TIMEVAL structure to specify timeout values. LdapLib.dll introduces scalar millisecond values instead (which are much easier to handle), and converts them transparently before all LDAP calls that involve a timeout.

Conclusion

If you need to traverse the directory tree of Exchange Server 5.x and evaluate the attributes attached to its entries, the LDAP interface is an alternative to Extended MAPI, the Exchange core interface. Since LDAP is being standardized in terms of Internet RFCs and drafts, it's an open Standard. However, many aspects of Microsoft's LDAP implementation are semidocumented, and writing LDAP clients is still an empirical task.

DDJ

Listing One

typedef struct _LDAP_CONN    {
    PLDAP        pl;
    DWORD        dPort;
    DWORD        dVersion;
    DWORD        dPage;
    DWORD        dTimeout;
    LDAP_TIMEVAL ltTimeout;
    TBYTE atUser [N_DN];
    }
    LDAP_CONN, *PLDAP_CONN, **PPLDAP_CONN;
#define LDAP_CONN_ sizeof (LDAP_CONN)


typedef struct _LDAP_FIND { struct _LDAP_FIND *plf; struct _LDAP_FIND *plfNext; PLDAP_CONN plc; PLDAPMessage plm; PLDAPMessage plmNext; DWORD dEntries; } LDAP_FIND, *PLDAP_FIND, **PPLDAP_FIND; #define LDAP_FIND_ sizeof (LDAP_FIND)

Back to Article

Listing Two

DWORD EXPORT LdapConnect (PLDAP_CONN plc,                          PTBYTE     ptServer,
                          PTBYTE     ptAccount,
                          PTBYTE     ptPassword,
                          DWORD      dPort,
                          DWORD      dVersion,
                          DWORD      dPage,
                          DWORD      dTimeout)
    {
    DWORD dStatus = LDAP_OTHER;
    plc->pl = NULL;
    plc->dPort    = (dPort    != LDAP_PRESET_PORT    ?  dPort
                                                     : gdPort);
    plc->dVersion = (dVersion != LDAP_PRESET_VERSION ?  dVersion
                                                     : gdVersion);
    plc->dPage    = (dPage    != LDAP_PRESET_PAGE    ?  dPage
                                                     : gdPage);
    plc->dTimeout = (dTimeout != LDAP_PRESET_TIMEOUT ?  dTimeout
                                                     : gdTimeout);
    plc->ltTimeout.tv_sec  = (plc->dTimeout / 1000);
    plc->ltTimeout.tv_usec = (plc->dTimeout % 1000) * 1000;


LdapNameFromAccount (ptAccount, plc->atUser, sizeof (plc->atUser)); if ((plc->pl = ldap_init ((*ptServer ? ptServer : NULL), plc->dPort)) != NULL) { LdapVersionSet (plc, plc->dVersion); if (((dStatus = ldap_connect (plc->pl, &plc->ltTimeout)) != LDAP_SUCCESS) || ((dStatus = ldap_bind_s (plc->pl, plc->atUser, ptPassword, (ptPassword != NULL ? LDAP_AUTH_SIMPLE : LDAP_AUTH_NTLM))) != LDAP_SUCCESS)) { LdapDisconnect (plc); } } else { dStatus = LdapError (); } return dStatus; }

Back to Article

DDJ


Copyright © 1998, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.