Click here to Skip to main content
13,594,152 members
Click here to Skip to main content
Add your own
alternative version


60 bookmarked
Posted 14 Apr 2002

Implementing Interoperable LDAP Applications

, 14 Apr 2002
Rate this:
Please Sign up or sign in to vote.
Implementing Ldap wrapper classes for both W2K and Unix
<!-- Download Links --> <!-- Add the rest of your HTML here -->


The following document provides an overview of the ClLdap and related classes that implements "portable" code that support LDAP to manage directory services from an application. ClLdap is a high level class and hides most details of managing a connection and directories. The current version also provides MS-SQL external procedures to manage the ClLdap class and LDAP request queueing using MS-Message Queue based on LDIF wrapped by a XML document (read on for details). To handle queued transactions I have added a service that will run in NT/W2k that fetches the transactions from the queue and sudmit them to the Directory Services server using LDAP (protocol).

Why take a look at this work? You can do it your self, but I have spent a lot of  time working and experimenting and coding for LDAP and trying to maintain interoperable (W2K to UNIX and vs) code that implements it. This experience I am trying to give to you may be of value so take a peek. The code samples in other references provide you with samples of calling LDAP API functions, Here I have tried to implement a collection of classes that from my point of view are a lot more simpler to use. For example, with the ClLdap and ClLdapUser classes you can manage users as simple as:

