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.

928 lines
19 KiB

  1. /*++
  2. Copyright (c) 1999-2000 Microsoft Corporation
  3. Module Name:
  4. RemoteDesktopUtils
  5. Abstract:
  6. Misc. RD Utils
  7. Author:
  8. Tad Brockway 02/00
  9. Revision History:
  10. --*/
  11. #ifdef TRC_FILE
  12. #undef TRC_FILE
  13. #endif
  14. #define TRC_FILE "_rdsutl"
  15. #include <winsock2.h>
  16. #include <RemoteDesktop.h>
  17. #include "RemoteDesktopUtils.h"
  18. #include "base64.h"
  19. //#include "RemoteDesktopDBG.h"
  20. int
  21. GetClientmachineAddressList(
  22. OUT CComBSTR& clientmachineAddressList
  23. )
  24. /*++
  25. --*/
  26. {
  27. char hostname[MAX_PATH+1];
  28. int errCode = 0;
  29. struct hostent* pHostEnt;
  30. if( gethostname(hostname, sizeof(hostname)) != 0 ) {
  31. errCode = WSAGetLastError();
  32. }
  33. else {
  34. pHostEnt = gethostbyname( hostname );
  35. if( NULL != pHostEnt ) {
  36. clientmachineAddressList = pHostEnt->h_name;
  37. }
  38. else {
  39. errCode = WSAGetLastError();
  40. }
  41. }
  42. return errCode;
  43. }
  44. BSTR
  45. CreateConnectParmsString(
  46. IN DWORD protocolType,
  47. IN CComBSTR &machineAddressList,
  48. IN CComBSTR &assistantAccount,
  49. IN CComBSTR &assistantAccountPwd,
  50. IN CComBSTR &helpSessionID,
  51. IN CComBSTR &helpSessionName,
  52. IN CComBSTR &helpSessionPwd,
  53. IN CComBSTR &protocolSpecificParms
  54. )
  55. /*++
  56. Routine Description:
  57. Create a connect parms string. Format is:
  58. "protocolType,machineAddressList,assistantAccount,assistantAccountPwd,helpSessionName,helpSessionPwd,protocolSpecificParms"
  59. Arguments:
  60. protocolType - Identifies the protocol type.
  61. See RemoteDesktopChannels.h
  62. machineAddressList - Identifies network address of server machine.
  63. assistantAccountName - Account name for initial log in to server
  64. machine, ignore for Whistler
  65. assistantAccountNamePwd - Password for assistantAccountName
  66. helpSessionID - Help session identifier.
  67. helpSessionName - Help session name.
  68. helpSessionPwd - Password to help session once logged in to server
  69. machine.
  70. protocolSpecificParms - Parameters specific to a particular protocol.
  71. Return Value:
  72. --*/
  73. {
  74. CComBSTR result;
  75. WCHAR buf[256];
  76. UNREFERENCED_PARAMETER(assistantAccount);
  77. //
  78. // Add a version stamp for our connect parm.
  79. wsprintf(buf, TEXT("%ld"), SALEM_CURRENT_CONNECTPARM_VERSION);
  80. result = buf;
  81. result += TEXT(",");
  82. wsprintf(buf, TEXT("%ld"), protocolType);
  83. result += buf;
  84. result += TEXT(",");
  85. result += machineAddressList;
  86. result += TEXT(",");
  87. result += assistantAccountPwd;
  88. result += TEXT(",");
  89. result += helpSessionID;
  90. result += TEXT(",");
  91. result += helpSessionName;
  92. result += TEXT(",");
  93. result += helpSessionPwd;
  94. if (protocolSpecificParms.Length() > 0) {
  95. result += TEXT(",");
  96. result += protocolSpecificParms;
  97. }
  98. return result.Detach();
  99. }
  100. DWORD
  101. ParseHelpAccountName(
  102. IN BSTR helpAccount,
  103. OUT CComBSTR& machineAddressList,
  104. OUT CComBSTR& AccountName
  105. )
  106. /*++
  107. HelpAccount in connection parameter is <machineAddressList>\HelpAssistant.
  108. --*/
  109. {
  110. DC_BEGIN_FN("ParseHelpAccountName");
  111. BSTR tmp;
  112. WCHAR *tok;
  113. DWORD result = ERROR_SUCCESS;
  114. DWORD len;
  115. //
  116. // Make a copy of the input string so we can parse it.
  117. //
  118. tmp = SysAllocString(helpAccount);
  119. if (tmp == NULL) {
  120. TRC_ERR((TB, TEXT("Can't allocate parms string.")));
  121. result = ERROR_OUTOFMEMORY;
  122. goto CLEANUPANDEXIT;
  123. }
  124. machineAddressList = L"";
  125. AccountName = L"";
  126. //
  127. // Machine Name
  128. //
  129. tok = wcstok(tmp, L"\\");
  130. if (tok != NULL) {
  131. machineAddressList = tok;
  132. }
  133. else {
  134. //
  135. // for backward compatible.
  136. //
  137. machineAddressList = L"";
  138. AccountName = helpAccount;
  139. goto CLEANUPANDEXIT;
  140. }
  141. //
  142. // Actual help assistant account name.
  143. //
  144. len = wcslen(helpAccount);
  145. if (tok < (tmp + len)) {
  146. tok += wcslen(tok);
  147. tok += 1;
  148. if (*tok != L'\0') {
  149. AccountName = tok;
  150. }
  151. }
  152. //
  153. // Help Assistant accout name must be in the string or
  154. // this is critical error.
  155. //
  156. if( AccountName.Length() == 0 ) {
  157. result = ERROR_INVALID_PARAMETER;
  158. }
  159. CLEANUPANDEXIT:
  160. if (tmp != NULL) {
  161. SysFreeString(tmp);
  162. }
  163. DC_END_FN();
  164. return result;
  165. }
  166. DWORD
  167. ParseConnectParmsString(
  168. IN BSTR parmsString,
  169. OUT DWORD* pdwConnParmVersion,
  170. OUT DWORD *protocolType,
  171. OUT CComBSTR &machineAddressList,
  172. OUT CComBSTR &assistantAccount,
  173. OUT CComBSTR &assistantAccountPwd,
  174. OUT CComBSTR &helpSessionID,
  175. OUT CComBSTR &helpSessionName,
  176. OUT CComBSTR &helpSessionPwd,
  177. OUT CComBSTR &protocolSpecificParms
  178. )
  179. /*++
  180. Routine Description:
  181. Parse a connect string created by a call to CreateConnectParmsString.
  182. Arguments:
  183. Return Value:
  184. ERROR_SUCCESS on success. Otherwise, an error code is returned.
  185. --*/
  186. {
  187. DC_BEGIN_FN("ParseConnectParmsString");
  188. BSTR tmp;
  189. WCHAR *tok;
  190. DWORD result = ERROR_SUCCESS;
  191. DWORD len;
  192. DWORD dwVersion = 0;
  193. UNREFERENCED_PARAMETER(assistantAccount);
  194. //
  195. // Make a copy of the input string so we can parse it.
  196. //
  197. tmp = SysAllocString(parmsString);
  198. if (tmp == NULL) {
  199. TRC_ERR((TB, TEXT("Can't allocate parms string.")));
  200. result = ERROR_OUTOFMEMORY;
  201. goto CLEANUPANDEXIT;
  202. }
  203. //
  204. // Retrieve connect parm version stamp, Whistler beta 1
  205. // connect parm does not have version stamp, bail out,
  206. // sessmgr/termsrv will wipe out pending help.
  207. //
  208. tok = wcstok(tmp, L",");
  209. if (tok != NULL) {
  210. dwVersion = _wtol(tok);
  211. }
  212. else {
  213. result = ERROR_INVALID_USER_BUFFER;
  214. goto CLEANUPANDEXIT;
  215. }
  216. if( dwVersion < SALEM_FIRST_VALID_CONNECTPARM_VERSION ) {
  217. //
  218. // connect parm is whistler beta 1
  219. //
  220. result = ERROR_NOT_SUPPORTED;
  221. goto CLEANUPANDEXIT;
  222. }
  223. *pdwConnParmVersion = dwVersion;
  224. //
  225. // We have no use for version at this time,
  226. // future update on connect parm should
  227. // take make the necessary change
  228. //
  229. //
  230. // Protocol.
  231. //
  232. tok = wcstok(NULL, L",");
  233. if (tok != NULL) {
  234. *protocolType = _wtoi(tok);
  235. }
  236. //
  237. // Machine Name
  238. //
  239. tok = wcstok(NULL, L",");
  240. if (tok != NULL) {
  241. machineAddressList = tok;
  242. }
  243. else {
  244. result = ERROR_INVALID_USER_BUFFER;
  245. goto CLEANUPANDEXIT;
  246. }
  247. //
  248. // Assistant Account Password
  249. //
  250. tok = wcstok(NULL, L",");
  251. if (tok != NULL) {
  252. assistantAccountPwd = tok;
  253. }
  254. else {
  255. result = ERROR_INVALID_USER_BUFFER;
  256. goto CLEANUPANDEXIT;
  257. }
  258. //
  259. // Help Session ID
  260. //
  261. tok = wcstok(NULL, L",");
  262. if (tok != NULL) {
  263. helpSessionID = tok;
  264. }
  265. else {
  266. result = ERROR_INVALID_USER_BUFFER;
  267. goto CLEANUPANDEXIT;
  268. }
  269. //
  270. // Help Session Name
  271. //
  272. tok = wcstok(NULL, L",");
  273. if (tok != NULL) {
  274. helpSessionName = tok;
  275. }
  276. else {
  277. result = ERROR_INVALID_USER_BUFFER;
  278. goto CLEANUPANDEXIT;
  279. }
  280. //
  281. // Help Session Password
  282. //
  283. tok = wcstok(NULL, L",");
  284. if (tok != NULL) {
  285. helpSessionPwd = tok;
  286. }
  287. else {
  288. result = ERROR_INVALID_USER_BUFFER;
  289. goto CLEANUPANDEXIT;
  290. }
  291. //
  292. // Protocol-Specific Parms
  293. //
  294. len = wcslen(parmsString);
  295. if (tok < (tmp + len)) {
  296. tok += wcslen(tok);
  297. tok += 1;
  298. if (*tok != L'\0') {
  299. protocolSpecificParms = tok;
  300. }
  301. else {
  302. protocolSpecificParms = L"";
  303. }
  304. }
  305. else {
  306. protocolSpecificParms = L"";
  307. }
  308. CLEANUPANDEXIT:
  309. if (result != ERROR_SUCCESS) {
  310. TRC_ERR((TB, TEXT("Error parsing %s"), parmsString));
  311. }
  312. if (tmp != NULL) {
  313. SysFreeString(tmp);
  314. }
  315. DC_END_FN();
  316. return result;
  317. }
  318. BSTR
  319. ReallocBSTR(
  320. IN BSTR origStr,
  321. IN DWORD requiredByteLen
  322. )
  323. /*++
  324. Routine Description:
  325. Realloc a BSTR
  326. Arguments:
  327. Return Value:
  328. The realloc'd string on success. Otherwise, NULL is returned.
  329. --*/
  330. {
  331. DC_BEGIN_FN("ReallocBSTR");
  332. BSTR tmp;
  333. DWORD len;
  334. DWORD origLen;
  335. //
  336. // Allocate the new string.
  337. //
  338. tmp = SysAllocStringByteLen(NULL, requiredByteLen);
  339. if (tmp == NULL) {
  340. TRC_ERR((TB, TEXT("Failed to allocate %ld bytes."), requiredByteLen));
  341. goto CLEANUPANDEXIT;
  342. }
  343. //
  344. // Copy data from the original string.
  345. //
  346. origLen = SysStringByteLen(origStr);
  347. len = origLen <= requiredByteLen ? origLen : requiredByteLen;
  348. memcpy(tmp, origStr, len);
  349. //
  350. // Release the old string.
  351. //
  352. SysFreeString(origStr);
  353. CLEANUPANDEXIT:
  354. DC_END_FN();
  355. return tmp;
  356. }
  357. DWORD
  358. CreateSystemSid(
  359. PSID *ppSystemSid
  360. )
  361. /*++
  362. Routine Description:
  363. Create a SYSTEM SID.
  364. Arguments:
  365. Return Value:
  366. ERROR_SUCCESS on success. Otherwise, an error code is returned.
  367. --*/
  368. {
  369. DC_BEGIN_FN("CreateSystemSid");
  370. DWORD dwStatus = ERROR_SUCCESS;
  371. PSID pSid;
  372. SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY;
  373. TRC_ASSERT(ppSystemSid != NULL, (TB, L"ppSystemSid != NULL"));
  374. if( TRUE == AllocateAndInitializeSid(
  375. &SidAuthority,
  376. 1,
  377. SECURITY_LOCAL_SYSTEM_RID,
  378. 0, 0, 0, 0, 0, 0, 0,
  379. &pSid
  380. ) )
  381. {
  382. *ppSystemSid = pSid;
  383. }
  384. else
  385. {
  386. dwStatus = GetLastError();
  387. }
  388. DC_END_FN();
  389. return dwStatus;
  390. }
  391. BOOL
  392. IsSystemToken(
  393. HANDLE TokenHandle,
  394. PSID pSystemSid
  395. )
  396. /*++
  397. Routine Description:
  398. Returns whether the current token is running under SYSTEM security.
  399. Arguments:
  400. TokenHandle - Param1 Thread or process token
  401. pSystemSid - System SID.
  402. Return Value:
  403. TRUE if System token. FALSE otherwise.
  404. --*/
  405. {
  406. DC_BEGIN_FN("IsSystemToken");
  407. BOOL Result = FALSE;
  408. ULONG ReturnLength, BufferLength;
  409. DWORD dwStatus;
  410. PTOKEN_USER pTokenUser = NULL;
  411. TRC_ASSERT(NULL != pSystemSid, (TB, L"NULL != pSystemSid"));
  412. // Get user SID.
  413. ReturnLength = 0;
  414. Result = GetTokenInformation(
  415. TokenHandle,
  416. TokenUser,
  417. NULL,
  418. 0,
  419. &ReturnLength
  420. );
  421. if( ReturnLength == 0 )
  422. {
  423. TRC_ERR((TB, L"GetTokenInformation: %08X", GetLastError()));
  424. Result = FALSE;
  425. CloseHandle( TokenHandle );
  426. goto CLEANUPANDEXIT;
  427. }
  428. BufferLength = ReturnLength;
  429. pTokenUser = (PTOKEN_USER)LocalAlloc( LPTR, BufferLength );
  430. if( pTokenUser == NULL )
  431. {
  432. TRC_ERR((TB, L"LocalAlloc: %08X", GetLastError()));
  433. Result = FALSE;
  434. CloseHandle( TokenHandle );
  435. goto CLEANUPANDEXIT;
  436. }
  437. Result = GetTokenInformation(
  438. TokenHandle,
  439. TokenUser,
  440. pTokenUser,
  441. BufferLength,
  442. &ReturnLength
  443. );
  444. CloseHandle( TokenHandle );
  445. if( TRUE == Result ) {
  446. Result = EqualSid( pTokenUser->User.Sid, pSystemSid);
  447. }
  448. else {
  449. TRC_ERR((TB, L"GetTokenInformation: %08X", GetLastError()));
  450. }
  451. CLEANUPANDEXIT:
  452. if( pTokenUser )
  453. {
  454. LocalFree( pTokenUser );
  455. }
  456. DC_END_FN();
  457. return Result;
  458. }
  459. BOOL
  460. IsCallerSystem(
  461. PSID pSystemSid
  462. )
  463. /*++
  464. Routine Description:
  465. Returns whether the current thread is running under SYSTEM security.
  466. NOTE: Caller should be impersonated prior to invoking this function.
  467. Arguments:
  468. pSystemSid - System SID.
  469. Return Value:
  470. TRUE if System. FALSE otherwise.
  471. --*/
  472. {
  473. DC_BEGIN_FN("IsCallerSystem");
  474. BOOL Result;
  475. HANDLE TokenHandle;
  476. //
  477. // Open the thread token and check if System token.
  478. //
  479. Result = OpenThreadToken(
  480. GetCurrentThread(),
  481. TOKEN_QUERY,
  482. FALSE, // Use impersonation
  483. &TokenHandle
  484. );
  485. if( TRUE == Result ) {
  486. //
  487. // This token should not be released. This function does not leak
  488. // handles.
  489. //
  490. Result = IsSystemToken(TokenHandle, pSystemSid);
  491. }
  492. else {
  493. TRC_ERR((TB, L"OpenThreadToken: %08X", GetLastError()));
  494. }
  495. DC_END_FN();
  496. return Result;
  497. }
  498. void
  499. AttachDebugger(
  500. LPCTSTR pszDebugger
  501. )
  502. /*++
  503. Routine Description:
  504. Attach debugger to our process or process hosting our DLL.
  505. Parameters:
  506. pszDebugger : Debugger command, e.g. ntsd -d -g -G -p %d
  507. Returns:
  508. None.
  509. Note:
  510. Must have "-p %d" since we don't know debugger's parameter for process.
  511. --*/
  512. {
  513. //
  514. // Attach debugger
  515. //
  516. if( !IsDebuggerPresent() ) {
  517. TCHAR szCommand[256];
  518. PROCESS_INFORMATION ProcessInfo;
  519. STARTUPINFO StartupInfo;
  520. //
  521. // ntsd -d -g -G -p %d
  522. //
  523. wsprintf( szCommand, pszDebugger, GetCurrentProcessId() );
  524. ZeroMemory(&StartupInfo, sizeof(StartupInfo));
  525. StartupInfo.cb = sizeof(StartupInfo);
  526. if (!CreateProcess(NULL, szCommand, NULL, NULL, FALSE, 0, NULL, NULL, &StartupInfo, &ProcessInfo)) {
  527. return;
  528. }
  529. else {
  530. CloseHandle(ProcessInfo.hProcess);
  531. CloseHandle(ProcessInfo.hThread);
  532. while (!IsDebuggerPresent())
  533. {
  534. Sleep(500);
  535. }
  536. }
  537. } else {
  538. DebugBreak();
  539. }
  540. return;
  541. }
  542. void
  543. AttachDebuggerIfAsked(HINSTANCE hInst)
  544. /*++
  545. Routine Description:
  546. Check if debug enable flag in our registry HKLM\Software\Microsoft\Remote Desktop\<module name>,
  547. if enable, attach debugger to running process.
  548. Parameter :
  549. hInst : instance handle.
  550. Returns:
  551. None.
  552. --*/
  553. {
  554. CRegKey regKey;
  555. DWORD dwStatus;
  556. TCHAR szModuleName[MAX_PATH+1];
  557. TCHAR szFileName[MAX_PATH+1];
  558. CComBSTR bstrRegKey(_TEXT("Software\\Microsoft\\Remote Desktop\\"));
  559. TCHAR szDebugCmd[256];
  560. DWORD cbDebugCmd = sizeof(szDebugCmd)/sizeof(szDebugCmd[0]);
  561. dwStatus = GetModuleFileName( hInst, szModuleName, MAX_PATH+1 );
  562. if( 0 == dwStatus ) {
  563. //
  564. // Can't attach debugger with name.
  565. //
  566. return;
  567. }
  568. _tsplitpath( szModuleName, NULL, NULL, szFileName, NULL );
  569. bstrRegKey += szFileName;
  570. //
  571. // Check if we are asked to attach/break into debugger
  572. //
  573. dwStatus = regKey.Open( HKEY_LOCAL_MACHINE, bstrRegKey );
  574. if( 0 != dwStatus ) {
  575. return;
  576. }
  577. dwStatus = regKey.QueryValue( szDebugCmd, _TEXT("Debugger"), &cbDebugCmd );
  578. if( 0 != dwStatus || cbDebugCmd > 200 ) {
  579. // 200 chars is way too much for debugger command.
  580. return;
  581. }
  582. AttachDebugger( szDebugCmd );
  583. return;
  584. }
  585. DWORD
  586. HashSecurityData(
  587. IN PBYTE const pbData,
  588. IN DWORD cbData,
  589. OUT CComBSTR& bstrHashedData
  590. )
  591. /*++
  592. Routine Description:
  593. Hash a blob of data and return hased data in BSTR
  594. Parameters:
  595. pbData : Pointer to data to be hashed.
  596. cbData : Size of data to be hashed.
  597. bstrHashedData : Return hashed data in BSTR form.
  598. Returns:
  599. ERROR_SUCCESS or error code.
  600. --*/
  601. {
  602. DC_BEGIN_FN("HashSecurityData");
  603. DWORD dwStatus;
  604. LPSTR pbEncodedData = NULL;
  605. DWORD cbEncodedData = 0;
  606. PBYTE pbHashedData = NULL;
  607. DWORD cbHashedData = 0;
  608. DWORD dwSize;
  609. HCRYPTPROV hCryptProv = NULL;
  610. HCRYPTHASH hHash = NULL;
  611. BOOL bSuccess;
  612. bSuccess = CryptAcquireContext(
  613. &hCryptProv,
  614. NULL,
  615. NULL,
  616. PROV_RSA_FULL,
  617. CRYPT_VERIFYCONTEXT
  618. );
  619. if( FALSE == bSuccess ) {
  620. dwStatus = GetLastError();
  621. TRC_ERR((TB, L"CryptAcquireContext: %08X", dwStatus));
  622. goto CLEANUPANDEXIT;
  623. }
  624. bSuccess = CryptCreateHash(
  625. hCryptProv,
  626. CALG_SHA1,
  627. 0,
  628. 0,
  629. &hHash
  630. );
  631. if( FALSE == bSuccess ) {
  632. dwStatus = GetLastError();
  633. TRC_ERR((TB, L"CryptCreateHash: %08X", dwStatus));
  634. goto CLEANUPANDEXIT;
  635. }
  636. bSuccess = CryptHashData(
  637. hHash,
  638. pbData,
  639. cbData,
  640. 0
  641. );
  642. if( FALSE == bSuccess ) {
  643. dwStatus = GetLastError();
  644. TRC_ERR((TB, L"CryptHashData: %08X", dwStatus));
  645. goto CLEANUPANDEXIT;
  646. }
  647. dwSize = sizeof( cbHashedData );
  648. bSuccess = CryptGetHashParam(
  649. hHash,
  650. HP_HASHSIZE,
  651. (PBYTE)&cbHashedData,
  652. &dwSize,
  653. 0
  654. );
  655. if( FALSE == bSuccess ) {
  656. dwStatus = GetLastError();
  657. TRC_ERR((TB, L"CryptGetHashParam with HP_HASHSIZE : %08X", dwStatus));
  658. goto CLEANUPANDEXIT;
  659. }
  660. pbHashedData = (PBYTE)LocalAlloc(LPTR, cbHashedData);
  661. if( NULL == pbHashedData ) {
  662. dwStatus = GetLastError();
  663. goto CLEANUPANDEXIT;
  664. }
  665. bSuccess = CryptGetHashParam(
  666. hHash,
  667. HP_HASHVAL,
  668. pbHashedData,
  669. &cbHashedData,
  670. 0
  671. );
  672. if( FALSE == bSuccess ) {
  673. dwStatus = GetLastError();
  674. TRC_ERR((TB, L"CryptGetHashParam with HP_HASHVAL : %08X", dwStatus));
  675. goto CLEANUPANDEXIT;
  676. }
  677. //
  678. // Hash data and convert to string form.
  679. //
  680. dwStatus = LSBase64EncodeA(
  681. pbHashedData,
  682. cbHashedData,
  683. NULL,
  684. &cbEncodedData
  685. );
  686. if( ERROR_SUCCESS != dwStatus ) {
  687. TRC_ERR((TB, L"LSBase64EncodeA : %08X", dwStatus));
  688. goto CLEANUPANDEXIT;
  689. }
  690. pbEncodedData = (LPSTR) LocalAlloc( LPTR, cbEncodedData+1 );
  691. if( NULL == pbEncodedData ) {
  692. dwStatus = GetLastError();
  693. goto CLEANUPANDEXIT;
  694. }
  695. dwStatus = LSBase64EncodeA(
  696. pbHashedData,
  697. cbHashedData,
  698. pbEncodedData,
  699. &cbEncodedData
  700. );
  701. if( ERROR_SUCCESS == dwStatus ) {
  702. //
  703. // Base64 encoding always add '\r', '\n' at the end,
  704. // remove it
  705. //
  706. if( pbEncodedData[cbEncodedData - 1] == '\n' &&
  707. pbEncodedData[cbEncodedData - 2] == '\r' )
  708. {
  709. pbEncodedData[cbEncodedData - 2] = 0;
  710. cbEncodedData -= 2;
  711. }
  712. bstrHashedData = pbEncodedData;
  713. }
  714. else {
  715. TRC_ERR((TB, L"LSBase64EncodeA : %08X", dwStatus));
  716. }
  717. CLEANUPANDEXIT:
  718. if( NULL != pbEncodedData ) {
  719. LocalFree( pbEncodedData );
  720. }
  721. if( NULL != pbHashedData ) {
  722. LocalFree( pbHashedData );
  723. }
  724. if( NULL != hHash ) {
  725. CryptDestroyHash( hHash );
  726. }
  727. if( NULL != hCryptProv ) {
  728. CryptReleaseContext( hCryptProv, 0 );
  729. }
  730. DC_END_FN();
  731. return dwStatus;
  732. }