Microsoft KB Archive/223049

= How To Query Exchange 5.x Anonymously Through ADSI =

Article ID: 223049

Article Last Modified on 9/28/2007

-

APPLIES TO


 * Microsoft Active Directory Service Interfaces 2.5
 * Microsoft Exchange Server 5.0 Standard Edition
 * Microsoft Exchange Server 5.5 Standard Edition

-



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:

http://msdn.microsoft.com/library/psdk/adsi/dsstartpage_1rg3.htm

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 = ";(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

