Source code of Windows XP (NT5)
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.

723 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;
  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->DnsDomainName ?
  167. pDcInfo->TestedDomain->DnsDomainName :
  168. pDcInfo->TestedDomain->NetbiosDomainName));
  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. ldap_msgfree(res);
  280. L_ERROR:
  281. if (ld)
  282. {
  283. ldap_unbind(ld);
  284. }
  285. //Only report fatal error when we successfully query SPN registration
  286. //and all DCs doesn't have the default SPN's
  287. if (fFailQuerySpn)
  288. {
  289. //IDS_LDAP_SPN_FAILURE "Failed to query SPN registration from DC %ws.\n"
  290. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  291. IDS_LDAP_SPN_FAILURE, pDcInfo->ComputerName);
  292. return TRUE;
  293. }
  294. else if (!fFqdnSpnFound || !fShortSpnFound)
  295. {
  296. //IDS_LDAP_SPN_MISSING " [WARNING] The default SPN registration for '%s' is missing on DC '%ws'.\n"
  297. if (!fFqdnSpnFound)
  298. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  299. IDS_LDAP_SPN_MISSING, szDefaultFqdnSpn, pDcInfo->ComputerName);
  300. if (!fShortSpnFound)
  301. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  302. IDS_LDAP_SPN_MISSING, szDefaultShortSpn, pDcInfo->ComputerName);
  303. return FALSE;
  304. }
  305. else
  306. return TRUE;
  307. }
  308. void LDAPGlobalPrint(IN NETDIAG_PARAMS *pParams, IN OUT NETDIAG_RESULT *pResults)
  309. {
  310. if (pParams->fVerbose || !FHrOK(pResults->LDAP.hr))
  311. {
  312. PrintNewLine(pParams, 2);
  313. PrintTestTitleResult(pParams, IDS_LDAP_LONG, IDS_LDAP_SHORT, pResults->LDAP.fPerformed,
  314. pResults->LDAP.hr, 0);
  315. if (!FHrOK(pResults->LDAP.hr))
  316. {
  317. if(pResults->LDAP.idsContext)
  318. PrintError(pParams, pResults->LDAP.idsContext, pResults->LDAP.hr);
  319. }
  320. PrintMessageList(pParams, &pResults->LDAP.lmsgOutput);
  321. }
  322. }
  323. void LDAPPerInterfacePrint(IN NETDIAG_PARAMS *pParams,
  324. IN OUT NETDIAG_RESULT *pResults,
  325. IN INTERFACE_RESULT *pIfResult)
  326. {
  327. // no perinterface information
  328. }
  329. void LDAPCleanup(IN NETDIAG_PARAMS *pParams,
  330. IN OUT NETDIAG_RESULT *pResults)
  331. {
  332. MessageListCleanUp(&pResults->LDAP.lmsgOutput);
  333. pResults->LDAP.lmsgOutput.Flink = NULL;
  334. }
  335. BOOL
  336. TestLdapOnDc(
  337. IN PTESTED_DC TestedDc,NETDIAG_PARAMS* pParams, NETDIAG_RESULT* pResults
  338. )
  339. /*++
  340. Routine Description:
  341. Ensure we can use LDAP focused at the specified DC
  342. Arguments:
  343. TestedDc - Description of DC to test
  344. Return Value:
  345. TRUE: Test suceeded.
  346. FALSE: Test failed
  347. --*/
  348. {
  349. NET_API_STATUS NetStatus;
  350. NTSTATUS Status;
  351. BOOL RetVal = TRUE;
  352. int LdapMessageId;
  353. PLDAPMessage LdapMessage = NULL;
  354. PLDAPMessage CurrentEntry;
  355. int LdapError;
  356. ULONG AuthType;
  357. LPSTR AuthTypeName;
  358. LPWSTR DcIpAddress;
  359. LDAP *LdapHandle = NULL;
  360. //
  361. // Avoid this test if the DC is already known to be down.
  362. //
  363. if ( TestedDc->Flags & DC_IS_DOWN ) {
  364. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  365. IDS_LDAP_DCDOWN, TestedDc->ComputerName);
  366. goto Cleanup;
  367. }
  368. //
  369. // If there is no IP Address,
  370. // get it.
  371. //
  372. if ( !GetIpAddressForDc( TestedDc ) ) {
  373. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  374. IDS_LDAP_NOIPADDR, TestedDc->ComputerName);
  375. RetVal = FALSE;
  376. goto Cleanup;
  377. }
  378. DcIpAddress = TestedDc->DcIpAddress;
  379. //
  380. // Loop trying each type of authentication.
  381. //
  382. for ( AuthType = 0; AuthType < 3; AuthType++ ) {
  383. int AuthMethod;
  384. SEC_WINNT_AUTH_IDENTITY_W NtAuthIdentity;
  385. LPSTR AuthGuru;
  386. //
  387. // Bind as appropropriate
  388. //
  389. RtlZeroMemory( &NtAuthIdentity, sizeof(NtAuthIdentity));
  390. NtAuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
  391. switch ( AuthType ) {
  392. case 0:
  393. AuthTypeName = "un-";
  394. break;
  395. case 1:
  396. AuthTypeName = "NTLM ";
  397. AuthMethod = LDAP_AUTH_NTLM;
  398. AuthGuru = NTLM_GURU;
  399. break;
  400. case 2:
  401. AuthTypeName = "Negotiate ";
  402. AuthMethod = LDAP_AUTH_NEGOTIATE;
  403. AuthGuru = KERBEROS_GURU;
  404. break;
  405. }
  406. //
  407. // Only Members and Domain controllers can use authenticated RPC.
  408. //
  409. if ( AuthType != 0 ) {
  410. if ( pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleMemberWorkstation ||
  411. pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleMemberServer ||
  412. pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RoleBackupDomainController ||
  413. pResults->Global.pPrimaryDomainInfo->MachineRole == DsRole_RolePrimaryDomainController ) {
  414. //
  415. // If we're logged onto a local account,
  416. // we can't test authenticated RPC.
  417. //
  418. if ( pResults->Global.pLogonDomain == NULL ) {
  419. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  420. IDS_LDAP_LOGONASLOCALUSER,
  421. pResults->Global.pLogonDomainName,
  422. pResults->Global.pLogonUser,
  423. AuthTypeName, TestedDc->ComputerName);
  424. goto Cleanup;
  425. }
  426. } else {
  427. goto Cleanup;
  428. }
  429. }
  430. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  431. IDS_LDAP_DOAUTHEN,
  432. AuthTypeName, TestedDc->ComputerName);
  433. //
  434. // Cleanup from a previous iteration.
  435. //
  436. if ( LdapMessage != NULL )
  437. {
  438. ldap_msgfree( LdapMessage );
  439. LdapMessage = NULL;
  440. }
  441. if ( LdapHandle != NULL )
  442. {
  443. ldap_unbind( LdapHandle );
  444. LdapHandle = NULL;
  445. }
  446. //
  447. // Connect to the DC.
  448. //
  449. LdapHandle = ldap_openW( DcIpAddress, LDAP_PORT );
  450. if ( LdapHandle == NULL )
  451. {
  452. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  453. IDS_LDAP_CANNOTOPEN,
  454. TestedDc->ComputerName, DcIpAddress );
  455. goto Cleanup;
  456. }
  457. //
  458. // Bind to the DC.
  459. //
  460. if ( AuthType != 0 ) {
  461. LdapError = ldap_bind_s( LdapHandle, NULL, (char *)&NtAuthIdentity, AuthMethod );
  462. if ( LdapError != LDAP_SUCCESS ) {
  463. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  464. IDS_LDAP_CANNOTBIND,
  465. AuthTypeName, TestedDc->ComputerName, ldap_err2stringA(LdapError) );
  466. //
  467. // Try other authentication methods.
  468. //
  469. RetVal = FALSE;
  470. continue;
  471. }
  472. }
  473. //
  474. // Do a trivial search to isolate LDAP problems from authentication
  475. // problems
  476. //
  477. LdapError = ldap_search_sA(
  478. LdapHandle,
  479. NULL, // DN
  480. LDAP_SCOPE_BASE,
  481. "(objectClass=*)", // filter
  482. NULL,
  483. FALSE,
  484. &LdapMessage );
  485. if ( LdapError != LDAP_SUCCESS ) {
  486. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  487. IDS_LDAP_CANNOTSEARCH,
  488. AuthTypeName, TestedDc->ComputerName, ldap_err2stringA(LdapError) );
  489. goto Cleanup;
  490. }
  491. //
  492. // How many entries were returned.
  493. //
  494. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  495. IDS_LDAP_ENTRIES,
  496. ldap_count_entries( LdapHandle, LdapMessage ) );
  497. //
  498. // Print the entries.
  499. //
  500. CurrentEntry = ldap_first_entry( LdapHandle, LdapMessage );
  501. while ( CurrentEntry != NULL )
  502. {
  503. PVOID Context;
  504. char *AttrName;
  505. //
  506. // Test for error
  507. //
  508. if ( LdapHandle->ld_errno != 0 )
  509. {
  510. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  511. IDS_LDAP_CANNOTFIRSTENTRY,
  512. TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) );
  513. goto Cleanup;
  514. }
  515. //
  516. // Walk through the list of returned attributes.
  517. //
  518. AttrName = ldap_first_attributeA( LdapHandle, CurrentEntry, (PVOID)&Context );
  519. while ( AttrName != NULL )
  520. {
  521. PLDAP_BERVAL *Berval;
  522. //
  523. // Test for error
  524. //
  525. if ( LdapHandle->ld_errno != 0 ) {
  526. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  527. IDS_LDAP_CANNOTFIRSTATTR,
  528. TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) );
  529. goto Cleanup;
  530. }
  531. //
  532. // Grab the attribute and it's value
  533. //
  534. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  535. IDS_LDAP_ATTR, AttrName );
  536. Berval = ldap_get_values_lenA( LdapHandle, CurrentEntry, AttrName );
  537. if ( Berval == NULL )
  538. {
  539. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_Quiet, 0,
  540. IDS_LDAP_CANNOTLEN,
  541. TestedDc->ComputerName, ldap_err2stringA(LdapHandle->ld_errno) );
  542. goto Cleanup;
  543. }
  544. else
  545. {
  546. int i;
  547. for ( i=0; Berval[i] != NULL; i++ ) {
  548. AddIMessageToList(&pResults->LDAP.lmsgOutput, Nd_ReallyVerbose, 0,
  549. IDS_LDAP_VAL,
  550. Berval[i]->bv_len, Berval[i]->bv_val );
  551. }
  552. ldap_value_free_len( Berval );
  553. }
  554. //
  555. // Get the next entry
  556. //
  557. AttrName = ldap_next_attributeA( LdapHandle, CurrentEntry, (PVOID)Context );
  558. }
  559. //
  560. // Get the next entry
  561. //
  562. CurrentEntry = ldap_next_entry( LdapHandle, CurrentEntry );
  563. }
  564. }
  565. Cleanup:
  566. if ( LdapMessage != NULL ) {
  567. ldap_msgfree( LdapMessage );
  568. }
  569. return RetVal;
  570. }