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.

679 lines
18 KiB

  1. // This class is to help setup retrieve the old-style LSA keys and convert them
  2. // into the new MetaData keys.
  3. // created by BoydM 4/2/97
  4. #include "stdafx.h"
  5. #include "LSAKeys.h"
  6. #ifndef _CHICAGO_
  7. // it is assumed that #include "ntlsa.h" is included in stdafx.h
  8. #define KEYSET_LIST L"W3_KEY_LIST"
  9. #define KEYSET_PUB_KEY L"W3_PUBLIC_KEY_%s"
  10. #define KEYSET_PRIV_KEY L"W3_PRIVATE_KEY_%s"
  11. #define KEYSET_PASSWORD L"W3_KEY_PASSWORD_%s"
  12. #define KEYSET_DEFAULT L"Default"
  13. #define KEY_NAME_BASE "W3_KEYMAN_KEY_"
  14. #define KEY_LINKS_SECRET_W L"W3_KEYMAN_LINKS"
  15. #define KEYMAN_LINK_DEFAULT "DEFAULT"
  16. #define KEY_VERSION 0x102 // version we are converting from
  17. #define MDNAME_INCOMPLETE "incomplete"
  18. #define MDNAME_DISABLED "disabled"
  19. #define MDNAME_DEFAULT "default"
  20. #define MDNAME_PORT ":443" // use the default SSL port
  21. //----------------------------------------------------------------------
  22. // construction
  23. CLSAKeys::CLSAKeys():
  24. m_cbPublic(0),
  25. m_pPublic(NULL),
  26. m_cbPrivate(0),
  27. m_pPrivate(NULL),
  28. m_cbPassword(0),
  29. m_pPassword(NULL),
  30. m_cbRequest(0),
  31. m_pRequest(NULL),
  32. m_hPolicy(NULL)
  33. {
  34. }
  35. //----------------------------------------------------------------------
  36. CLSAKeys::~CLSAKeys()
  37. {
  38. DWORD err;
  39. // clear out the last loaded key
  40. UnloadKey();
  41. // if it is opehn, close the LSA policy
  42. if ( m_hPolicy )
  43. FCloseLSAPolicy( m_hPolicy, &err );
  44. };
  45. //----------------------------------------------------------------------
  46. // clean up the currently loaded key
  47. void CLSAKeys::UnloadKey()
  48. {
  49. // unload the public key
  50. if ( m_cbPublic && m_pPublic )
  51. {
  52. GlobalFree( m_pPublic );
  53. m_cbPublic = 0;
  54. m_pPublic = NULL;
  55. }
  56. // unload the private key
  57. if ( m_cbPrivate && m_pPrivate )
  58. {
  59. GlobalFree( m_pPrivate );
  60. m_cbPrivate = 0;
  61. m_pPrivate = NULL;
  62. }
  63. // unload the password
  64. if ( m_cbPassword && m_pPassword )
  65. {
  66. GlobalFree( m_pPassword );
  67. m_cbPassword = 0;
  68. m_pPassword = NULL;
  69. }
  70. // unload the key request
  71. if ( m_cbRequest && m_pRequest )
  72. {
  73. GlobalFree( m_pRequest );
  74. m_cbRequest = 0;
  75. m_pRequest = NULL;
  76. }
  77. // empty the strings too
  78. m_szFriendlyName[0] = 0;
  79. m_szMetaName[0] = 0;
  80. }
  81. //----------------------------------------------------------------------
  82. // DeleteAllLSAKeys deletes ALL remenents of the LSA keys in the Metabase.
  83. // (not including, of course anything written out there in the future as part
  84. // of some backup scheme when uninstalling). Call this only AFTER ALL the keys
  85. // have been converted to the metabase. They will no longer be there after
  86. // this routine is used.
  87. // NOTE: this also blows away any really-old KeySet keys because they look
  88. // like the KeyMan keys. And we have to kill both the keyset keys and the
  89. // generic storage used by the server.
  90. DWORD CLSAKeys::DeleteAllLSAKeys()
  91. {
  92. DWORD err;
  93. // first, delete the KeyManager type keys.
  94. err = DeleteKMKeys();
  95. if ( err != KEYLSA_SUCCESS )
  96. return err;
  97. // second, delete the keyset style keys. - this also removes the ones
  98. // that the server uses and any keyset keys.
  99. return DeleteServerKeys();
  100. }
  101. //----------------------------------------------------------------------
  102. DWORD CLSAKeys::DeleteKMKeys()
  103. {
  104. PCHAR pName = (PCHAR)GlobalAlloc( GPTR, MAX_PATH+1 );
  105. PWCHAR pWName = (PWCHAR)GlobalAlloc( GPTR, (MAX_PATH+1) * sizeof(WCHAR) );
  106. PLSA_UNICODE_STRING pLSAData;
  107. DWORD err;
  108. if ( !pName || !pWName )
  109. return ERROR_NOT_ENOUGH_MEMORY;
  110. // reset the index so we get the first key
  111. m_iKey = 0;
  112. // loop through the keys, deleting each in turn
  113. while( TRUE )
  114. {
  115. // increment the index
  116. m_iKey++;
  117. // build the key secret name
  118. sprintf( pName, "%s%d", KEY_NAME_BASE, m_iKey );
  119. // unicodize the name
  120. MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, pName, -1, pWName, MAX_PATH+1 );
  121. // get the secret
  122. pLSAData = FRetrieveLSASecret( m_hPolicy, pWName, &err );
  123. // if we don't get the secret, exit
  124. if ( !pLSAData )
  125. {
  126. break;
  127. }
  128. // The secret is there. Clean up first
  129. DisposeLSAData( pLSAData );
  130. // now delete the secret
  131. FStoreLSASecret( m_hPolicy, pWName, NULL, 0, &err );
  132. };
  133. return KEYLSA_SUCCESS;
  134. }
  135. //----------------------------------------------------------------------
  136. DWORD CLSAKeys::DeleteServerKeys()
  137. {
  138. DWORD err;
  139. PLSA_UNICODE_STRING pLSAData;
  140. // get the secret list of keys
  141. pLSAData = FRetrieveLSASecret( m_hPolicy, KEYSET_LIST, &err );
  142. // if we get lucky, there won't be any keys to get rid of
  143. if ( !pLSAData )
  144. return KEYLSA_SUCCESS;
  145. // allocate the name buffer
  146. PWCHAR pWName = (PWCHAR)GlobalAlloc( GPTR, (MAX_PATH+1) * sizeof(WCHAR) );
  147. ASSERT( pWName );
  148. if ( !pWName )
  149. {
  150. return 0xFFFFFFFF;
  151. }
  152. // No such luck. Now we have to walk the list and delete all those secrets
  153. WCHAR* pszAddress = (WCHAR*)(pLSAData->Buffer);
  154. WCHAR* pchKeys;
  155. // loop the items in the list, deleting the associated items
  156. while( ( pchKeys = wcschr(pszAddress, L',') ) != NULL )
  157. {
  158. // ignore empty segments
  159. if ( *pszAddress != L',' )
  160. {
  161. *pchKeys = L'\0';
  162. // Nuke the secrets, one at a time
  163. swprintf( pWName, KEYSET_PUB_KEY, pszAddress );
  164. FStoreLSASecret( m_hPolicy, pWName, NULL, 0, &err );
  165. swprintf( pWName, KEYSET_PRIV_KEY, pszAddress );
  166. FStoreLSASecret( m_hPolicy, pWName, NULL, 0, &err );
  167. swprintf( pWName, KEYSET_PASSWORD, pszAddress );
  168. FStoreLSASecret( m_hPolicy, pWName, NULL, 0, &err );
  169. }
  170. // increment the pointers
  171. pchKeys++;
  172. pszAddress = pchKeys;
  173. }
  174. // delete the list key itself
  175. FStoreLSASecret( m_hPolicy, KEYSET_LIST, NULL, 0, &err );
  176. // free the buffer for the names
  177. GlobalFree( (HANDLE)pWName );
  178. // free the info we originally retrieved from the secret
  179. if ( pLSAData )
  180. DisposeLSAData( pLSAData );
  181. // return success
  182. return KEYLSA_SUCCESS;
  183. }
  184. //----------------------------------------------------------------------
  185. // loading the keys
  186. // LoadFirstKey loads the first key on the specified target machine. Until
  187. // this method is called, the data values in the object are meaningless
  188. // this method works by preparing the list of keys to load. Then it calls
  189. // LoadNextKey to start the process
  190. // Unfortunately, the whole process of saving keys in the LSA registry was a bit
  191. // of a mess because they all had to be on the same level.
  192. DWORD CLSAKeys::LoadFirstKey( PWCHAR pszwTargetMachine )
  193. {
  194. DWORD err;
  195. // open the policy on the target machine being administered
  196. m_hPolicy = HOpenLSAPolicy( pszwTargetMachine, &err );
  197. if ( !m_hPolicy ) return KEYLSA_UNABLE_TO_OPEN_POLICY;
  198. // tell it to load the first key. The first key's index is actually 1,
  199. // but LoadNextKey impliess that it is ++LoadNextKey, so start it at 0
  200. m_iKey = 0;
  201. // load that first key and return the response
  202. return LoadNextKey();
  203. }
  204. //----------------------------------------------------------------------
  205. // LoadNextKey loads the next key on the target machine specified in LoadFirstKey
  206. // LoadNextKey automatically cleans up the memory used by the previous key.
  207. DWORD CLSAKeys::LoadNextKey()
  208. {
  209. // the very first thing we do is - get rid of any previously loaded key
  210. UnloadKey();
  211. PCHAR pName = (PCHAR)GlobalAlloc( GPTR, MAX_PATH+1 );
  212. PWCHAR pWName = (PWCHAR)GlobalAlloc( GPTR, (MAX_PATH+1) * sizeof(WCHAR) );
  213. PLSA_UNICODE_STRING pLSAData = NULL;
  214. DWORD err = 0xFFFFFFFF;
  215. PUCHAR pSrc;
  216. WORD cbSrc;
  217. DWORD dword, version, i;
  218. DWORD cbChar;
  219. PUCHAR p;
  220. CHAR szIPAddress[256];
  221. BOOL fDefault;
  222. if ( !pName || !pWName )
  223. return err;
  224. // increment the index so we get the next key
  225. m_iKey++;
  226. // build the key secret name
  227. sprintf( pName, "%s%d", KEY_NAME_BASE, m_iKey );
  228. // unicodize the name
  229. MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, pName, -1, pWName, MAX_PATH+1 );
  230. // get the secret
  231. pLSAData = FRetrieveLSASecret( m_hPolicy, pWName, &err );
  232. // if we don't get the secret, exit with the error
  233. if ( !pLSAData )
  234. {
  235. err = KEYLSA_NO_MORE_KEYS;
  236. goto cleanup;
  237. }
  238. // we have the data from the secret. Now we parse it out into the components we desire
  239. // this probably could have been done cleaner the first time - but now it doesn't matter
  240. // anyway because the MetaBase takes care of storing all the individual pieces of info
  241. // anyway. It should also be way faster too.
  242. // This part of the routine is pretty much lifted out of CW3Key::InitializeFromPointer
  243. // from the w3key.dll. The appropriate sections have been either commented out or changed.
  244. pSrc = (PUCHAR)pLSAData->Buffer;
  245. cbSrc = pLSAData->Length;
  246. cbChar = sizeof(TCHAR);
  247. p = pSrc;
  248. //========================== start from CW3Key::InitializeFromPointer
  249. ASSERT(pSrc && cbSrc);
  250. // get the version of the data - just put it into dword for now
  251. version = *((UNALIGNED DWORD*)p);
  252. // check the version for validity
  253. // if ( version > KEY_VERSION )
  254. // {
  255. // return FALSE;
  256. // }
  257. p += sizeof(DWORD);
  258. // anything below version 0x101 is BAD. Do not accept it
  259. if ( version < 0x101 )
  260. {
  261. err = KEYLSA_INVALID_VERSION;
  262. goto cleanup;
  263. }
  264. // get the bits and the complete flag
  265. // no longer used
  266. p += sizeof(DWORD);
  267. p += sizeof(BOOL);
  268. ASSERT( p < (pSrc + cbSrc) );
  269. // get the reserved dword - (acutally, just skip over it)
  270. p += sizeof(DWORD);
  271. // now the strings......
  272. // for each string, first get the size of the string, then the data from the string
  273. // get the reserved string - (actually, just skip over it)
  274. dword = *((UNALIGNED DWORD*)p);
  275. p += sizeof(DWORD);
  276. p += dword;
  277. // get the name
  278. dword = *((UNALIGNED DWORD*)p);
  279. p += sizeof(DWORD);
  280. strcpy( m_szFriendlyName, (PCHAR)p );
  281. p += dword;
  282. ASSERT( p < (pSrc + cbSrc) );
  283. // get the password
  284. dword = *((UNALIGNED DWORD*)p);
  285. p += sizeof(DWORD);
  286. // if there is no password, don't worry, just skip it
  287. if ( dword )
  288. {
  289. // make a new pointer for it
  290. m_cbPassword = dword;
  291. m_pPassword = (PVOID)GlobalAlloc( GPTR, m_cbPassword );
  292. if ( !m_pPassword )
  293. {
  294. err = 0xFFFFFFFF;
  295. goto cleanup;
  296. }
  297. // put in the private key
  298. CopyMemory( m_pPassword, p, m_cbPassword );
  299. p += dword;
  300. ASSERT( p < (pSrc + cbSrc) );
  301. }
  302. // get the organization
  303. // no longer used - skip the DN info
  304. for ( i = 0; i < 6; i++ )
  305. {
  306. dword = *((UNALIGNED DWORD*)p);
  307. p += sizeof(DWORD);
  308. p += dword;
  309. ASSERT( p < (pSrc + cbSrc) );
  310. }
  311. // get the ip addres it is attached to
  312. dword = *((UNALIGNED DWORD*)p);
  313. p += sizeof(DWORD);
  314. // szIPAddress = p;
  315. strcpy( szIPAddress, (PCHAR)p );
  316. p += dword;
  317. ASSERT( p < (pSrc + cbSrc) );
  318. // get the default flag
  319. fDefault = *((UNALIGNED BOOL*)p);
  320. p += sizeof(BOOL);
  321. // now put get the number of bytes in the private key
  322. m_cbPrivate = *((UNALIGNED DWORD*)p);
  323. p += sizeof(DWORD);
  324. ASSERT( p < (pSrc + cbSrc) );
  325. // make a new pointer for it
  326. m_pPrivate = (PVOID)GlobalAlloc( GPTR, m_cbPrivate );
  327. if ( !m_pPrivate )
  328. {
  329. err = 0xFFFFFFFF;
  330. goto cleanup;
  331. }
  332. // put in the private key
  333. CopyMemory( m_pPrivate, p, m_cbPrivate );
  334. p += m_cbPrivate;
  335. ASSERT( p < (pSrc + cbSrc) );
  336. // now put get the number of bytes in the certificate
  337. m_cbPublic = *((UNALIGNED DWORD*)p);
  338. p += sizeof(DWORD);
  339. ASSERT( p < (pSrc + cbSrc) );
  340. // only make a certificate pointer if m_cbCertificate is greater than zero
  341. m_pPublic = NULL;
  342. if ( m_cbPublic )
  343. {
  344. m_pPublic = (PVOID)GlobalAlloc( GPTR, m_cbPublic );
  345. if ( !m_pPublic )
  346. {
  347. err = 0xFFFFFFFF;
  348. goto cleanup;
  349. }
  350. // put in the private key
  351. CopyMemory( m_pPublic, p, m_cbPublic );
  352. p += m_cbPublic;
  353. if ( version >= KEY_VERSION ) {
  354. ASSERT( p < (pSrc + cbSrc) );
  355. } else {
  356. ASSERT( p == (pSrc + cbSrc) );
  357. }
  358. }
  359. // added near the end
  360. if ( version >= KEY_VERSION )
  361. {
  362. // now put get the number of bytes in the certificte request
  363. m_cbRequest = *((UNALIGNED DWORD*)p);
  364. p += sizeof(DWORD);
  365. ASSERT( p < (pSrc + cbSrc) );
  366. // only make a certificate pointer if m_cbCertificate is greater than zero
  367. m_pRequest = NULL;
  368. if ( m_cbRequest )
  369. {
  370. m_pRequest = (PVOID)GlobalAlloc( GPTR, m_cbRequest );
  371. if ( !m_pRequest )
  372. {
  373. err = 0xFFFFFFFF;
  374. goto cleanup;
  375. }
  376. // put in the private key
  377. CopyMemory( m_pRequest, p, m_cbRequest );
  378. p += m_cbRequest;
  379. ASSERT( p < (pSrc + cbSrc) );
  380. }
  381. }
  382. else
  383. {
  384. m_cbRequest = 0;
  385. m_pRequest = NULL;
  386. }
  387. //========================== end from CW3Key::InitializeFromPointer
  388. // now we figure out the appropriate metabase name for this key
  389. // this isn't too bad. If the targets a specific address, then the title
  390. // is the in the form of {IP}:{PORT}. Since there were no ports in the old
  391. // version, we will assume an appropriate default number. If it is the
  392. // default key, then the name is "default". If it is a disabled key, then
  393. // the name is "disabled". If it is an incomplete key, then the name is
  394. // "incomplete". Of course, it takes a little logic to tell the difference
  395. // between some of these.
  396. // first, see if it is an incomplete key. - test for the public portion
  397. if ( !m_pPublic )
  398. {
  399. // there may be multiple incomplete keys, so make sure they have unique names
  400. // m_szMetaName.Format( _T("%s%d"), MDNAME_INCOMPLETE, m_iKey );
  401. sprintf( m_szMetaName, "%s%d", MDNAME_INCOMPLETE, m_iKey );
  402. }
  403. // now test if it is the default key
  404. else if ( fDefault )
  405. {
  406. // m_szMetaName = MDNAME_DEFAULT;
  407. strcpy( m_szMetaName, MDNAME_DEFAULT );
  408. }
  409. // test for a disabled key
  410. else if ( szIPAddress[0] == 0 )
  411. {
  412. // there may be multiple disabled keys, so make sure they have unique names
  413. // m_szMetaName.Format( _T("%s%d"), MDNAME_DISABLED, m_iKey );
  414. sprintf( m_szMetaName, "%s%d", MDNAME_DISABLED, m_iKey );
  415. }
  416. else
  417. {
  418. // it is a regular old IP targeted key
  419. // m_szMetaName = szIPAddress;
  420. // add on the default port specification
  421. // m_szMetaName += MDNAME_PORT;
  422. // sprintf( m_szMetaName, "%s%s", szIPAddress, MDNAME_PORT );
  423. strcpy(m_szMetaName, szIPAddress);
  424. }
  425. // free the buffers
  426. cleanup:
  427. GlobalFree( (HANDLE)pName );
  428. GlobalFree( (HANDLE)pWName );
  429. if ( pLSAData )
  430. DisposeLSAData( pLSAData );
  431. return err;
  432. }
  433. //============================================= LSA Utility routines
  434. //-------------------------------------------------------------
  435. // pass in a NULL pszwServer name to open the local machine
  436. HANDLE CLSAKeys::HOpenLSAPolicy( WCHAR *pszwServer, DWORD *pErr )
  437. {
  438. NTSTATUS ntStatus;
  439. LSA_OBJECT_ATTRIBUTES objectAttributs;
  440. LSA_HANDLE hPolicy;
  441. LSA_UNICODE_STRING unicodeServer;
  442. if ( ( wcslen(pszwServer) * sizeof(WCHAR) ) >= MAXUSHORT )
  443. {
  444. return NULL;
  445. }
  446. // prepare the object attributes
  447. InitializeObjectAttributes( &objectAttributs, NULL, 0L, NULL, NULL );
  448. // prepare the lsa_unicode name of the server
  449. if ( pszwServer )
  450. {
  451. unicodeServer.Buffer = pszwServer;
  452. unicodeServer.Length = (USHORT) ( wcslen(pszwServer) * sizeof(WCHAR) );
  453. unicodeServer.MaximumLength = unicodeServer.Length + sizeof(WCHAR);
  454. }
  455. // attempt to open the policy
  456. ntStatus = LsaOpenPolicy( pszwServer ? &unicodeServer : NULL,
  457. &objectAttributs, POLICY_ALL_ACCESS, &hPolicy );
  458. // check for an error
  459. if ( !NT_SUCCESS(ntStatus) )
  460. {
  461. *pErr = LsaNtStatusToWinError( ntStatus );
  462. return NULL;
  463. }
  464. // success, so return the policy handle as a regular handle
  465. *pErr = 0;
  466. return hPolicy;
  467. }
  468. //-------------------------------------------------------------
  469. BOOL CLSAKeys::FCloseLSAPolicy( HANDLE hPolicy, DWORD *pErr )
  470. {
  471. NTSTATUS ntStatus;
  472. // close the policy
  473. ntStatus = LsaClose( hPolicy );
  474. // check for an error
  475. if ( !NT_SUCCESS(ntStatus) )
  476. {
  477. *pErr = LsaNtStatusToWinError( ntStatus );
  478. return FALSE;
  479. }
  480. // success, so return the policy handle as a regular handle
  481. *pErr = 0;
  482. return TRUE;
  483. }
  484. //-------------------------------------------------------------
  485. // passing NULL in for pvData deletes the secret
  486. BOOL CLSAKeys::FStoreLSASecret( HANDLE hPolicy, WCHAR* pszwSecretName, void* pvData, WORD cbData, DWORD *pErr )
  487. {
  488. LSA_UNICODE_STRING unicodeSecretName;
  489. LSA_UNICODE_STRING unicodeData;
  490. NTSTATUS ntStatus;
  491. // make sure we have a policy and a secret name
  492. if ( !hPolicy ||
  493. !pszwSecretName ||
  494. ( ( wcslen(pszwSecretName) * sizeof(WCHAR) ) >= MAXUSHORT )
  495. )
  496. {
  497. *pErr = 1;
  498. return FALSE;
  499. }
  500. // prepare the lsa_unicode name of the server
  501. unicodeSecretName.Buffer = pszwSecretName;
  502. unicodeSecretName.Length = (USHORT) wcslen(pszwSecretName) * sizeof(WCHAR);
  503. unicodeSecretName.MaximumLength = unicodeSecretName.Length + sizeof(WCHAR);
  504. // prepare the unicode data record
  505. if ( pvData )
  506. {
  507. unicodeData.Buffer = (WCHAR*)pvData;
  508. unicodeData.Length = cbData;
  509. unicodeData.MaximumLength = cbData;
  510. }
  511. // it is now time to store the secret
  512. ntStatus = LsaStorePrivateData( hPolicy, &unicodeSecretName, pvData ? &unicodeData : NULL );
  513. // check for an error
  514. if ( !NT_SUCCESS(ntStatus) )
  515. {
  516. *pErr = LsaNtStatusToWinError( ntStatus );
  517. return FALSE;
  518. }
  519. // success, so return the policy handle as a regular handle
  520. *pErr = 0;
  521. return TRUE;
  522. }
  523. //-------------------------------------------------------------
  524. // passing NULL in for pvData deletes the secret
  525. PLSA_UNICODE_STRING CLSAKeys::FRetrieveLSASecret( HANDLE hPolicy, WCHAR* pszwSecretName, DWORD *pErr )
  526. {
  527. LSA_UNICODE_STRING unicodeSecretName;
  528. LSA_UNICODE_STRING* pUnicodeData = NULL;
  529. NTSTATUS ntStatus;
  530. // make sure we have a policy and a secret name
  531. if ( !hPolicy ||
  532. !pszwSecretName ||
  533. ( ( wcslen( pszwSecretName ) * sizeof(WCHAR) ) >= MAXUSHORT )
  534. )
  535. {
  536. *pErr = 1;
  537. return FALSE;
  538. }
  539. // prepare the lsa_unicode name of the server
  540. unicodeSecretName.Buffer = pszwSecretName;
  541. unicodeSecretName.Length = (USHORT) wcslen(pszwSecretName) * sizeof(WCHAR);
  542. unicodeSecretName.MaximumLength = unicodeSecretName.Length + sizeof(WCHAR);
  543. // it is now time to store the secret
  544. ntStatus = LsaRetrievePrivateData( hPolicy, &unicodeSecretName, &pUnicodeData );
  545. // check for an error
  546. if ( !NT_SUCCESS(ntStatus) )
  547. {
  548. *pErr = LsaNtStatusToWinError( ntStatus );
  549. return NULL;
  550. }
  551. // success, so return the policy handle as a regular handle
  552. *pErr = 0;
  553. return pUnicodeData;
  554. }
  555. //-------------------------------------------------------------
  556. void CLSAKeys::DisposeLSAData( PVOID pData )
  557. {
  558. PLSA_UNICODE_STRING pDataLSA = (PLSA_UNICODE_STRING)pData;
  559. if ( !pDataLSA || !pDataLSA->Buffer ) return;
  560. GlobalFree(pDataLSA);
  561. }
  562. #endif //_CHICAGO_