Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

727 lines
20 KiB

  1. //++
  2. //
  3. // Copyright (C) Microsoft Corporation, 1987 - 1999
  4. //
  5. // Module Name:
  6. //
  7. // ldaptest.c
  8. //
  9. // Abstract:
  10. //
  11. // Queries into network drivers
  12. //
  13. // Author:
  14. //
  15. // Anilth - 4-20-1998
  16. //
  17. // Environment:
  18. //
  19. // User mode only.
  20. // Contains NT-specific code.
  21. //
  22. // Revision History:
  23. //
  24. //--
  25. #include "precomp.h"
  26. #include "malloc.h"
  27. BOOL TestLdapOnDc(IN PTESTED_DC TestedDc, NETDIAG_PARAMS* pParams, NETDIAG_RESULT* pResults);
  28. DWORD TestSpnOnDC(IN PTESTED_DC pDcInfo, NETDIAG_RESULT* pResults);
  29. HRESULT LDAPTest( NETDIAG_PARAMS* pParams, NETDIAG_RESULT* pResults)
  30. {
  31. HRESULT hr = S_OK;
  32. BOOL RetVal = TRUE;
  33. PTESTED_DOMAIN TestedDomain = pParams->pDomain;
  34. PTESTED_DC TestedDc = NULL;
  35. PLIST_ENTRY ListEntry;
  36. BOOLEAN OneLdapFailed = FALSE;
  37. BOOLEAN OneLdapWorked = FALSE;
  38. BOOL fSpnTested = FALSE;
  39. BOOL fSpnPassed = FALSE;
  40. NET_API_STATUS NetStatus;
  41. //if the machine is a member machine or DC, LDAP Test will get called.
  42. //Otherwise, the test will be skipped
  43. pResults->LDAP.fPerformed = TRUE;
  44. // assume link entry is initialized to 0000
  45. if(pResults->LDAP.lmsgOutput.Flink == NULL)
  46. InitializeListHead(&pResults->LDAP.lmsgOutput);
  47. PrintStatusMessage(pParams, 4, IDS_LDAP_STATUS_MSG, TestedDomain->PrintableDomainName);
  48. //
  49. // If a DC hasn't been discovered yet,
  50. // find one.
  51. //
  52. if ( TestedDomain->DcInfo == NULL )
  53. {
  54. LPTSTR pszDcType;
  55. if ( TestedDomain->fTriedToFindDcInfo )
  56. {
  57. CHK_HR_CONTEXT(pResults->LDAP, S_FALSE, IDS_LDAP_NODC);
  58. }
  59. pszDcType = LoadAndAllocString(IDS_DCTYPE_DC);
  60. NetStatus = DoDsGetDcName( pParams,
  61. pResults,
  62. &pResults->LDAP.lmsgOutput,
  63. TestedDomain,
  64. DS_DIRECTORY_SERVICE_PREFERRED,
  65. "DC",
  66. FALSE,
  67. &TestedDomain->DcInfo );
  68. Free(pszDcType);
  69. TestedDomain->fTriedToFindDcInfo = TRUE;
  70. if ( NetStatus != NO_ERROR )
  71. {
  72. CHK_HR_CONTEXT(pResults->LDAP, hr = HRESULT_FROM_WIN32(NetStatus), IDS_LDAP_NODC);
  73. }
  74. }
  75. //
  76. // Ensure the DC is running the Ds.
  77. //
  78. if ( (TestedDomain->DcInfo->Flags & DS_DS_FLAG) == 0 )
  79. {
  80. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0, IDS_LDAP_NOTRUNNINGDS,
  81. TestedDomain->DcInfo->DomainControllerName);
  82. }
  83. //
  84. // Test ldap on all of the found DCs in the domain.
  85. //
  86. for ( ListEntry = TestedDomain->TestedDcs.Flink ;
  87. ListEntry != &TestedDomain->TestedDcs ;
  88. ListEntry = ListEntry->Flink ) {
  89. //
  90. // Loop through the list of DCs in this domain
  91. //
  92. TestedDc = CONTAINING_RECORD( ListEntry, TESTED_DC, Next );
  93. //
  94. // Only run test on DCs that might support LDAP.
  95. //
  96. if ( TestedDc->Flags & DC_IS_NT5 )
  97. {
  98. if ( !TestLdapOnDc( TestedDc, pParams, pResults ) )
  99. OneLdapFailed = TRUE;
  100. else
  101. OneLdapWorked = TRUE;
  102. //test the SPN registration if this is a DC on the primary domain
  103. if (TestedDomain->fPrimaryDomain)
  104. {
  105. fSpnTested = TRUE;
  106. if (TestSpnOnDC(TestedDc, pResults))
  107. fSpnPassed = TRUE;
  108. }
  109. }
  110. else
  111. {
  112. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  113. IDS_LDAP_NOTRUNNINGDS_SKIP, TestedDc->ComputerName);
  114. }
  115. }
  116. //
  117. // If one of the DCs failed,
  118. // and none worked.
  119. // Don't do any more tests.
  120. //
  121. if ( OneLdapFailed && !OneLdapWorked )
  122. {
  123. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  124. IDS_LDAP_NOLDAPSERVERSWORK, TestedDomain->PrintableDomainName);
  125. CHK_HR_CONTEXT(pResults->LDAP, hr = E_FAIL, 0);
  126. }
  127. if ( fSpnTested && !fSpnPassed )
  128. {
  129. //IDS_LDAP_NO_SPN " [FATAL] The default SPNs are not properly registered on and DCs.\n"
  130. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  131. IDS_LDAP_NO_SPN);
  132. CHK_HR_CONTEXT(pResults->LDAP, hr = E_FAIL, 0);
  133. }
  134. L_ERR:
  135. //$REVIEW (nsun) we should return S_FALSE or S_OK
  136. //so that we can go on with other tests
  137. if (!FHrOK(hr))
  138. hr = S_FALSE;
  139. return hr;
  140. }
  141. DWORD TestSpnOnDC(IN PTESTED_DC pDcInfo, NETDIAG_RESULT* pResults)
  142. {
  143. WCHAR FlatName[ MAX_PATH + 1 ];
  144. PWSTR Dot ;
  145. HANDLE hDs = NULL;
  146. ULONG NetStatus ;
  147. PDS_NAME_RESULTW Result ;
  148. LPWSTR Flat = FlatName;
  149. LDAP *ld = NULL;
  150. int rc;
  151. LDAPMessage *e, *res = NULL;
  152. WCHAR *base_dn;
  153. WCHAR *search_dn, search_ava[ MAX_PATH + 30 ];
  154. WCHAR Domain[ MAX_PATH + 1 ];
  155. CHAR szDefaultFqdnSpn[MAX_PATH + 10];
  156. CHAR szDefaultShortSpn[MAX_PATH + 10];
  157. BOOL fFqdnSpnFound = FALSE;
  158. BOOL fShortSpnFound = FALSE;
  159. BOOL fFailQuerySpn = FALSE;
  160. USES_CONVERSION;
  161. //construct the default SPN's
  162. lstrcpy(szDefaultFqdnSpn, _T("HOST/"));
  163. lstrcat(szDefaultFqdnSpn, pResults->Global.szDnsHostName);
  164. lstrcpy(szDefaultShortSpn, _T("HOST/"));
  165. lstrcat(szDefaultShortSpn, W2T(pResults->Global.swzNetBiosName));
  166. wcscpy(Domain, GetSafeStringW(pDcInfo->TestedDomain->NetbiosDomainName ?
  167. pDcInfo->TestedDomain->NetbiosDomainName :
  168. pDcInfo->TestedDomain->DnsDomainName));
  169. ld = ldap_open(W2A(pDcInfo->ComputerName), LDAP_PORT);
  170. if (ld == NULL) {
  171. DebugMessage2("ldap_init failed = %x", LdapGetLastError());
  172. fFailQuerySpn = TRUE;
  173. goto L_ERROR;
  174. }
  175. rc = ldap_bind_s(ld, NULL, NULL, LDAP_AUTH_NEGOTIATE);
  176. if (rc != LDAP_SUCCESS) {
  177. DebugMessage2("ldap_bind failed = %x", LdapGetLastError());
  178. fFailQuerySpn = TRUE;
  179. goto L_ERROR;
  180. }
  181. NetStatus = DsBindW( NULL, Domain, &hDs );
  182. if ( NetStatus != 0 )
  183. {
  184. DebugMessage3("Failed to bind to DC of domain %ws, %#x\n",
  185. Domain, NetStatus );
  186. fFailQuerySpn = TRUE;
  187. goto L_ERROR;
  188. }
  189. Dot = wcschr( Domain, L'.' );
  190. if ( Dot )
  191. {
  192. *Dot = L'\0';
  193. }
  194. wcscpy( FlatName, Domain );
  195. if ( Dot )
  196. {
  197. *Dot = L'.' ;
  198. }
  199. wcscat( FlatName, L"\\" );
  200. wcscat( FlatName, pResults->Global.swzNetBiosName );
  201. wcscat( FlatName, L"$" );
  202. NetStatus = DsCrackNamesW(
  203. hDs,
  204. 0,
  205. DS_NT4_ACCOUNT_NAME,
  206. DS_FQDN_1779_NAME,
  207. 1,
  208. &Flat,
  209. &Result );
  210. if ( NetStatus != 0)
  211. {
  212. DebugMessage3("Failed to crack name %ws into the FQDN, %#x\n",
  213. FlatName, NetStatus );
  214. DsUnBind( &hDs );
  215. fFailQuerySpn = TRUE;
  216. goto L_ERROR;
  217. }
  218. search_dn = pResults->Global.swzNetBiosName;
  219. if (0 == Result->cItems)
  220. {
  221. DsUnBind( &hDs );
  222. fFailQuerySpn = TRUE;
  223. goto L_ERROR;
  224. }
  225. if (DS_NAME_NO_ERROR != Result->rItems[0].status || NULL == Result->rItems[0].pName)
  226. {
  227. DsUnBind( &hDs );
  228. fFailQuerySpn = TRUE;
  229. goto L_ERROR;
  230. }
  231. base_dn = wcschr(Result->rItems[0].pName, L',');
  232. if (!base_dn)
  233. base_dn = Result->rItems[0].pName;
  234. else
  235. base_dn++;
  236. DsUnBind( &hDs );
  237. swprintf(search_ava, L"(sAMAccountName=%s$)", search_dn);
  238. rc = ldap_search_s(ld, W2A(base_dn), LDAP_SCOPE_SUBTREE,
  239. W2A(search_ava), NULL, 0, &res);
  240. //base_dn can no longer be used because base_dn refers to that buffer
  241. DsFreeNameResultW( Result );
  242. if (rc != LDAP_SUCCESS) {
  243. DebugMessage2("ldap_search_s failed: %s", ldap_err2string(rc));
  244. fFailQuerySpn = TRUE;
  245. goto L_ERROR;
  246. }
  247. for (e = ldap_first_entry(ld, res);
  248. e;
  249. e = ldap_next_entry(ld, e))
  250. {
  251. BerElement *b;
  252. CHAR *attr;
  253. //IDS_LDAP_REG_SPN "Registered Service Principal Names:\n"
  254. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  255. IDS_LDAP_REG_SPN);
  256. for (attr = ldap_first_attribute(ld, e, &b);
  257. attr;
  258. attr = ldap_next_attribute(ld, e, b))
  259. {
  260. CHAR **values, **p;
  261. values = ldap_get_values(ld, e, attr);
  262. for (p = values; *p; p++)
  263. {
  264. if (strcmp(attr, "servicePrincipalName") == 0)
  265. {
  266. // IDS_LDAP_SPN_NAME " %s\n"
  267. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  268. IDS_LDAP_SPN_NAME, *p);
  269. if (0 == _stricmp(*p, szDefaultFqdnSpn))
  270. fFqdnSpnFound = TRUE;
  271. else if (0 == _stricmp(*p, szDefaultShortSpn))
  272. fShortSpnFound = TRUE;
  273. }
  274. }
  275. ldap_value_free(values);
  276. ldap_memfree(attr);
  277. }
  278. }
  279. L_ERROR:
  280. if (res)
  281. {
  282. ldap_msgfree(res);
  283. }
  284. if (ld)
  285. {
  286. ldap_unbind(ld);
  287. }
  288. //Only report fatal error when we successfully query SPN registration
  289. //and all DCs doesn't have the default SPN's
  290. if (fFailQuerySpn)
  291. {
  292. //IDS_LDAP_SPN_FAILURE "Failed to query SPN registration from DC %ws.\n"
  293. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  294. IDS_LDAP_SPN_FAILURE, pDcInfo->ComputerName);
  295. return TRUE;
  296. }
  297. else if (!fFqdnSpnFound || !fShortSpnFound)
  298. {
  299. //IDS_LDAP_SPN_MISSING " [WARNING] The default SPN registration for '%s' is missing on DC '%ws'.\n"
  300. if (!fFqdnSpnFound)
  301. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  302. IDS_LDAP_SPN_MISSING, szDefaultFqdnSpn, pDcInfo->ComputerName);
  303. if (!fShortSpnFound)
  304. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  305. IDS_LDAP_SPN_MISSING, szDefaultShortSpn, pDcInfo->ComputerName);
  306. return FALSE;
  307. }
  308. else
  309. return TRUE;
  310. }
  311. void LDAPGlobalPrint(IN NETDIAG_PARAMS *pParams, IN OUT NETDIAG_RESULT *pResults)
  312. {
  313. if (pParams->fVerbose || !FHrOK(pResults->LDAP.hr))
  314. {
  315. PrintNewLine(pParams, 2);
  316. PrintTestTitleResult(pParams, IDS_LDAP_LONG, IDS_LDAP_SHORT, pResults->LDAP.fPerformed,
  317. pResults->LDAP.hr, 0);
  318. if (!FHrOK(pResults->LDAP.hr))
  319. {
  320. if(pResults->LDAP.idsContext)
  321. PrintError(pParams, pResults->LDAP.idsContext, pResults->LDAP.hr);
  322. }
  323. PrintMessageList(pParams, &pResults->LDAP.lmsgOutput);
  324. }
  325. }
  326. void LDAPPerInterfacePrint(IN NETDIAG_PARAMS *pParams,
  327. IN OUT NETDIAG_RESULT *pResults,
  328. IN INTERFACE_RESULT *pIfResult)
  329. {
  330. // no perinterface information
  331. }
  332. void LDAPCleanup(IN NETDIAG_PARAMS *pParams,
  333. IN OUT NETDIAG_RESULT *pResults)
  334. {
  335. MessageListCleanUp(&pResults->LDAP.lmsgOutput);
  336. pResults->LDAP.lmsgOutput.Flink = NULL;
  337. }
  338. BOOL
  339. TestLdapOnDc(
  340. IN PTESTED_DC TestedDc,NETDIAG_PARAMS* pParams, NETDIAG_RESULT* pResults
  341. )
  342. /*++
  343. Routine Description:
  344. Ensure we can use LDAP focused at the specified DC
  345. Arguments:
  346. TestedDc - Description of DC to test
  347. Return Value:
  348. TRUE: Test suceeded.
  349. FALSE: Test failed
  350. --*/
  351. {
  352. NET_API_STATUS NetStatus;
  353. NTSTATUS Status;
  354. BOOL RetVal = TRUE;
  355. int LdapMessageId;
  356. PLDAPMessage LdapMessage = NULL;
  357. PLDAPMessage CurrentEntry;
  358. int LdapError;
  359. ULONG AuthType;
  360. LPSTR AuthTypeName;
  361. LPWSTR DcIpAddress;
  362. LDAP *LdapHandle = NULL;
  363. //
  364. // Avoid this test if the DC is already known to be down.
  365. //
  366. if ( TestedDc->Flags & DC_IS_DOWN ) {
  367. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  368. IDS_LDAP_DCDOWN, TestedDc->ComputerName);
  369. goto Cleanup;
  370. }
  371. //
  372. // If there is no IP Address,
  373. // get it.
  374. //
  375. if ( !GetIpAddressForDc( TestedDc ) ) {
  376. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  377. IDS_LDAP_NOIPADDR, TestedDc->ComputerName);
  378. RetVal = FALSE;
  379. goto Cleanup;
  380. }
  381. DcIpAddress = TestedDc->DcIpAddress;
  382. //
  383. // Loop trying each type of authentication.
  384. //
  385. for ( AuthType = 0; AuthType < 3; AuthType++ ) {
  386. int AuthMethod;
  387. SEC_WINNT_AUTH_IDENTITY_W NtAuthIdentity;
  388. LPSTR AuthGuru;
  389. //
  390. // Bind as appropropriate
  391. //
  392. RtlZeroMemory( &NtAuthIdentity, sizeof(NtAuthIdentity));
  393. NtAuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
  394. switch ( AuthType ) {
  395. case 0:
  396. AuthTypeName = "un-";
  397. break;
  398. case 1:
  399. AuthTypeName = "NTLM ";
  400. AuthMethod = LDAP_AUTH_NTLM;
  401. AuthGuru = NTLM_GURU;
  402. break;
  403. case 2:
  404. AuthTypeName = "Negotiate ";
  405. AuthMethod = LDAP_AUTH_NEGOTIATE;
  406. AuthGuru = KERBEROS_GURU;
  407. break;
  408. }
  409. //
  410. // Only Members and Domain controllers can use authenticated RPC.
  411. //
  412. if ( AuthType != 0 ) {
  413. if ( pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleMemberWorkstation ||
  414. pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleMemberServer ||
  415. pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleBackupDomainController ||
  416. pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RolePrimaryDomainController ) {
  417. //
  418. // If we're logged onto a local account,
  419. // we can't test authenticated RPC.
  420. //
  421. if ( pResults->Global.pLogonDomain == NULL ) {
  422. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  423. IDS_LDAP_LOGONASLOCALUSER,
  424. pResults->Global.pLogonDomainName,
  425. pResults->Global.pLogonUser,
  426. AuthTypeName, TestedDc->ComputerName);
  427. goto Cleanup;
  428. }
  429. } else {
  430. goto Cleanup;
  431. }
  432. }
  433. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  434. IDS_LDAP_DOAUTHEN,
  435. AuthTypeName, TestedDc->ComputerName);
  436. //
  437. // Cleanup from a previous iteration.
  438. //
  439. if ( LdapMessage != NULL )
  440. {
  441. ldap_msgfree( LdapMessage );
  442. LdapMessage = NULL;
  443. }
  444. if ( LdapHandle != NULL )
  445. {
  446. ldap_unbind( LdapHandle );
  447. LdapHandle = NULL;
  448. }
  449. //
  450. // Connect to the DC.
  451. //
  452. LdapHandle = ldap_openW( DcIpAddress, LDAP_PORT );
  453. if ( LdapHandle == NULL )
  454. {
  455. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  456. IDS_LDAP_CANNOTOPEN,
  457. TestedDc->ComputerName, DcIpAddress );
  458. goto Cleanup;
  459. }
  460. //
  461. // Bind to the DC.
  462. //
  463. if ( AuthType != 0 ) {
  464. LdapError = ldap_bind_s( LdapHandle, NULL, (char *)&NtAuthIdentity, AuthMethod );
  465. if ( LdapError != LDAP_SUCCESS ) {
  466. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  467. IDS_LDAP_CANNOTBIND,
  468. AuthTypeName, TestedDc->ComputerName, ldap_err2stringA(LdapError) );
  469. //
  470. // Try other authentication methods.
  471. //
  472. RetVal = FALSE;
  473. continue;
  474. }
  475. }
  476. //
  477. // Do a trivial search to isolate LDAP problems from authentication
  478. // problems
  479. //
  480. LdapError = ldap_search_sA(
  481. LdapHandle,
  482. NULL, // DN
  483. LDAP_SCOPE_BASE,
  484. "(objectClass=*)", // filter
  485. NULL,
  486. FALSE,
  487. &LdapMessage );
  488. if ( LdapError != LDAP_SUCCESS ) {
  489. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  490. IDS_LDAP_CANNOTSEARCH,
  491. AuthTypeName, TestedDc->ComputerName, ldap_err2stringA(LdapError) );
  492. goto Cleanup;
  493. }
  494. //
  495. // How many entries were returned.
  496. //
  497. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  498. IDS_LDAP_ENTRIES,
  499. ldap_count_entries( LdapHandle, LdapMessage ) );
  500. //
  501. // Print the entries.
  502. //
  503. CurrentEntry = ldap_first_entry( LdapHandle, LdapMessage );
  504. while ( CurrentEntry != NULL )
  505. {
  506. PVOID Context;
  507. char *AttrName;
  508. //
  509. // Test for error
  510. //
  511. if ( LdapHandle->ld_errno != 0 )
  512. {
  513. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  514. IDS_LDAP_CANNOTFIRSTENTRY,
  515. TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) );
  516. goto Cleanup;
  517. }
  518. //
  519. // Walk through the list of returned attributes.
  520. //
  521. AttrName = ldap_first_attributeA( LdapHandle, CurrentEntry, (PVOID)&Context );
  522. while ( AttrName != NULL )
  523. {
  524. PLDAP_BERVAL *Berval;
  525. //
  526. // Test for error
  527. //
  528. if ( LdapHandle->ld_errno != 0 ) {
  529. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  530. IDS_LDAP_CANNOTFIRSTATTR,
  531. TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) );
  532. goto Cleanup;
  533. }
  534. //
  535. // Grab the attribute and it's value
  536. //
  537. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  538. IDS_LDAP_ATTR, AttrName );
  539. Berval = ldap_get_values_lenA( LdapHandle, CurrentEntry, AttrName );
  540. if ( Berval == NULL )
  541. {
  542. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  543. IDS_LDAP_CANNOTLEN,
  544. TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) );
  545. goto Cleanup;
  546. }
  547. else
  548. {
  549. int i;
  550. for ( i=0; Berval[i] != NULL; i++ ) {
  551. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  552. IDS_LDAP_VAL,
  553. Berval[i]->bv_len, Berval[i]->bv_val );
  554. }
  555. ldap_value_free_len( Berval );
  556. }
  557. //
  558. // Get the next entry
  559. //
  560. AttrName = ldap_next_attributeA( LdapHandle, CurrentEntry, (PVOID)Context );
  561. }
  562. //
  563. // Get the next entry
  564. //
  565. CurrentEntry = ldap_next_entry( LdapHandle, CurrentEntry );
  566. }
  567. }
  568. Cleanup:
  569. if ( LdapMessage != NULL ) {
  570. ldap_msgfree( LdapMessage );
  571. }
  572. return RetVal;
  573. }