How To Query Exchange 5.x Anonymously Through ADSI (223049)



The information in this article applies to:

  • Microsoft Active Directory Service Interfaces 2.5
  • Microsoft Exchange Server 5.0
  • Microsoft Exchange Server 5.5

This article was previously published under Q223049

SUMMARY

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. For more information on searching a directory with ADO, please see the ADSI Programmers Guide in the MSDN: 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 Object
Dim oRecordset As Object
Dim oCommand as Object

Set oConnection = CreateObject("ADODB.Connection")
Set oRecordset = CreateObject("ADODB.Recordset")
Set oCommand = CreateObject("ADODB.Command")

oConnection.Provider = "ADsDSOObject"  'The ADSI OLE-DB provider
oConnection.Properties("User ID") = ""
oConnection.Properties("Password") = ""
oConnection.Properties("Encrypt Password") = False
oConnection.Open "ADs Provider"

' To Do:  Replace ServerName with the name of your server
strQuery = "<GC://ServerName:389>;(objectClass=organizationalPerson);cn,givenName,sn;subtree"

oCommand.ActiveConnection = oConnection
oCommand.CommandText = strQuery
oCommand.Properties("Page Size") = 99 
Set oRecordset = oCommand.Execute

While Not oRecordset.EOF
   Debug.Print oRecordset.Fields("cn") & "    " & oRecordset.Fields("givenName") & " " & oRecordset.Fields("sn")
   oRecordset.MoveNext
Wend

Set oRecordset = Nothing
Set oCommand = Nothing
Set oConnection = Nothing
				

REFERENCES

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:

Modification Type:MinorLast Reviewed:3/25/2005
Keywords:kbhowto kbMsg KB223049