int main(int argc, char* argv[])
    ClLdap myLdap ;

    // connect to default LDAP server and authenticate currently logged user.

    if (myLdap.Connect()) {
        if (myLdap.AuthenticateUser()) {

            // get an "LDAP" related user object located at given RDN
            // (Relative Distinguished Name) "CN=Users,OU=MyOrganization"...

            ClLdapUser myuser(&myLdap,"CN=Users,OU=MyOrganization") ;

            // if this users don't exist, then add them...

            if (!myuser.Exists("Maria"))
               myuser.Add("Maria","batata") ;
            if (!luser.Exists("Carmina"))
               myuser.Add("Carmina","bacalao") ;

            // reset "Maria's password to "malanga"
            // don't miss-interpret the following (YOU CAN'T ACCESS THE
            // PASSWORD OR CHANGE IT IN LDAP) this you will learn fast;
            // deep in a bit later I have more on password info...

            myuser.SetPassword("Maria","malanga") ;

            // find the value of Maria's "CN" (Common-Name)...

            char rVal[MINSTRLEN+1] ;
            myuser.Find("Maria","cn",rVal,MINSTRLEN) ;
            cout << "Maria's CN=" << rVal << endl ;

            // authenticate Maria with the password "malanga"

            myuser.Authenticate("Maria","malanga") ;

            // delete entries...

            myuser.Delete("Maria") ;
            myuser.Delete("Carmina") ;

        // disconnect from server
        myLdap.Disconnect() ;

To learn about LDAP and Active Directory you should get acquainted with the terminology (eg. LDAP, RDN, ...). Beside that, the above code does a lot in very few lines. A nice thing about it is that you will be able to compile the code for Windows 2K or UNIX and "it will work just as well".

Table of Content

[ BACK ]

Things You Should Know
The ClLdap And Related Classes
MS-Message Queue Support
Setting Up Directory Services for LDAP
The ClLDAP Code Package
References and Tools
How to Contact Us
<!-- -------------------------------------------------------------------- --> <!-- Introduction -->


[ BACK ]

This is not the place to look for an overview of Active Directory and/or LDAP this is better covered in given references. Still I will highlight some issues that in my LDAP experimenting / learning where confusing and required some info digin, therefore I am taking some time to explain the things that you should know. If you are interested in LDAP API programming you can jump into the code it self. I will provide a "Code Package" with all the code for your enjoyment. Details about this package is what follows. <!-- -------------------------------------------------------------------- --> <!-- Things that you should know -->

Things You Should Know

[ BACK ]

This is my list of things that you as a minimum should understand to be able to manage the directory services:

Know Your Schema

The first thing you should understand is the schema that your directory is using. The directory has a hierrachical structure as a tree with nodes and leafs. A node and a leaf do have a collection of attributes and those values. The schema has the "metadata" of those attributes and tree objects. In the schema you will find that a node (or a directory object) had to had of some kind (lets say a "User") needs to have a minimum set of attributes to be able to be added and manage. For each attribute defined there are a collection of constraints that most be followed. My advice is that you sit down and spend some time browsing the directory and look at objects, attributes and their values.

A nice tool to do so is the "adsvw.exe" application. When you run it the first thing it will ask you is how you whant to look at the directory. For quick browsing select the "ObjectViewer" option by pressing the "OK" button. Next it will display a dialog box so you type what you will like to view. Here type "ADs:" and unselect the "Use OpenObject" box. By unselecting open object you will be binded (logged in) without entering any id or password. The "ADs:" in turn will possition you at the top (root) of the directory. If all goes well you will be given a "tree viewer" kind of dialog are where you can browse the tree and at the right a view of the object particulars that you are browsing (or possition at). You should find the "adsvw.exe" application in the SDK directory.

Know How To Refer To Entries

This is the next thing that you should master. In the general literature you will find different terms related to an entry position in the tree. The path of an entry in the directory is also known as the "Distinguished Name" or "DN" for short. A partial path is refer as a "Relative Distringushed Name" or "RDN" for short. You will here a lot about the "Naming Context". When you connect to a server this server does have a base or a its root, this must probably not the top of the organization tree but the root of the local hierrarchy. We will refer to this as the "Naming Context" where we will use as our root. For example, an organization that has a corporate office in Puerto Rico may have a domain controller with a "Naming Context" as "".

If you are lots, just have in mind that the LDAP server that you are trying to connect does have a name (a path) in the directory that you use to refer to it. To now about this name go to the "Control Panel" then select the "System" component and click on the "Network Identification" tab. Here you will see the full name of the host, for example "". In this example "myhost" is the name of the computer and "" the base root of your site or what we will call the "Default Naming-Context".

Now that you master how to refer where you are, now you need to know how to refer to objects in the tree. Since this is a tree structure you are probably thinking that is similar to you usual file path names. Yes it is very similar, still you need to get use to tagging each entry item with its attribute name. LDAP entries are refered using the attribute names and their values for example in our sample for the "" the LDAP real name is:


Using Microsoft schema for Active Directory the users will be located at:

"CN=Users, DC=corporate, DC=pr, DC=my-organization, DC=com"

And a particular user (lets say "Maria del Mar") at:

"CN=Maria del Mar,CN=Users, DC=corporate, DC=pr, DC=my-organization, DC=com"

To refer to objects from ADSI point of view you will access or refer to a directory entry by appending "LDAP://" infront of your DN, for example the Maria's directory will be:

"LDAP://CN=Maria del Mar,CN=Users,DC=corporate,DC=pr,DC=my-organization,DC=com"

In summary, every object in the directory has a naming attribute. This attribute serves as the objects's name withing its container. Some of them are:

ATTRIBUTE Meaning of Relative Distinguished Namimg Attributes
cn(common name) Attribute for user objects.
dc(domain component) Naming attributes for domains.
o (organization)
l (locality)
ou(organizational unit)
st(state or province)
c (country)
uid(user id)

The "Distinguished Name" (or DN) of an object is the RDN of the object concatenated with the DN of its container. The fully qualified name of an object in an LDAP directory (its distringuished name) is the concatenation of its relative distringuished name and the distinguished name of its container.

Know That LDAP Uses Unicode

In fact it uses UTF-8 format [8-bits chars] to represent chars of a string. Here the highest order bit is checked and if it is 0 (that is the same as using ASCII codes less than 128), then the characters are assumed to be represented in one byte. Else if the highest level bit is one, the char spans at two bytes. When using Microsoft LDAP API you will not need to be aware of it using UTF-8 since you will use ASCII strings and they later convert them in the UTF-8 format.

Know About Managing Password/s

You can't change a user password using LDAP. Windows Active Directory stores the password in the "unicodePwd" attribute of a user object as an "octectString". If you read the MSDN as of April/2k the definition for the "unicodePwd" is:

"The unicodePwd property is the password for the user. For setting the password of the user, you should use the IADsUser::ChangePassword method (if your script or application is allowing the user to change his/her own password) or IADsUser::SetPassword method (if your script or application is allowing an administrator to reset a password). The password of the user in Windows NT (NT) one-way format (OWF). Windows® 2000 uses the NT OWF. This property is used only by operating system. Note that you cannot derive the clear password back from the OWF form of the password...".

I implemented a change password function that will not port to UNIX since it uses ADSI stuff. Therefore don't try to call it on UNIX boxes...

Lets clarify some issues here. You can't change the user password represented in "unicodePwd" using LDAP as implemented in W2k (if you do have some info on how to do so, please tell me I have not find a way to do so), still, you can authenticate any user as a system user from any other box (NT, W2k or UNIX) by trying to bind it (calling ldap_bind or ldap_simple_bind or related calls [see ClLdap::Authenticateuser method in "ClLdap.cpp" that does exactly that]). If you need to manage and change the password of a system user you where the LDAP server is MS-AD you will doit using W2k Administrative tools or invoking ADSI the related methods to do so (again in a W2k environment).

Still if what you need is to manage none system users (users that will never logon into the system or need to do so) you may manage their password using the "userPassword" available in the "Users" object. This is an "octect string", still you can set it and request it as you like. The provided "ClLdapUser" do just that.

Know About What Can Be Done With The LDAP API's

It is very simple and intuitive. The operations that can be done are:

  1. Connect to an LDAP server. (ldap_init)
  2. Authenticate a user. (ldap_bind)
  3. Add entries. (ldap_add)
  4. Modify entries. (ldap_modify)
  5. Remove entries. (ldap_delete)
  6. Search for entries. (ldap_search)
As you can see, there is not much to it. The given "ldap_" functions are the base ones. They come in two flavors "synchronous" and "asynchronous", for example (ldap_init / ldap_init_s). In this first version of the ClLdap classess I am not using the "async" flavor, still I have implemented an tested a method called "ClLdap::WaitForOneResult(int iMessid)" that will wait for one result when calling the "async" related call (the ones that don't end with "_s").

Probably the most controvertial of all operations is the searching of info. Don't think that you can look for an attribute and have a value. What you must likely will get is a result set. In plain LDAP API use, you will need to be continously requesting for next item in the result set to get the attributes of an object and for each attribute the set of values. I have simplify this operation by hiding all details of moving from attribute to attribute and while fetching values, from attribute to attribute (see Searching For An Entry sample code).

Know About Importing Data To A Directory

To do so you will use a standard format LDIF (LDAP Data Interchange Format). I have implemented and tested a bit of it an LDIF reader with the LDIFEntries class. If you call the "ClLdap::LoadLDIF(char *fname)" method it will try to read an LDIF document and do as needed to add the entries. It should work still I have not tested it in detail. <!-- -------------------------------------------------------------------- --> <!-- Implementing LDAP with the ClLdap class -->

The ClLdap And Related Classes

[ BACK ]

The ClLdap and related classess will give you a headstart to implement more complex LDAP applications. This first implementation I concentrated using the synchronious LDAP calls. Still I have tested some support that is already there for async invocation and works great. In summary the implemented classess are:

ClLdap Provides you to connect, authenticate, set defaults, find entries, dump a tree, add and delete entries.
LDAPEntry This class supports searching / traversing the directory tree.
LDIFEntries Will help you in building entries and support the LDIF (LDAP Data Interchange Format) to manage and populate the directory.
ClLdapUser Using the previous classess this one is oriented to manage basic user management such as adding, updating and removing entries.
Sample code using the classes will follow.

Connecting To The Server

Connecting to the server and getting an LDAP session is the first thing you will do. The next thing to do is to authenticate your self, else the session is worthless. Here (see below) I pass "NULL" to the "Connect" method to connect to the default directory server. If no argument is given as the example at the begining of the document, it will connect as if "NULL" where passed.

int main(int argc, char* argv[])
{  ClLdap myLdap ;

   if (myLdap.Connect(NULL)) {
      if (myLdap.AuthenticateUser(NULL,NULL,AUTH_LOGGED_USER)) {
         // do somthing here...
      // disconnect from server
      myLdap.Disconnect() ;

For authentication I provide some possibilities:

AUTH_ANNONYMOUS This method will ignore the user and password and will try to connect asynchrously as nobody.
AUTH_CLEAR_TEXT Provided a user "Common Name" and a password, this call will try to connect the user asynchrously.
AUTH_LOGGED_USER This authentication method will try to negotiate the authentication and as implemented will try to log with current user credentials.
After you have connected and you are authenticated you can search, add, modify and delete entries.

Adding, Modifying and Removing Entries

With the LDIFEntries and LDAPEntry classes adding, modifying and removing entris is very simple (as implemented in the ClLdapUser class) as shown below.

// ------------------------------------------------------
// Add a User.

bool ClLdapUser::Add(char *pUserId,char *pPassword)
{  bool isok ;

   if (pUserId && pUsersDN && pLdap) {
      char str[MINSTRLEN] ;
      sprintf(str,"CN=%s,%s",pUserId,pUsersDN) ;
      LDIFEntries entries(pLdap->psLdap,str) ;

      entries.AppendToAdd("objectClass","user") ;
      entries.AppendToAdd("cn",pUserId) ;
      entries.AppendToAdd("sAMAccountName",pUserId) ;

      if (pPassword)
         entries.AppendToAdd("userPassword",pPassword) ;

      isok = entries.Add() ;
      isok = false ;

   return(isok) ;
}  // end of add new directory entries...

Let's do some explaining here. This method takes a user-id (eg. "Maria") and a clear text password and set's the "userPassword" attribute to it. this attribute is not the actual user password used by W2k to authenticate a system user. It is an "octectString" attribute that your application have access to do password management not related to NT system security. I do use it for handling users that are not realy part of the system and that don't have access to it. Such as those of applications accessed through the Web. If you take a look in the MSDN and search for "userPassword" you will find that "Site Server" does allow its use to handle subcriptors.

The "pUsersDN" used here is set when you create an instance of the "ClLdapUser" class and represents the RDN of the users path. The "pLdap" variable is also set at creation time and represents a pointer to an instantiated "ClLdap" object.

// ------------------------------------------------------
// Modify User "UserPassword"

bool ClLdapUser::SetPassword(char *pUserId,char *pNewPassword)
{  bool isok ;

   if (pUserId && pUsersDN && pLdap && pNewPassword) {
      char str[MINSTRLEN] ;
      sprintf(str,"CN=%s,%s",pUserId,pUsersDN) ;
      LDIFEntries entries(pLdap->psLdap,str) ;

      if (pNewPassword)
         entries.AppendToUpdate("userPassword",pNewPassword) ;

      isok = entries.Update() ;
      isok = false ;

   return(isok) ;
}  // end of set user password...
As documented earlier the "ClLdapUser" class uses the "userPassword" attribute to store and handle the user password. For an example on how to fetch this password take a look at the "ClLdapUser::Authenticate(...)" method.
// ------------------------------------------------------
// Remove User.

bool ClLdapUser::Delete(char *pUserId)
{  bool isok ;

   if (pUserId && pUsersDN && pLdap) {
      char str[MINSTRLEN] ;
      sprintf(str,"CN=%s,%s",pUserId,pUsersDN) ;
      isok = pLdap->DeleteEntry(str) ;
      isok = false ;

   return(isok) ;
}  // end of add new directory entries...

If you take a look into the "DeleteEntry" function you will see that it uses the "ldap_delete_s" call to do so.

Searching For An Entry

To search for an entry I implement the "LDAPEntry" class. After setting the base DN of your choice, the scope (BASE, ONELEVEL or SUBSTREE), and the object class (or what ever you set so) and what attribute you are looking for you will get a result set for your query. If you enter "NULL" for the target object or the attribute it will fetch everything. By setting the object class to "*" (eg. "objectclass=*") you will fetch all types of objects.

bool ClLdapUser::Find(char *pUserId,char *pAttr,char *pValue,int iValLen)
{  bool isok = false ;

   if (pUserId && pUsersDN && pLdap) {
      char str[MINSTRLEN] ;
      sprintf(str,"CN=%s",pUserId) ;

      LDAPEntry ent ;

      ent.psLdap = pLdap->psLdap ;
      ent.pNamingContext = pUsersDN ;
      ent.Find(str,LDAP_SCOPE_BASE,"objectclass=user",pAttr) ;

      while (!ent.Eof) {
         if (!strcmp(pAttr,ent.pEntryName)) {
            int iLen = strlen(ent.pEntryValue) ;
            if (iLen > iValLen) iLen = iValLen ;
            memcpy(pValue,ent.pEntryValue,iLen) ;
            pValue[iLen] = NULL ;
            isok = true ;
            break ;
         ent.Next() ;
      ent.Free() ;

   return(isok) ;
}  // end of add new directory entries...
I am not trying here to make you an LDAP searching guru, to get acquainted with advance stuff consult provided references, this is a topic well discussed on any of those. <!-- -------------------------------------------------------------------- --> <!-- MS-SQL LDAP Support -->


[ BACK ]

I have added support for using some of the ClLdap functionality by preparing the 'clldaplib.dll' (dynamic link library). To use it just drop the 'dll' into the ".../MSSQL/Binn" directory and execute the 'sp_addextendedproc' MS-SQL store procedure to define the entry points for each function as follows:
-- Define entry points for each available function in 'clldaplib.dll'...

USE master

EXEC sp_addextendedproc 'xp_ClLDAPConnect','clldaplib.dll' ;
EXEC sp_addextendedproc 'xp_ClLDAPDisconnect','clldaplib.dll' ;
EXEC sp_addextendedproc 'xp_ClLDAPSetOrganizationalUnitDN','clldaplib.dll' ;
EXEC sp_addextendedproc 'xp_ClLDAPAddUser','clldaplib.dll' ;
EXEC sp_addextendedproc 'xp_ClLDAPSetPassword','clldaplib.dll' ;
EXEC sp_addextendedproc 'xp_ClLDAPDeleteUser','clldaplib.dll' ;
EXEC sp_addextendedproc 'xp_ClLDAPSetQueueFormatName','clldaplib.dll' ;
As you know, you most be in the master database to be able to add / define external procedures. Therefore we most move to the "master" database first. Having defined 'dll' functions then you will be able to connect to an LDAP server, add and remove users and change their passwords. If you prefer you can queue the transactions (see details in next section). Here are some samples:
USE YourDatabaseName

-- connect to the "ldaphost" and login as administrator

exec master..xp_ClLDAPConnect 'ldaphost','Administrator','admin-password' ;

-- setup the organizational unit distinguished name (DN)
-- this most be defined before attempting to add/delete or modify users...

exec master..xp_ClLDAPSetOrganizationalUnitDN 'ou=subcriptors,dc=mycompany,dc=com' ;

-- add/change password/delete Maria record

exec master..xp_ClLDAPAddUser 'maria','malanga' ;
exec master..xp_ClLDAPSetPassword 'maria','cocoloco' ;
exec master..xp_ClLDAPDeleteUser 'maria' ;

-- disconnect from LDAP server...

exec master..xp_ClLDAPDisconnect

-- now lets queue the transactions...
-- while queuing we don't need to be connected to any server...

exec master..xp_ClLDAPSetQueueFormatName
                 'PUBLIC=93517BE9-9EC7-4FC3-ABBC-E73E9DC3C0C7' ;

exec master..xp_ClLDAPAddUser 'carla','malanga' ;
exec master..xp_ClLDAPSetPassword 'carla','cocoloco' ;
exec master..xp_ClLDAPDeleteUser 'carla' ;

This "external procedures" are available to any database still you most prefix the call with 'master..' to access them. As illustrated you most define the DN where the users are located before requesting any add,delete or set-password directory entries handling. If you prefer you can queue the requests and in this case you can work offline. As you can guess this functionality is only available in W2k/NT platform, the code will still compile on UNIX boxes. You still can access such functionality using ODBC from any other box independently of the OS.

Installing 'ClLdap' MS-SQL Support

I have put together a set of MS-SQL '*.sql' scripts that will help you use the 'ClLdap' class while managing your database. To setup them you most first install the 'dll' as explain above. Then run each script in the order listed. To run the script open the 'MS-SQL Query Analyzer' connect to the server by supplying a log id and password (eg. 'sa' and 'sa-password') and then open each document and execute them by pressing 'F5'. The supplied documents are:

1 ClLdap0.sql Assuming that the 'clldaplib.dll' is in place as already explained, this script will move to the 'master' database and define / setup the entry points to access the 'ClLdap' functionality.
2 ClLdap1.sql This creates a table that will be used to manage the accounts. Included are a set of triggers that are invoke as accounts are added, updated and deleted. Make sure that the supplied triggers point to your 'Customers' table. Update the script as needed to reflect account disabling and recconnection.
3 ClLdap2.sql While handling the ClLdap table you will need some triggers that provide for the actual ClLdap invocation. This script contains all triggers that call for adding, updating and removing accoounts. Try to make adjustments on the 'ClLdap1' and not here.
N/A ClLdapU.sql The script will upload accounts from a given table to the directory. Some assembly is require to configure the upload to your specific needs.
N/A ClLdapX.sql If you need to cleanup all 'ClLdap' stuff, use this script and it will remove all traces of the LDAP support.

After installing the above and configuring it for your particular database, you need to test the installation by using your application and creating, modifying and disabling/removing accounts. <!-- -------------------------------------------------------------------- --> <!-- MS-Message Queue Support -->

MS-Message Queue Support

[ BACK ]

Why queueing? Simple, because you don't need to be concern about being connected or not, or if the server is available or not. Your transactions will be processed when the server is available. This is probably the best method if your LDAP server is on a remove site and/or network communication are not that reliable, or just because you whant peace of mind. Microsoft argues that the MSMQ technology will transfer the message in the order they where queued and that only a single copy will be transfer. The message queue has fancy mechanism to manage administration queues, receiving feed back and other methods to help you be sure that transactions arrive and where fetched from the queue. I will not take time to explain this kind of support still the included code can be setup to support and take advantage of this functionality.

I have been using the message queue since its first release v1.0, when it was available through the NT options pack. In Windows 2000 you just go to the control panel and select the add/remove programs component. Here select "Add/Remove Windows Components" and look for Message Queue there. After the queue is setup create a 'Public Queue' where we will insert the messages. If you have not done this before, just look for the "Computer Management" located in the "Start" -> "Programs" -> "Administrative Tools" menu. For the message queue you will go to the "Services and Applications" on the left window pane and click under "Message Queueing". On the "Public Queues" with a left mouse button click you will select the "New" -> "Queue" option/s.

If you have NT you will install the message queue with the options pack and when done select the "Message Queue Explorer" from the "Start" -> "Programs" -> "NT Options Pack" menu. Here select your site and add a new queue. You may need to change the default permitions from write only to read/write or as needed.

Before we can use the ClLdap message queuing support we need to get the queue format name. On any platfrom position the cursor in to of the queue entry and click the left button and select "Properties". You are looking for the "ID" of the queue (eg. "{93517BE9-9EC7-4FC3-ABBC-E73E9DC3C0C7}") the number inside the curly brackets is what uniquely identify this queue. To set the queue "Format Name" on the ClLdap class you will need to append the "PUBLIC=" at the front, although this is not the only way to specify this property, it does identify a public queue uniquely. As you probably know you may also query for the format name by specifying the host and symbolic queue name and translating the path to a format. I don't like this method because will require that we are connected at least while getting the format-name by querying the MS-Queue server that holds the database. If we set the format by hand then we can connect without quering. After all you will queue to a known queue always and should not be creating or destroying it, therefore it is a static resource that should always be available.

To use the queue just call "ClLdap::SetQueue(QueueFormatName)" and start queuing requests. For example:

ClLdap myLdap ;

myLdap.SetQueue("PUBLIC=93517BE9-9EC7-4FC3-ABBC-E73E9DC3C0C7") ;

ClLdapUser user(&myLdap,"ou=cocos-host-computer,cn=MyCompany,cn=local") ;

user.Add("Maria","calabaza") ;
user.SetPassword("Maria","balcalao") ;
user.Delete("Maria") ;

After queue some request take a pick at the queue (you may need to refresh its content). You will fine something like this (not related to the above sample). This is a sample entry that I did from my "cocos-host-computer.local" host where users are located at "ou=subcriptors".
3C 6C 64 69 66 3E 3C 6C 6E <ldif><ln
3E 64 6E 3A 20 63 6E 3D 6A >dn: cn=j
75 61 6E 63 68 6F 2C 6F 75 uancho,ou
3D 73 75 62 63 72 69 70 74 =subcript
6F 72 73 2C 64 63 3D 62 75 ors,dc=co
69 6C 64 69 6E 67 76 63 73 cos-host-
6F 6C 75 74 69 6F 6E 73 2C computer,
64 63 3D 6C 6F 63 61 6C 3C dc=local<
2F 6C 6E 3E 3C 6C 6E 3E 63 /ln><ln>c
68 61 6E 67 65 74 79 70 65 hangetype
3A 20 41 64 64 3C 2F 6C 6E : Add</ln
3E 3C 6C 6E 3E 6F 62 6A 65 ><ln>obje
63 74 43 6C 61 73 73 3A 20 ctClass:
75 73 65 72 3C 2F 6C 6E 3E user</ln>
3C 6C 6E 3E 63 6E 3A 20 6A <ln>cn: j
75 61 6E 63 68 6F 3C 2F 6C uancho</l
6E 3E 3C 6C 6E 3E 73 41 4D n><ln>sAM
41 63 63 6F 75 6E 74 4E 61 AccountNa
6D 65 3A 20 6A 75 61 6E 63 me: juanc
68 6F 3C 2F 6C 6E 3E 3C 6C ho</ln><l
6E 3E 75 73 65 72 50 61 73 n>userPas
73 77 6F 72 64 3A 20 6D 61 sword: ma
6C 61 6E 67 61 3C 2F 6C 6E langa</ln
3E 3C 2F 6C 64 69 66 3E    ></ldif>

I am also supplying an Window 2000 service that fetches the messages from the queue and submit the requests to an LDAP server. As you can guess this functionality is only available in W2k/NT platforms. The code will still compile for UNIX boxes but will not be able to queue transactions. Using the MS-SQL ClLdap support you can still access such functionality using ODBC from any other box independently of the OS.

Setting Up The ClLDAP Queue Services

To do so, you will prepare an "*.ini" file that will describe to the service how to connect to the directory server, the naming context and some other runtime parameters to control retries, error log, and timeouts. The "ini" file should include the following:

formatname Format Name of queue as explained above.
queuetimeout Timeout time in milliseconds that we will wait for a message if you specify -1 it will wait for ever.
logid Required login id to be able to connect to the Directory server.
password Corresponding password for the previous login id.
logpath Directory path where to store log information. The full path name is created (one by day) as %logpath% + "MMDDYYYY.log".
ldaptimeout Timeout time to wait for asyc requests.
uselog 'y' (in low case) to write to a log file.
ldaphost Host name (or it's IP) where the directory server is located.
userdntemplate Users DN where to locate the system user accounts.
defaultnamingcontext Naming context as above explained.
connectretries Number of connection retries before giving up.
resendretries Number of times to try to resend a request for processing before giving up.
sleeponretry Sleep time before resending / reconnecting in milliseconds.

A sample ini file follows (this are good defaults [for most of it]):
formatname            = PUBLIC=93517BE9-9EC7-4FC3-ABBC-E73E9DC3C0C7
queuetimeout          = -1
logid                 = Administrator
password              = admin
logpath               = c:/temp/
ldaptimeout           = 1000
uselog                = y
ldaphost              = openk2
userdntemplate        = "cn=%s,cn=Users,dc=openk,dc=com"
defaultnamingcontext  = "dc=openk,dc=com"
connectretries        = 3
resendretries         = 3
sleeponretry          = 100
<!-- -------------------------------------------------------------------- --> <!-- Setting Up Directory Services for LDAP -->

Setting Up Directory Services for LDAP

[ BACK ]

To deploy your application you will need the following libraries:

kernel32.dll user32.dll gdi32.dll winspool.dll comdlg32.dll advapi32.dll
shell32.dll ole32.dll oleaut32.dll uuid.dll odbc32.dll odbccp32.dll
opends60.dll wldap32.dll activeds.dll adsiid.dll comsupp.dll
mqrt.dll rpcrt4.dll

The usual stuff. Probably the unusuall ones are the opends60.dll that is required to support the 'clldaplib.dll' to be used by MS-SQL; the wldap32.dll used to suport LDAP calls; mqrt.dll and rpcrt4.dll required for message queueing.

After you get the required libraries in place you will need to setup the client NT's, 98/95's hosts that will use the services. This is very easy, just make sure that they point to your DNS where your directory service host is recognized. Then make sure that you have the LDAP service declared. To do so open it with the notepad.exe. This file is located at "c:\WINNT\system32\drivers\etc\services" (on UNIX "/etc/services"). Make sure that the following line is there:

ldap 389/tcp       #Lightweight Directory Access Protocol

If you don't have a DNS or don't whant to depend on it, just open the "c:\WINNT\system32\drivers\etc\hosts" (on UNIX "/etc/hosts") file with the notepad and add a line with the name of the directory services host and its IP address as follows: ds-host-name

That's it. You should be set to run your application. <!-- -------------------------------------------------------------------- --> <!-- The ClLDAP Code Package -->

The ClLDAP Code Package

[ BACK ]

Don't get scared by the amount of comments in the *.cpp's and *.h'es. There is a lot more comments than code, actually you will see a lot of those comments as part of this document. I tend to write a lot into the code to be self supported and informative so others spend less time trying to figure out what I am doing.

Finally here is the list of stuff included here:

ClLdap.cpp Implementation code for ClLdap, LDAPEntry, LDIFEntries classes.
ClLdap.h The needed include file with ClLdap, LDAPEntry, LDIFEntries and other required declarations.
ClLdapUser.cpp Implementation code for ClLdapUser classes.
ClLdapUser.h The needed include file with ClLdapUser declarations.
LdapConsole.cpp Here is a "main" for a console application that test most of the ClLdap, ClLdapuser and other classes. Compile it and bind it with other given files to test the classes.
fstring.cpp Not the most glorious implementation of a class to handle in memory formated string. Still, if you whant speed and don't care about a bit of funny syntax and code size. This sucker will inline most of the string formating and will perform impresively at run time. We did test it against similar formating using "sprintf" and if you do invoke it a lot it may do more than 30% better, beleave it or not. We did recreate about the same formating samples in VB and got 60+ better performance using this class over VB compiled code. Don't take this as good, do your own tests and get to your own conclution. If your finding differ from mine please inform me about them I will appretiate the info.
fstring.h The needed include file for the fstring.cpp.
clldapservice.cpp Sample 'cpp' that implements an NT/W2K service that drives the reading of a queue.
clldapservice.h The companion include file for 'clldapservice.cpp'.
ClLdap.html This documentation.

After the addition of the MSMQ support I am probably missing some cpp's and include files. If you are really interested in taking a look at the message queuing code tell use and as soon as we have some spare time I will put it together for you. By reviewing the code you will see that there are some support for XML stuff. We do light weight XML here with a class that we implemented that will also port to UNIX. This is also missing on the code package and soon will be included.<!-- List of references -->

References and Tools

[ BACK ]
  1. Howes, T., "LDAP Programming Directory-Enabled Applications with Lightwieght Directory Access Protocol", Macmillan (1997)
    This is my personal favorite for LDAP stuff. Author do give lots of samples and details about implementing LDAP apps.

  2. Kirkpatrick, G., "Active Directory Programming", SAMS (2000)
    If you are looking for a mixture of Active Directory and LDAP stuff this is the best reference that I have found. It provides you with good overview of the Ms Active Directory implementation and how it relates to LDAP. LDAP programming samples are a bit less detailed than Mr. Howes still very usefull. This is my second favorite.

  3. Robinson, S., "Professional ADSI Programming", WROX (1999)
    I did find nice information about available tools to browse and view the directories on W2k. See tools listing below.

  4. Wilcox, M., "Implementing LDAP", WROX (1999)
    A combo of methods to access LDAP on about every existing method.

  5. <a href=>
    Solid LDAP information and their API's from the University of Michigan.

    LDAP Extentions document with lots of other links to more LDAP related info.

Also take a look at the MSDN Library since it has tons of info on ADSI / LDAP / LDIF and directory services. To browse/view the Active Directory take a look at the schema, its properties and entries the following are great:
  1. Active Directory Browser (adsvw.exe) This tool provides you to browse the directory and get acquainted with the schema.

  2. ADSIEDIT (a MMC snap-in) Provide a nice UI to edit entries by hand still it hardly help in your understanding of schema internals.
<!-- -------------------------------------------------------------------- --> <!-- Contact Info -->

How to Contact Us

[ BACK ]

Recalling that you are on your own, still if you feel that you need some help drop in some e-mail at or

I will appreciate any comment of the content and/or advice to improve the quality of the code, documentation or what ever you think it can be improved still I expect that they are constructive.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Web Developer
Puerto Rico Puerto Rico
C/C++ programmer since 1984.

You may also be interested in...


Comments and Discussions

GeneralMissing files Pin
JPawlak23-May-05 14:51
sussJPawlak23-May-05 14:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web02-2016 | 2.8.180618.1 | Last Updated 15 Apr 2002
Article Copyright 2002 by esob
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid