How To Query Exchange 5.x Anonymously Through ADSI

This article was previously published under Q223049
This article has been archived. It is offered "as is" and will no longer be updated.
In order to query a Microsoft Exchange Server anonymously you must prevent an objectClass search on certain objects. This article explains how to avoid a base-level search and successfully query an Exchange 5.x server anonymously.
More information
Active Directory Service Interfaces (ADSI) is a set of COM interfaces that make accessing directories easier for programmers. ADSI is built on the provider-base model, which allows the individual providers to natively communicate with their respective directories. When querying an LDAP server the preferred interface is IDirectorySearch for non-automation clients, while ActiveX Data Objects (ADO) should be used with automation languages, such as Visual Basic and VBScript. When querying an LDAP server, a username and password can be given to specify the account under which the query should take place. This is done through IDirectorySearch by requesting that interface from ADsOpenObject(). Credentials are passed in ADO by setting the "User ID" and "Password" properties on the Connection object.

Often, the application that you are writing will be designed to access the directory in the security context in which is it currently running. In this case, a username and password should not be specified. ADSI attempts to bind to the server using the current credentials.

There may also be times that you would like to query a directory anonymously. Binding anonymously can be achieved by setting the username and password to an empty string. Depending on the directory, the anonymous user may or may not have permissions to see certain objects. In Microsoft Exchange 5.5 this is configurable through Admin.exe for each Exchange site by clicking Site, Configuration, DS Site Configuration, Attributes tab, Configure, and then selecting Anonymous Requests.

Notice that permissions for the anonymous user to view certain properties can only be given to mail recipient objects.

In your query you will need to specify where you would like to begin your search. This is done by passing the distinguished name of that object. Your search can begin anywhere in the directory hierarchy. By default, the first thing that happens when your query runs is ADSI checks to see if the object that you specified actually exists. It does this with base search for that object's class. When the objectClass property is requested for an object the Exchange server checks to see if the user has permission to view that object. If the object does not exist or the anonymous user does not have sufficient permissions to view properties this search fails. If the initial base level search fails ADSI will not continue with the query.

Since the anonymous user does not have permissions to view properties on container objects, any search performed fails unless the distinguished name of a mail recipient is specified.In order to query Exchange anonymously you must prevent an objectClass search on certain objects. This is done with the ADS_FAST_BIND flag which can be used in Visual Basic through OpenDSObject() or in Visual C++ with ADsOpenObject(). Below is an example of how this flag can be used in Visual C++ to anonymously query Exchange with IDirectorySearch:
	CoInitialize(NULL);	HRESULT hr = S_OK;	IDirectorySearch *pContainerToSearch = NULL;	LPOLESTR szPath = new OLECHAR[MAX_PATH];	   // TO DO: replace MyServer with the name of your Exchange server   // followed by the proper distinguished name for your start location	wcscpy(szPath,L"LDAP://MyServer/o=MyOrganization");	hr = ADsOpenObject(szPath,		L"",		L"",		ADS_FAST_BIND,		IID_IDirectorySearch,		(void**)&pContainerToSearch);		if (SUCCEEDED(hr))	{		LPOLESTR pszSearchFilter = new OLECHAR[MAX_PATH];				//Add the filter.		wsprintf(pszSearchFilter, L"(objectClass=organizationalPerson)");				//Specify subtree and paged search		DWORD dwNumPrefs = 2;		ADS_SEARCHPREF_INFO SearchPrefs[2];		SearchPrefs[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;		SearchPrefs[0].vValue.dwType = ADSTYPE_INTEGER;		SearchPrefs[0].vValue.Integer = ADS_SCOPE_SUBTREE;		SearchPrefs[1].dwSearchPref = ADS_SEARCHPREF_PAGESIZE;		SearchPrefs[1].vValue.dwType = ADSTYPE_INTEGER;		SearchPrefs[1].vValue.Integer = 99;				LPWSTR pszAttr[] = { L"cn", L"givenName", L"sn" };		ADS_SEARCH_HANDLE hSearch;		ADS_SEARCH_COLUMN col;		LPOLESTR pszColumn = NULL;  		DWORD dwCount= sizeof(pszAttr)/sizeof(LPWSTR);		int iCount = 0;		DWORD x = 0L;				// Set the search preference		hr = pContainerToSearch->SetSearchPreference( SearchPrefs, dwNumPrefs);		if (FAILED(hr))			return hr;				hr = pContainerToSearch->ExecuteSearch(pszSearchFilter, pszAttr, dwCount, &hSearch );		if ( SUCCEEDED(hr) )		{    			// Call IDirectorySearch::GetNextRow() to retrieve the next row of data			hr = pContainerToSearch->GetFirstRow( hSearch);			if (SUCCEEDED(hr))			{				while( hr != S_ADS_NOMORE_ROWS )				{					//Keep track of count.					iCount++;					// loop through the array of passed column names,					// print the data for each column										while( pContainerToSearch->GetNextColumnName( hSearch, &pszColumn ) != S_ADS_NOMORE_COLUMNS )					{						hr = pContainerToSearch->GetColumn( hSearch, pszColumn, &col );						if ( SUCCEEDED(hr) )						{							for (x = 0; x< col.dwNumValues; x++)								wprintf(L"%s: %s  ",col.pszAttrName,col.pADsValues[x].CaseIgnoreString);						}												pContainerToSearch->FreeColumn( &col );					}					FreeADsMem( pszColumn );					//Get the next row					hr = pContainerToSearch->GetNextRow( hSearch);					wprintf(L"\n");				}			}				}		// Close the search handle to clean up		pContainerToSearch->CloseSearchHandle(hSearch);		if (SUCCEEDED(hr) && 0==iCount)			hr = S_FALSE;	}	if (pContainerToSearch)		pContainerToSearch->Release();						
