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.

1001 lines
22 KiB

  1. /*++
  2. Copyright (c) 1995-1996 Microsoft Corporation
  3. Module Name :
  4. tokencache.cxx
  5. Abstract:
  6. Ming's token cache refactored for general consumption
  7. Author:
  8. Bilal Alam (balam) May-4-2000
  9. Revision History:
  10. --*/
  11. #include <iis.h>
  12. #include "dbgutil.h"
  13. #include <acache.hxx>
  14. #include <string.hxx>
  15. #include <tokencache.hxx>
  16. #include <irtltoken.h>
  17. #include <ntsecapi.h>
  18. #include <wincrypt.h>
  19. ALLOC_CACHE_HANDLER * TOKEN_CACHE_ENTRY::sm_pachTokenCacheEntry = NULL;
  20. //
  21. // Handle of a cryptographic service provider
  22. //
  23. HCRYPTPROV g_hCryptProv = NULL;
  24. //static
  25. HRESULT
  26. TOKEN_CACHE_ENTRY::Initialize(
  27. VOID
  28. )
  29. /*++
  30. Description:
  31. Token entry lookaside initialization
  32. Arguments:
  33. None
  34. Return:
  35. HRESULT
  36. --*/
  37. {
  38. ALLOC_CACHE_CONFIGURATION acConfig;
  39. HRESULT hr;
  40. //
  41. // Initialize allocation lookaside
  42. //
  43. acConfig.nConcurrency = 1;
  44. acConfig.nThreshold = 100;
  45. acConfig.cbSize = sizeof( TOKEN_CACHE_ENTRY );
  46. DBG_ASSERT( sm_pachTokenCacheEntry == NULL );
  47. sm_pachTokenCacheEntry = new ALLOC_CACHE_HANDLER( "TOKEN_CACHE_ENTRY",
  48. &acConfig );
  49. if ( sm_pachTokenCacheEntry == NULL )
  50. {
  51. hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
  52. DBGPRINTF(( DBG_CONTEXT,
  53. "Error initializing sm_pachTokenCacheEntry. hr = 0x%x\n",
  54. hr ));
  55. return hr;
  56. }
  57. return NO_ERROR;
  58. }
  59. //static
  60. VOID
  61. TOKEN_CACHE_ENTRY::Terminate(
  62. VOID
  63. )
  64. /*++
  65. Description:
  66. Token cache cleanup
  67. Arguments:
  68. None
  69. Return:
  70. None
  71. --*/
  72. {
  73. if ( sm_pachTokenCacheEntry != NULL )
  74. {
  75. delete sm_pachTokenCacheEntry;
  76. sm_pachTokenCacheEntry = NULL;
  77. }
  78. }
  79. HRESULT
  80. TOKEN_CACHE_ENTRY::Create(
  81. IN HANDLE hToken,
  82. IN LARGE_INTEGER *pliPwdExpiry,
  83. IN BOOL fImpersonation
  84. )
  85. /*++
  86. Description:
  87. Initialize a cached token
  88. Arguments:
  89. hToken - Token
  90. liPwdExpiry - Password expiration time
  91. fImpersonation - Is hToken an impersonation token?
  92. Return:
  93. HRESULT
  94. --*/
  95. {
  96. if ( hToken == NULL )
  97. {
  98. DBG_ASSERT( FALSE );
  99. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  100. }
  101. if ( fImpersonation )
  102. {
  103. m_hImpersonationToken = hToken;
  104. }
  105. else
  106. {
  107. m_hPrimaryToken = hToken;
  108. }
  109. if (pliPwdExpiry)
  110. {
  111. memcpy( ( VOID * )&m_liPwdExpiry,
  112. ( VOID * )pliPwdExpiry,
  113. sizeof( LARGE_INTEGER ) );
  114. }
  115. return NO_ERROR;
  116. }
  117. HANDLE
  118. TOKEN_CACHE_ENTRY::QueryImpersonationToken(
  119. VOID
  120. )
  121. /*++
  122. Description:
  123. Get impersonation token
  124. Arguments:
  125. None
  126. Return:
  127. Handle to impersonation token
  128. --*/
  129. {
  130. if ( m_hImpersonationToken == NULL )
  131. {
  132. LockCacheEntry();
  133. if ( m_hImpersonationToken == NULL )
  134. {
  135. DBG_ASSERT( m_hPrimaryToken != NULL );
  136. if ( !DuplicateTokenEx( m_hPrimaryToken,
  137. TOKEN_ALL_ACCESS,
  138. NULL,
  139. SecurityImpersonation,
  140. TokenImpersonation,
  141. &m_hImpersonationToken ) )
  142. {
  143. DBGPRINTF(( DBG_CONTEXT,
  144. "DuplicateTokenEx failed, GetLastError = %lx\n",
  145. GetLastError() ));
  146. }
  147. else
  148. {
  149. DBG_ASSERT( m_hImpersonationToken != NULL );
  150. //
  151. // Tweak the token so that all member of the worker process group
  152. // can access it, and so that it works correctly for OOP requests
  153. //
  154. HRESULT hr = GrantWpgAccessToToken( m_hImpersonationToken );
  155. DBG_ASSERT( SUCCEEDED( hr ) );
  156. hr = AddWpgToTokenDefaultDacl( m_hImpersonationToken );
  157. DBG_ASSERT( SUCCEEDED( hr ) );
  158. }
  159. }
  160. UnlockCacheEntry();
  161. }
  162. return m_hImpersonationToken;
  163. }
  164. HANDLE
  165. TOKEN_CACHE_ENTRY::QueryPrimaryToken(
  166. VOID
  167. )
  168. /*++
  169. Description:
  170. Get primary token
  171. Arguments:
  172. None
  173. Return:
  174. Handle to primary token
  175. --*/
  176. {
  177. if ( m_hPrimaryToken == NULL )
  178. {
  179. LockCacheEntry();
  180. if ( m_hPrimaryToken == NULL )
  181. {
  182. DBG_ASSERT( m_hImpersonationToken != NULL );
  183. if ( !DuplicateTokenEx( m_hImpersonationToken,
  184. TOKEN_ALL_ACCESS,
  185. NULL,
  186. SecurityImpersonation,
  187. TokenPrimary,
  188. &m_hPrimaryToken ) )
  189. {
  190. DBGPRINTF(( DBG_CONTEXT,
  191. "DuplicateTokenEx failed, GetLastError = %lx\n",
  192. GetLastError() ));
  193. }
  194. else
  195. {
  196. DBG_ASSERT( m_hPrimaryToken != NULL );
  197. }
  198. }
  199. UnlockCacheEntry();
  200. }
  201. return m_hPrimaryToken;
  202. }
  203. PSID
  204. TOKEN_CACHE_ENTRY::QuerySid(
  205. VOID
  206. )
  207. /*++
  208. Description:
  209. Get the sid for this token
  210. Arguments:
  211. None
  212. Return:
  213. Points to SID buffer owned by this object
  214. --*/
  215. {
  216. BYTE abTokenUser[ SID_DEFAULT_SIZE + sizeof( TOKEN_USER ) ];
  217. TOKEN_USER * pTokenUser = (TOKEN_USER*) abTokenUser;
  218. BOOL fRet;
  219. HANDLE hImpersonation;
  220. DWORD cbBuffer;
  221. hImpersonation = QueryImpersonationToken();
  222. if ( hImpersonation == NULL )
  223. {
  224. return NULL;
  225. }
  226. if ( m_pSid == NULL )
  227. {
  228. LockCacheEntry();
  229. fRet = GetTokenInformation( hImpersonation,
  230. TokenUser,
  231. pTokenUser,
  232. sizeof( abTokenUser ),
  233. &cbBuffer );
  234. if ( fRet )
  235. {
  236. //
  237. // If we can't get the sid, then that is OK. We're return NULL
  238. // and as a result we will do the access check always
  239. //
  240. memcpy( m_abSid,
  241. pTokenUser->User.Sid,
  242. sizeof( m_abSid ) );
  243. m_pSid = m_abSid;
  244. }
  245. UnlockCacheEntry();
  246. }
  247. return m_pSid;
  248. }
  249. HRESULT
  250. TOKEN_CACHE_KEY::GenMD5HashKey(
  251. IN STRU & strKey,
  252. OUT STRA * strHashKey
  253. )
  254. /*++
  255. Description:
  256. Generate MD5 hash key used for token cache
  257. Arguments:
  258. strKey - string to be MD5 hashed
  259. strHashKey - MD5 hashed string
  260. Return:
  261. HRESULT
  262. --*/
  263. {
  264. HRESULT hr;
  265. DWORD dwError;
  266. HCRYPTHASH hHash = NULL;
  267. DWORD dwHashDataLen;
  268. STACK_BUFFER( buffHashData, DEFAULT_MD5_HASH_SIZE );
  269. if ( !CryptCreateHash( g_hCryptProv,
  270. CALG_MD5,
  271. 0,
  272. 0,
  273. &hHash ) )
  274. {
  275. hr = HRESULT_FROM_WIN32( GetLastError() );
  276. DBGPRINTF((DBG_CONTEXT,
  277. "CryptCreateHash() failed : hr = 0x%x\n",
  278. hr ));
  279. return hr;
  280. }
  281. if ( !CryptHashData( hHash,
  282. ( BYTE * )strKey.QueryStr(),
  283. strKey.QueryCB(),
  284. 0 ) )
  285. {
  286. hr = HRESULT_FROM_WIN32( GetLastError() );
  287. DBGPRINTF((DBG_CONTEXT,
  288. "CryptHashData() failed : hr = 0x%x\n",
  289. hr ));
  290. goto exit;
  291. }
  292. dwHashDataLen = DEFAULT_MD5_HASH_SIZE;
  293. if ( !CryptGetHashParam( hHash,
  294. HP_HASHVAL,
  295. ( BYTE * )buffHashData.QueryPtr(),
  296. &dwHashDataLen,
  297. 0 ) )
  298. {
  299. dwError = GetLastError();
  300. if( dwError == ERROR_MORE_DATA )
  301. {
  302. if( !buffHashData.Resize( dwHashDataLen ) )
  303. {
  304. hr = E_OUTOFMEMORY;
  305. goto exit;
  306. }
  307. if( !CryptGetHashParam( hHash,
  308. HP_HASHVAL,
  309. ( BYTE * )buffHashData.QueryPtr(),
  310. &dwHashDataLen,
  311. 0 ) )
  312. {
  313. hr = HRESULT_FROM_WIN32( GetLastError() );
  314. goto exit;
  315. }
  316. }
  317. else
  318. {
  319. hr = HRESULT_FROM_WIN32( dwError );
  320. goto exit;
  321. }
  322. }
  323. //
  324. // Convert binary data to ASCII hex representation
  325. //
  326. hr = ToHex( buffHashData, _strHashKey );
  327. exit:
  328. CryptDestroyHash( hHash );
  329. ZeroMemory( ( VOID * )strKey.QueryStr(), strKey.QueryCB() );
  330. return hr;
  331. }
  332. HRESULT
  333. TOKEN_CACHE_KEY::CreateCacheKey(
  334. WCHAR * pszUserName,
  335. WCHAR * pszDomainName,
  336. WCHAR * pszPassword,
  337. DWORD dwLogonMethod
  338. )
  339. /*++
  340. Description:
  341. Build the key used for token cache
  342. Arguments:
  343. pszUserName - User name
  344. pszDomainName - Domain name
  345. pszPassword - Password
  346. dwLogonMethod - Logon method
  347. Return:
  348. HRESULT
  349. --*/
  350. {
  351. HRESULT hr;
  352. WCHAR achNum[ 64 ];
  353. STACK_STRU( strKey, 64 );
  354. if ( pszUserName == NULL ||
  355. pszDomainName == NULL ||
  356. pszPassword == NULL )
  357. {
  358. DBG_ASSERT( FALSE );
  359. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  360. }
  361. hr = strKey.Copy( pszUserName );
  362. if ( FAILED( hr ) )
  363. {
  364. return hr;
  365. }
  366. hr = strKey.Append( pszDomainName );
  367. if ( FAILED( hr ) )
  368. {
  369. return hr;
  370. }
  371. hr = strKey.Append( pszPassword );
  372. if ( FAILED( hr ) )
  373. {
  374. return hr;
  375. }
  376. _ultow( dwLogonMethod, achNum, 10 );
  377. hr = strKey.Append( achNum );
  378. if ( FAILED( hr ) )
  379. {
  380. return hr;
  381. }
  382. return GenMD5HashKey( strKey, &_strHashKey );
  383. }
  384. HRESULT
  385. TOKEN_CACHE::Initialize(
  386. VOID
  387. )
  388. /*++
  389. Description:
  390. Initialize token cache
  391. Arguments:
  392. None
  393. Return:
  394. HRESULT
  395. --*/
  396. {
  397. HRESULT hr;
  398. DWORD dwData;
  399. DWORD dwType;
  400. DWORD cbData = sizeof( DWORD );
  401. DWORD csecTTL = DEFAULT_CACHED_TOKEN_TTL;
  402. HKEY hKey;
  403. //
  404. // What is the TTL for the token cache
  405. //
  406. if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
  407. L"System\\CurrentControlSet\\Services\\inetinfo\\Parameters",
  408. 0,
  409. KEY_READ,
  410. &hKey ) == ERROR_SUCCESS )
  411. {
  412. DBG_ASSERT( hKey != NULL );
  413. if ( RegQueryValueEx( hKey,
  414. L"LastPriorityUPNLogon",
  415. NULL,
  416. &dwType,
  417. (LPBYTE) &dwData,
  418. &cbData ) == ERROR_SUCCESS &&
  419. dwType == REG_DWORD )
  420. {
  421. m_dwLastPriorityUPNLogon = dwData;
  422. }
  423. if ( RegQueryValueEx( hKey,
  424. L"UserTokenTTL",
  425. NULL,
  426. &dwType,
  427. (LPBYTE) &dwData,
  428. &cbData ) == ERROR_SUCCESS &&
  429. dwType == REG_DWORD )
  430. {
  431. csecTTL = dwData;
  432. }
  433. RegCloseKey( hKey );
  434. }
  435. //
  436. // We'll use TTL for scavenge period, and expect two inactive periods to
  437. // flush
  438. //
  439. hr = SetCacheConfiguration( csecTTL * 1000,
  440. csecTTL * 1000,
  441. 0,
  442. NULL );
  443. if ( FAILED( hr ) )
  444. {
  445. return hr;
  446. }
  447. //
  448. // Get a handle to the CSP we'll use for our MD5 hash functions.
  449. //
  450. if ( !CryptAcquireContext( &g_hCryptProv,
  451. NULL,
  452. NULL,
  453. PROV_RSA_FULL,
  454. CRYPT_VERIFYCONTEXT ) )
  455. {
  456. hr = HRESULT_FROM_WIN32( GetLastError() );
  457. DBGPRINTF(( DBG_CONTEXT,
  458. "CryptAcquireContext() failed. hr = 0x%x\n",
  459. hr ));
  460. return hr;
  461. }
  462. return TOKEN_CACHE_ENTRY::Initialize();
  463. }
  464. VOID
  465. TOKEN_CACHE::Terminate(
  466. VOID
  467. )
  468. /*++
  469. Description:
  470. Terminate token cache
  471. Arguments:
  472. None
  473. Return:
  474. None
  475. --*/
  476. {
  477. if ( g_hCryptProv )
  478. {
  479. CryptReleaseContext( g_hCryptProv, 0 );
  480. g_hCryptProv = NULL;
  481. }
  482. return TOKEN_CACHE_ENTRY::Terminate();
  483. }
  484. HRESULT
  485. TOKEN_CACHE::GetCachedToken(
  486. IN LPWSTR pszUserName,
  487. IN LPWSTR pszDomain,
  488. IN LPWSTR pszPassword,
  489. IN DWORD dwLogonMethod,
  490. IN BOOL fPossibleUPNLogon,
  491. OUT TOKEN_CACHE_ENTRY ** ppCachedToken,
  492. OUT DWORD * pdwLogonError,
  493. BOOL fAllowLocalSystem /* = FALSE */
  494. )
  495. /*++
  496. Description:
  497. Get cached token (the friendly interface for the token cache)
  498. Arguments:
  499. pszUserName - User name
  500. pszDomain - Domain name
  501. pszPassword - Password
  502. dwLogonMethod - Logon method (batch, interactive, etc)
  503. fPossibleUPNLogon - TRUE if we may need to do UPN logon,
  504. otherwise FALSE
  505. ppCachedToken - Filled with cached token on success
  506. pdwLogonError - Set to logon failure if *ppCacheToken==NULL
  507. pszDefaultDomain - Default domain specified in metabase
  508. Return:
  509. HRESULT
  510. --*/
  511. {
  512. TOKEN_CACHE_KEY tokenKey;
  513. TOKEN_CACHE_ENTRY * pCachedToken;
  514. HRESULT hr;
  515. HANDLE hToken = NULL;
  516. LARGE_INTEGER liPwdExpiry;
  517. LPVOID pProfile = NULL;
  518. DWORD dwProfileLength = 0;
  519. WCHAR * pszAtSign = NULL;
  520. WCHAR * pDomain[2];
  521. if ( pszUserName == NULL ||
  522. pszDomain == NULL ||
  523. pszPassword == NULL ||
  524. ppCachedToken == NULL ||
  525. pdwLogonError == NULL )
  526. {
  527. DBG_ASSERT( FALSE );
  528. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  529. }
  530. *ppCachedToken = NULL;
  531. *pdwLogonError = ERROR_SUCCESS;
  532. //
  533. // Find the key to look for
  534. //
  535. hr = tokenKey.CreateCacheKey( pszUserName,
  536. pszDomain,
  537. pszPassword,
  538. dwLogonMethod );
  539. if ( FAILED( hr ) )
  540. {
  541. return hr;
  542. }
  543. //
  544. // Look for it
  545. //
  546. hr = FindCacheEntry( &tokenKey,
  547. (CACHE_ENTRY**) ppCachedToken );
  548. if ( SUCCEEDED( hr ) )
  549. {
  550. DBG_ASSERT( *ppCachedToken != NULL );
  551. return hr;
  552. }
  553. //
  554. // Ok. It wasn't in the cache, create a token and add it
  555. //
  556. if ( fAllowLocalSystem &&
  557. 0 == _wcsicmp(L"LocalSystem", pszUserName) )
  558. {
  559. if (!OpenProcessToken(
  560. GetCurrentProcess(), // handle to process
  561. TOKEN_ALL_ACCESS, // desired access
  562. &hToken // returned token
  563. ) )
  564. {
  565. //
  566. // If we couldn't logon, then return no error. The caller will
  567. // determine failure due to *ppCachedToken == NULL
  568. //
  569. *pdwLogonError = GetLastError();
  570. hr = NO_ERROR;
  571. goto ExitPoint;
  572. }
  573. //
  574. // OpenProcessToken gives back a primary token
  575. // Below in the call to pCachedToken->Create we decide
  576. // if the token is an impersonation token or not based
  577. // on the LogonMethod. We know this is a primary token
  578. // therefor we set the LogonMethod here
  579. //
  580. dwLogonMethod = LOGON32_LOGON_SERVICE;
  581. }
  582. else
  583. {
  584. pszAtSign = wcschr( pszUserName, L'@' );
  585. if( pszAtSign != NULL && fPossibleUPNLogon )
  586. {
  587. if( !m_dwLastPriorityUPNLogon )
  588. {
  589. //
  590. // Try UPN logon first
  591. //
  592. pDomain[0] = L"";
  593. pDomain[1] = pszDomain;
  594. }
  595. else
  596. {
  597. //
  598. // Try default domain logon first
  599. //
  600. pDomain[0] = pszDomain;
  601. pDomain[1] = L"";
  602. }
  603. if(!LogonUserEx( pszUserName,
  604. pDomain[0],
  605. pszPassword,
  606. dwLogonMethod,
  607. LOGON32_PROVIDER_DEFAULT,
  608. &hToken,
  609. NULL, // Logon sid
  610. &pProfile,
  611. &dwProfileLength,
  612. NULL // Quota limits
  613. ) )
  614. {
  615. *pdwLogonError = GetLastError();
  616. if( *pdwLogonError == ERROR_LOGON_FAILURE )
  617. {
  618. if(!LogonUserEx( pszUserName,
  619. pDomain[1],
  620. pszPassword,
  621. dwLogonMethod,
  622. LOGON32_PROVIDER_DEFAULT,
  623. &hToken,
  624. NULL, // Logon sid
  625. &pProfile,
  626. &dwProfileLength,
  627. NULL // Quota limits
  628. ) )
  629. {
  630. //
  631. // If we couldn't logon, then return no error. The caller will
  632. // determine failure due to *ppCachedToken == NULL
  633. //
  634. *pdwLogonError = GetLastError();
  635. hr = NO_ERROR;
  636. goto ExitPoint;
  637. }
  638. }
  639. }
  640. }
  641. else
  642. {
  643. //
  644. // The user name is absolutely not in UPN format
  645. //
  646. if(!LogonUserEx( pszUserName,
  647. pszDomain,
  648. pszPassword,
  649. dwLogonMethod,
  650. LOGON32_PROVIDER_DEFAULT,
  651. &hToken,
  652. NULL, // Logon sid
  653. &pProfile,
  654. &dwProfileLength,
  655. NULL // Quota limits
  656. ) )
  657. {
  658. //
  659. // If we couldn't logon, then return no error. The caller will
  660. // determine failure due to *ppCachedToken == NULL
  661. //
  662. *pdwLogonError = GetLastError();
  663. hr = NO_ERROR;
  664. goto ExitPoint;
  665. }
  666. }
  667. }
  668. //
  669. // Create the entry
  670. //
  671. pCachedToken = new TOKEN_CACHE_ENTRY( this );
  672. if ( pCachedToken == NULL )
  673. {
  674. hr = HRESULT_FROM_WIN32( GetLastError() );
  675. goto ExitPoint;
  676. }
  677. //
  678. // Set the cache key
  679. //
  680. hr = pCachedToken->SetCacheKey( &tokenKey );
  681. if ( FAILED( hr ) )
  682. {
  683. goto ExitPoint;
  684. }
  685. if ( dwLogonMethod == LOGON32_LOGON_NETWORK )
  686. {
  687. //
  688. // Tweak the token so that all member of the worker process group
  689. // can access it, and so that it works correctly for OOP requests
  690. //
  691. // Note that we only do this for impersonation tokens. In the case
  692. // of a primary token, the TOKEN_CACHE_ENTRY::QueryImpersonationToken
  693. // will do it.
  694. //
  695. hr = GrantWpgAccessToToken( hToken );
  696. if ( FAILED( hr ) )
  697. {
  698. goto ExitPoint;
  699. }
  700. hr = AddWpgToTokenDefaultDacl( hToken );
  701. if ( FAILED( hr ) )
  702. {
  703. goto ExitPoint;
  704. }
  705. }
  706. //
  707. // Get the password expiration information for the current user
  708. //
  709. //
  710. // Set the token/properties
  711. //
  712. hr = pCachedToken->Create( hToken,
  713. pProfile ?
  714. &(( ( PMSV1_0_INTERACTIVE_PROFILE )pProfile )->PasswordMustChange) :
  715. NULL,
  716. dwLogonMethod == LOGON32_LOGON_NETWORK );
  717. if ( FAILED( hr ) )
  718. {
  719. goto ExitPoint;
  720. }
  721. AddCacheEntry( pCachedToken );
  722. //
  723. // Return it
  724. //
  725. *ppCachedToken = pCachedToken;
  726. ExitPoint:
  727. if ( FAILED( hr ) )
  728. {
  729. if ( pCachedToken != NULL )
  730. {
  731. pCachedToken->DereferenceCacheEntry();
  732. }
  733. if ( hToken != NULL )
  734. {
  735. CloseHandle( hToken );
  736. }
  737. }
  738. if ( pProfile != NULL )
  739. {
  740. LsaFreeReturnBuffer( pProfile );
  741. }
  742. return hr;
  743. }
  744. HRESULT
  745. ToHex(
  746. IN BUFFER & buffSrc,
  747. OUT STRA & strDst
  748. )
  749. /*++
  750. Routine Description:
  751. Convert binary data to ASCII hex representation
  752. Arguments:
  753. buffSrc - binary data to convert
  754. strDst - buffer receiving ASCII representation of pSrc
  755. Return Value:
  756. HRESULT
  757. --*/
  758. {
  759. #define TOHEX(a) ( (a) >= 10 ? 'a' + (a) - 10 : '0' + (a) )
  760. HRESULT hr = S_OK;
  761. PBYTE pSrc;
  762. PCHAR pDst;
  763. hr = strDst.Resize( 2 * buffSrc.QuerySize() + 1 );
  764. if( FAILED( hr ) )
  765. {
  766. goto exit;
  767. }
  768. pSrc = ( PBYTE ) buffSrc.QueryPtr();
  769. pDst = strDst.QueryStr();
  770. for ( UINT i = 0, j = 0 ; i < buffSrc.QuerySize() ; i++ )
  771. {
  772. UINT v;
  773. v = pSrc[ i ] >> 4;
  774. pDst[ j++ ] = TOHEX( v );
  775. v = pSrc[ i ] & 0x0f;
  776. pDst[ j++ ] = TOHEX( v );
  777. }
  778. DBG_REQUIRE( strDst.SetLen( j ) );
  779. exit:
  780. return hr;
  781. }