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.

771 lines
21 KiB

  1. /*++
  2. Copyright (c) 2001 Microsoft Corporation
  3. Module Name:
  4. ldaputil.c
  5. Abstract:
  6. Collection of functions needed to perform ldao operations.
  7. These functions are used by ntfrsapi.dll and
  8. any other frs tools.
  9. Author:
  10. Sudarshan Chitre 20-Mar-2001
  11. Environment
  12. User mode winnt
  13. --*/
  14. #include <ntreppch.h>
  15. #pragma hdrstop
  16. #include <perrepsr.h>
  17. #undef DEBSUB
  18. #define DEBSUB "SUP:"
  19. #include <frs.h>
  20. #include <frssup.h>
  21. //### These functions are also defined in ds,c
  22. //
  23. // Ldap client timeout structure. Value is overwritten by the value of LdapSearchTimeoutInMinutes.
  24. //
  25. LDAP_TIMEVAL FrsSupLdapTimeout = { 10 * 60 * 60, 0 }; //Default ldap timeout value. Overridden by registry param Ldap Search Timeout Value In Minutes
  26. #define FRS_LDAP_SEARCH_PAGESIZE 1000
  27. DWORD
  28. FrsSupBindToDC (
  29. IN PWCHAR pszDC,
  30. IN PSEC_WINNT_AUTH_IDENTITY_W pCreds,
  31. OUT PLDAP *ppLDAP
  32. )
  33. /*++
  34. Routine Description:
  35. Sets up an LDAP connection to the specified server
  36. Arguments:
  37. pwszDC - DS DC to bind to
  38. pCreds - Credentials used to bind to the DS.
  39. ppLDAP - The LDAP connection information is returned here
  40. Return Value:
  41. ERROR_SUCCESS - Success
  42. --*/
  43. {
  44. DWORD dwErr = ERROR_SUCCESS;
  45. ULONG ulOptions;
  46. //
  47. // if ldap_open is called with a server name the api will call DsGetDcName
  48. // passing the server name as the domainname parm...bad, because
  49. // DsGetDcName will make a load of DNS queries based on the server name,
  50. // it is designed to construct these queries from a domain name...so all
  51. // these queries will be bogus, meaning they will waste network bandwidth,
  52. // time to fail, and worst case cause expensive on demand links to come up
  53. // as referrals/forwarders are contacted to attempt to resolve the bogus
  54. // names. By setting LDAP_OPT_AREC_EXCLUSIVE to on using ldap_set_option
  55. // after the ldap_init but before any other operation using the ldap
  56. // handle from ldap_init, the delayed connection setup will not call
  57. // DsGetDcName, just gethostbyname, or if an IP is passed, the ldap client
  58. // will detect that and use the address directly.
  59. //
  60. // *ppLDAP = ldap_open(pszDC, LDAP_PORT);
  61. *ppLDAP = ldap_init(pszDC, LDAP_PORT);
  62. if(*ppLDAP == NULL)
  63. {
  64. dwErr = ERROR_PATH_NOT_FOUND;
  65. }
  66. else
  67. {
  68. //
  69. // set the options.
  70. //
  71. ulOptions = PtrToUlong(LDAP_OPT_ON);
  72. ldap_set_option(*ppLDAP, LDAP_OPT_AREC_EXCLUSIVE, &ulOptions);
  73. //
  74. // Do a bind...
  75. //
  76. dwErr = ldap_bind_s(*ppLDAP,
  77. NULL,
  78. (PWCHAR)pCreds,
  79. LDAP_AUTH_NEGOTIATE);
  80. }
  81. return(dwErr);
  82. }
  83. PVOID *
  84. FrsSupFindValues(
  85. IN PLDAP Ldap,
  86. IN PLDAPMessage Entry,
  87. IN PWCHAR DesiredAttr,
  88. IN BOOL DoBerVals
  89. )
  90. /*++
  91. Routine Description:
  92. Return the DS values for one attribute in an entry.
  93. Arguments:
  94. Ldap - An open, bound Ldap port.
  95. Entry - An Ldap entry returned by Ldap_search_s()
  96. DesiredAttr - Return values for this attribute.
  97. DoBerVals - Return the bervals (for binary data, v.s. WCHAR data)
  98. Return Value:
  99. An array of char pointers that represents the values for the attribute.
  100. The caller must free the array with LDAP_FREE_VALUES().
  101. NULL if unsuccessful.
  102. --*/
  103. {
  104. #undef DEBSUB
  105. #define DEBSUB "FrsSupFindValues:"
  106. PWCHAR Attr; // Retrieved from an Ldap entry
  107. BerElement *Ber; // Needed for scanning attributes
  108. //
  109. // Search the entry for the desired attribute
  110. //
  111. for (Attr = ldap_first_attribute(Ldap, Entry, &Ber);
  112. Attr != NULL;
  113. Attr = ldap_next_attribute(Ldap, Entry, Ber)) {
  114. if (WSTR_EQ(DesiredAttr, Attr)) {
  115. //
  116. // Return the values for DesiredAttr
  117. //
  118. if (DoBerVals) {
  119. return ldap_get_values_len(Ldap, Entry, Attr);
  120. } else {
  121. return ldap_get_values(Ldap, Entry, Attr);
  122. }
  123. }
  124. }
  125. return NULL;
  126. }
  127. PWCHAR
  128. FrsSupWcsDup(
  129. PWCHAR OldStr
  130. )
  131. /*++
  132. Routine Description:
  133. Duplicate a string using our memory allocater
  134. Arguments:
  135. OldArg - string to duplicate
  136. Return Value:
  137. Duplicated string. Free with FRS_SUP_FREE().
  138. --*/
  139. {
  140. #undef DEBSUB
  141. #define DEBSUB "FrsSupWcsDup:"
  142. PWCHAR NewStr;
  143. //
  144. // E.g., when duplicating NodePartner when none exists
  145. //
  146. if (OldStr == NULL) {
  147. return NULL;
  148. }
  149. NewStr = malloc((wcslen(OldStr) + 1) * sizeof(WCHAR));
  150. if (NewStr != NULL) {
  151. wcscpy(NewStr, OldStr);
  152. }
  153. return NewStr;
  154. }
  155. PWCHAR
  156. FrsSupFindValue(
  157. IN PLDAP Ldap,
  158. IN PLDAPMessage Entry,
  159. IN PWCHAR DesiredAttr
  160. )
  161. /*++
  162. Routine Description:
  163. Return a copy of the first DS value for one attribute in an entry.
  164. Arguments:
  165. ldap - An open, bound ldap port.
  166. Entry - An ldap entry returned by ldap_search_s()
  167. DesiredAttr - Return values for this attribute.
  168. Return Value:
  169. A zero-terminated string or NULL if the attribute or its value
  170. doesn't exist. The string is freed with FREE_NO_HEADER().
  171. --*/
  172. {
  173. #undef DEBSUB
  174. #define DEBSUB "FrsSupFindValue:"
  175. PWCHAR Val;
  176. PWCHAR *Values;
  177. // Get ldap's array of values
  178. Values = (PWCHAR *)FrsSupFindValues(Ldap, Entry, DesiredAttr, FALSE);
  179. // Copy the first value (if any)
  180. Val = (Values) ? FrsSupWcsDup(Values[0]) : NULL;
  181. // Free ldap's array of values
  182. LDAP_FREE_VALUES(Values);
  183. return Val;
  184. }
  185. BOOL
  186. FrsSupLdapSearch(
  187. IN PLDAP Ldap,
  188. IN PWCHAR Base,
  189. IN ULONG Scope,
  190. IN PWCHAR Filter,
  191. IN PWCHAR Attrs[],
  192. IN ULONG AttrsOnly,
  193. IN LDAPMessage **Msg
  194. )
  195. /*++
  196. Routine Description:
  197. Issue the ldap ldap_search_s call, check for errors, and check for
  198. a shutdown in progress.
  199. Arguments:
  200. ldap Session handle to Ldap server.
  201. Base The distinguished name of the entry at which to start the search
  202. Scope
  203. LDAP_SCOPE_BASE Search the base entry only.
  204. LDAP_SCOPE_ONELEVEL Search the base entry and all entries in the first
  205. level below the base.
  206. LDAP_SCOPE_SUBTREE Search the base entry and all entries in the tree
  207. below the base.
  208. Filter The search filter.
  209. Attrs A null-terminated array of strings indicating the attributes
  210. to return for each matching entry. Pass NULL to retrieve all
  211. available attributes.
  212. AttrsOnly A boolean value that should be zero if both attribute types
  213. and values are to be returned, nonzero if only types are wanted.
  214. mSG Contains the results of the search upon completion of the call.
  215. The ldap array of values or NULL if the Base, DesiredAttr, or its
  216. values does not exist.
  217. The ldap array is freed with LDAP_FREE_VALUES().
  218. Return Value:
  219. TRUE if not shutting down.
  220. --*/
  221. {
  222. #undef DEBSUB
  223. #define DEBSUB "FrsSupLdapSearch:"
  224. DWORD LStatus;
  225. *Msg = NULL;
  226. //
  227. // Issue the ldap search
  228. //
  229. LStatus = ldap_search_ext_s(Ldap,
  230. Base,
  231. Scope,
  232. Filter,
  233. Attrs,
  234. AttrsOnly,
  235. NULL,
  236. NULL,
  237. &FrsSupLdapTimeout,
  238. 0,
  239. Msg);
  240. //
  241. // Check for errors
  242. //
  243. if (LStatus != LDAP_SUCCESS) {
  244. //
  245. // Increment the DS Searches in Error counter
  246. //
  247. LDAP_FREE_MSG(*Msg);
  248. return FALSE;
  249. }
  250. return TRUE;
  251. }
  252. PWCHAR *
  253. FrsSupGetValues(
  254. IN PLDAP Ldap,
  255. IN PWCHAR Base,
  256. IN PWCHAR DesiredAttr
  257. )
  258. /*++
  259. Routine Description:
  260. Return all of the DS values for one attribute in an object.
  261. Arguments:
  262. ldap - An open, bound ldap port.
  263. Base - The "pathname" of a DS object.
  264. DesiredAttr - Return values for this attribute.
  265. Return Value:
  266. The ldap array of values or NULL if the Base, DesiredAttr, or its values
  267. does not exist. The ldap array is freed with LDAP_FREE_VALUES().
  268. --*/
  269. {
  270. #undef DEBSUB
  271. #define DEBSUB "FrsSupGetValues:"
  272. PLDAPMessage Msg = NULL; // Opaque stuff from ldap subsystem
  273. PWCHAR *Values; // Array of values for desired attribute
  274. //
  275. // Search Base for all of this attribute + values (objectCategory=*)
  276. //
  277. if (!FrsSupLdapSearch(Ldap, Base, LDAP_SCOPE_BASE, CATEGORY_ANY,
  278. NULL, 0, &Msg)) {
  279. return NULL;
  280. }
  281. //
  282. // Return the values for the desired attribute
  283. //
  284. Values = (PWCHAR *)FrsSupFindValues(Ldap,
  285. ldap_first_entry(Ldap, Msg),
  286. DesiredAttr,
  287. FALSE);
  288. LDAP_FREE_MSG(Msg);
  289. return Values;
  290. }
  291. PWCHAR
  292. FrsSupExtendDn(
  293. IN PWCHAR Dn,
  294. IN PWCHAR Cn
  295. )
  296. /*++
  297. Routine Description:
  298. Extend an existing DN with a new CN= component.
  299. Arguments:
  300. Dn - distinguished name
  301. Cn - common name
  302. Return Value:
  303. CN=Cn,Dn
  304. --*/
  305. {
  306. #undef DEBSUB
  307. #define DEBSUB "FrsDsExtendDn:"
  308. ULONG Len;
  309. PWCHAR NewDn;
  310. if ((Dn == NULL) || (Cn == NULL)) {
  311. return NULL;
  312. }
  313. Len = wcslen(L"CN=,") + wcslen(Dn) + wcslen(Cn) + 1;
  314. NewDn = (PWCHAR)malloc(Len * sizeof(WCHAR));
  315. if (NewDn != NULL) {
  316. wcscpy(NewDn, L"CN=");
  317. wcscat(NewDn, Cn);
  318. wcscat(NewDn, L",");
  319. wcscat(NewDn, Dn);
  320. }
  321. return NewDn;
  322. }
  323. PWCHAR
  324. FrsSupGetRootDn(
  325. PLDAP Ldap,
  326. PWCHAR NamingContext
  327. )
  328. /*++
  329. Routine Description:
  330. Arguments:
  331. Return Value:
  332. --*/
  333. {
  334. PWCHAR Root; // DS pathname of configuration container
  335. PWCHAR *Values; // values from the attribute "namingContexts"
  336. DWORD NumVals; // number of values
  337. //
  338. // Return all of the values for the attribute namingContexts
  339. //
  340. Values = FrsSupGetValues(Ldap, CN_ROOT, ATTR_NAMING_CONTEXTS);
  341. if (Values == NULL)
  342. return NULL;
  343. //
  344. // Find the naming context that begins with CN=Configuration
  345. //
  346. NumVals = ldap_count_values(Values);
  347. while (NumVals--) {
  348. Root = wcsstr(Values[NumVals], NamingContext);
  349. if (Root != NULL && Root == Values[NumVals]) {
  350. Root = FrsSupWcsDup(Root);
  351. ldap_value_free(Values);
  352. return Root;
  353. }
  354. }
  355. ldap_value_free(Values);
  356. return NULL;
  357. }
  358. BOOL
  359. FrsSupLdapSearchInit(
  360. PLDAP ldap,
  361. PWCHAR Base,
  362. ULONG Scope,
  363. PWCHAR Filter,
  364. PWCHAR Attrs[],
  365. ULONG AttrsOnly,
  366. PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
  367. )
  368. /*++
  369. Routine Description:
  370. Issue the ldap_create_page_control and ldap_search_ext_s calls,
  371. FrsSupLdapSearchInit(), and FrsSupLdapSearchNext() APIs are used to
  372. make ldap queries and retrieve the results in paged form.
  373. Arguments:
  374. ldap Session handle to Ldap server.
  375. Base The distinguished name of the entry at which to start the search.
  376. A copy of base is kept in the context.
  377. Scope
  378. LDAP_SCOPE_BASE Search the base entry only.
  379. LDAP_SCOPE_ONELEVEL Search the base entry and all entries in the first
  380. level below the base.
  381. LDAP_SCOPE_SUBTREE Search the base entry and all entries in the tree
  382. below the base.
  383. Filter The search filter. A copy of filter is kept in the context.
  384. Attrs A null-terminated array of strings indicating the attributes
  385. to return for each matching entry. Pass NULL to retrieve all
  386. available attributes.
  387. AttrsOnly A boolean value that should be zero if both attribute types
  388. and values are to be returned, nonzero if only types are wanted.
  389. FrsSearchContext
  390. An opaques structure that links the FrsSupLdapSearchInit() and
  391. FrsSupLdapSearchNext() calls together. The structure contains
  392. the information required to retrieve query results across pages.
  393. Return Value:
  394. BOOL result.
  395. --*/
  396. {
  397. #undef DEBSUB
  398. #define DEBSUB "FrsSupLdapSearchInit:"
  399. DWORD LStatus = LDAP_SUCCESS;
  400. PLDAPControl ServerControls[2];
  401. PLDAPControl ServerControl = NULL;
  402. UINT i;
  403. LDAP_BERVAL cookie1 = { 0, NULL };
  404. FrsSearchContext->LdapMsg = NULL;
  405. FrsSearchContext->CurrentLdapMsg = NULL;
  406. FrsSearchContext->EntriesInPage = 0;
  407. FrsSearchContext->CurrentEntry = 0;
  408. FrsSearchContext->BaseDn = FrsSupWcsDup(Base);
  409. FrsSearchContext->Filter = FrsSupWcsDup(Filter);
  410. FrsSearchContext->Scope = Scope;
  411. FrsSearchContext->Attrs = Attrs;
  412. LStatus = ldap_create_page_control(ldap,
  413. FRS_LDAP_SEARCH_PAGESIZE,
  414. &cookie1,
  415. FALSE, // is critical
  416. &ServerControl
  417. );
  418. ServerControls[0] = ServerControl;
  419. ServerControls[1] = NULL;
  420. if (LStatus != LDAP_SUCCESS) {
  421. FRS_SUP_FREE(FrsSearchContext->BaseDn);
  422. FRS_SUP_FREE(FrsSearchContext->Filter);
  423. return FALSE;
  424. }
  425. LStatus = ldap_search_ext_s(ldap,
  426. FrsSearchContext->BaseDn,
  427. FrsSearchContext->Scope,
  428. FrsSearchContext->Filter,
  429. FrsSearchContext->Attrs,
  430. FALSE,
  431. ServerControls,
  432. NULL,
  433. &FrsSupLdapTimeout,
  434. 0,
  435. &FrsSearchContext->LdapMsg);
  436. ldap_control_free(ServerControl);
  437. if (LStatus == LDAP_SUCCESS) {
  438. FrsSearchContext->EntriesInPage = ldap_count_entries(ldap, FrsSearchContext->LdapMsg);
  439. FrsSearchContext->CurrentEntry = 0;
  440. }
  441. if (LStatus != LDAP_SUCCESS) {
  442. FRS_SUP_FREE(FrsSearchContext->BaseDn);
  443. FRS_SUP_FREE(FrsSearchContext->Filter);
  444. return FALSE;
  445. }
  446. return TRUE;
  447. }
  448. PLDAPMessage
  449. FrsSupLdapSearchGetNextEntry(
  450. PLDAP ldap,
  451. PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
  452. )
  453. /*++
  454. Routine Description:
  455. Get the next entry form the current page of the results
  456. returned. This call is only made if there is a entry
  457. in the current page.
  458. Arguments:
  459. ldap Session handle to Ldap server.
  460. FrsSearchContext
  461. An opaques structure that links the FrsSupLdapSearchInit() and
  462. FrsSupLdapSearchNext() calls together. The structure contains
  463. the information required to retrieve query results across pages.
  464. Return Value:
  465. The first or the next entry from the current page.
  466. --*/
  467. {
  468. #undef DEBSUB
  469. #define DEBSUB "FrsSupLdapSearchGetNextEntry:"
  470. FrsSearchContext->CurrentEntry += 1;
  471. if ( FrsSearchContext->CurrentEntry == 1 ) {
  472. FrsSearchContext->CurrentLdapMsg = ldap_first_entry(ldap ,FrsSearchContext->LdapMsg);
  473. } else {
  474. FrsSearchContext->CurrentLdapMsg = ldap_next_entry(ldap ,FrsSearchContext->CurrentLdapMsg);
  475. }
  476. return FrsSearchContext->CurrentLdapMsg;
  477. }
  478. DWORD
  479. FrsSupLdapSearchGetNextPage(
  480. PLDAP ldap,
  481. PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
  482. )
  483. /*++
  484. Routine Description:
  485. Get the next page from the results returned by ldap_search_ext_s..
  486. Arguments:
  487. ldap Session handle to Ldap server.
  488. FrsSearchContext
  489. An opaques structure that links the FrsSupLdapSearchInit() and
  490. FrsSupLdapSearchNext() calls together. The structure contains
  491. the information required to retrieve query results across pages.
  492. Return Value:
  493. WINSTATUS
  494. --*/
  495. {
  496. #undef DEBSUB
  497. #define DEBSUB "FrsSupLdapSearchGetNextPage:"
  498. DWORD LStatus = LDAP_SUCCESS;
  499. LDAP_BERVAL * CurrCookie = NULL;
  500. PLDAPControl * CurrControls = NULL;
  501. ULONG retcode = 0;
  502. ULONG TotalEntries = 0;
  503. PLDAPControl ServerControls[2];
  504. PLDAPControl ServerControl= NULL;
  505. // Get the server control from the message, and make a new control with the cookie from the server
  506. LStatus = ldap_parse_result(ldap, FrsSearchContext->LdapMsg, &retcode,NULL,NULL,NULL,&CurrControls,FALSE);
  507. LDAP_FREE_MSG(FrsSearchContext->LdapMsg);
  508. if (LStatus != LDAP_SUCCESS) {
  509. return LdapMapErrorToWin32(LStatus);
  510. }
  511. LStatus = ldap_parse_page_control(ldap, CurrControls, &TotalEntries, &CurrCookie);
  512. if (LStatus != LDAP_SUCCESS) {
  513. return LdapMapErrorToWin32(LStatus);
  514. }
  515. if ( CurrCookie->bv_len == 0 && CurrCookie->bv_val == 0 ) {
  516. LStatus = LDAP_CONTROL_NOT_FOUND;
  517. ldap_controls_free(CurrControls);
  518. ber_bvfree(CurrCookie);
  519. return LdapMapErrorToWin32(LStatus);
  520. }
  521. LStatus = ldap_create_page_control(ldap,
  522. FRS_LDAP_SEARCH_PAGESIZE,
  523. CurrCookie,
  524. FALSE,
  525. &ServerControl);
  526. ServerControls[0] = ServerControl;
  527. ServerControls[1] = NULL;
  528. ldap_controls_free(CurrControls);
  529. CurrControls = NULL;
  530. ber_bvfree(CurrCookie);
  531. CurrCookie = NULL;
  532. if (LStatus != LDAP_SUCCESS) {
  533. return LdapMapErrorToWin32(LStatus);
  534. }
  535. // continue the search with the new cookie
  536. LStatus = ldap_search_ext_s(ldap,
  537. FrsSearchContext->BaseDn,
  538. FrsSearchContext->Scope,
  539. FrsSearchContext->Filter,
  540. FrsSearchContext->Attrs,
  541. FALSE,
  542. ServerControls,
  543. NULL,
  544. &FrsSupLdapTimeout,
  545. 0,
  546. &FrsSearchContext->LdapMsg);
  547. ldap_control_free(ServerControl);
  548. if (LStatus == LDAP_SUCCESS) {
  549. FrsSearchContext->EntriesInPage = ldap_count_entries(ldap, FrsSearchContext->LdapMsg);
  550. FrsSearchContext->CurrentEntry = 0;
  551. }
  552. return LdapMapErrorToWin32(LStatus);
  553. }
  554. PLDAPMessage
  555. FrsSupLdapSearchNext(
  556. PLDAP ldap,
  557. PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
  558. )
  559. /*++
  560. Routine Description:
  561. Get the next entry form the current page of the results
  562. returned or from the next page if we are at the end of the.
  563. current page.
  564. Arguments:
  565. ldap Session handle to Ldap server.
  566. FrsSearchContext
  567. An opaques structure that links the FrsSupLdapSearchInit() and
  568. FrsSupLdapSearchNext() calls together. The structure contains
  569. the information required to retrieve query results across pages.
  570. Return Value:
  571. The next entry on this page or the first entry from the next page.
  572. NULL if there are no more entries to return.
  573. --*/
  574. {
  575. #undef DEBSUB
  576. #define DEBSUB "FrsSupLdapSearchNext:"
  577. DWORD WStatus = ERROR_SUCCESS;
  578. PLDAPMessage NextEntry = NULL;
  579. if (FrsSearchContext->EntriesInPage > FrsSearchContext->CurrentEntry )
  580. {
  581. // return the next entry from the current page
  582. return FrsSupLdapSearchGetNextEntry(ldap, FrsSearchContext);
  583. }
  584. else
  585. {
  586. // see if there are more pages of results to get
  587. WStatus = FrsSupLdapSearchGetNextPage(ldap, FrsSearchContext);
  588. if (WStatus == ERROR_SUCCESS)
  589. {
  590. return FrsSupLdapSearchGetNextEntry(ldap, FrsSearchContext);
  591. }
  592. }
  593. return NextEntry;
  594. }
  595. VOID
  596. FrsSupLdapSearchClose(
  597. PFRS_LDAP_SEARCH_CONTEXT FrsSearchContext
  598. )
  599. /*++
  600. Routine Description:
  601. The search is complete. Free the elemetns of the context and reset
  602. them so the same context can be used for another search.
  603. Arguments:
  604. FrsSearchContext
  605. An opaques structure that links the FrsSupLdapSearchInit() and
  606. FrsSupLdapSearchNext() calls together. The structure contains
  607. the information required to retrieve query results across pages.
  608. Return Value:
  609. NONE
  610. --*/
  611. {
  612. #undef DEBSUB
  613. #define DEBSUB "FrsSupLdapSearchClose:"
  614. FrsSearchContext->EntriesInPage = 0;
  615. FrsSearchContext->CurrentEntry = 0;
  616. FRS_SUP_FREE(FrsSearchContext->BaseDn);
  617. FRS_SUP_FREE(FrsSearchContext->Filter);
  618. LDAP_FREE_MSG(FrsSearchContext->LdapMsg);
  619. }