ADSI is the standard method for accessing information which is contained in the Windows 2000 Active Directory. The ADSI libraries that ships with Windows 2000 contain enhancements over the version 2.5 libraries. One of these is the inclusion of a ADSI Flag property in the OLE-DB provider. Setting the ADS_FAST_BIND flag on this property for the ADO Connection will have the same effect as it does in OpenDSObject(). This flag is not available via ADO in ADSI 2.5.

In Windows 2000 there is a concept of the Global Catalog which contains information on every object in the Active Directory. The Global Catalog can be accessed through LDAP on TCP Port 3268, and natively in ADSI with the GC provider.

The ADSI 2.5 runtime library will not attempt to verify the existence of the Global Catalog in an ADO query when only the server name is given without the presence of a distinguished name. In this case a query will be issued without a base object. Therefore, if you specify that the GC provider should use TCP port 389 (default LDAP) you can search a directory without an objectClass base search. Unfortunately, this is not true if you specify a distinguished name along with the name of the server, in that case the base level search will be performed.

Although this is not the original intended purpose for the GC provider, you can use it as a work around. This is demonstrated in the following example which uses the GC provider with ADO and ADSI 2.5 to query a Microsoft Exchange 5.x server for all mailboxes anonymously:
Dim oConnection As ObjectDim oRecordset As ObjectDim oCommand as ObjectSet oConnection = CreateObject("ADODB.Connection")Set oRecordset = CreateObject("ADODB.Recordset")Set oCommand = CreateObject("ADODB.Command")oConnection.Provider = "ADsDSOObject"  'The ADSI OLE-DB provideroConnection.Properties("User ID") = ""oConnection.Properties("Password") = ""oConnection.Properties("Encrypt Password") = FalseoConnection.Open "ADs Provider"' To Do:  Replace ServerName with the name of your serverstrQuery = "<GC://ServerName:389>;(objectClass=organizationalPerson);cn,givenName,sn;subtree"oCommand.ActiveConnection = oConnectionoCommand.CommandText = strQueryoCommand.Properties("Page Size") = 99 Set oRecordset = oCommand.ExecuteWhile Not oRecordset.EOF   Debug.Print oRecordset.Fields("cn") & "    " & oRecordset.Fields("givenName") & " " & oRecordset.Fields("sn")   oRecordset.MoveNextWendSet oRecordset = NothingSet oCommand = NothingSet oConnection = Nothing				
For additional information on ADSI, please refer to the following articles in the Microsoft Knowledge Base:
233023 How To Find All ADSI Providers on a System

187529 How To Using ADO to Access Objects Through ADSI LDAP Provider

You can also get more information on ADSI from the following Microsoft WEB site:

Article ID: 223049 - Last Review: 11/02/2013 22:58:00 - Revision: 5.0

  • Microsoft Exchange Server 5.0 Standard Edition
  • Microsoft Exchange Server 5.5 Standard Edition
  • kbnosurvey kbarchive kbhowto kbmsg KB223049