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.

643 lines
18 KiB

  1. #include <wininetp.h>
  2. #include <urlmon.h>
  3. #include <splugin.hxx>
  4. #include <security.h>
  5. #include "auth.h"
  6. #define SSP_SPM_NT_DLL "security.dll"
  7. #define SSP_SPM_WIN95_DLL "secur32.dll"
  8. #define MAX_SILENT_RETRIES 3
  9. #define OUTPUT_BUFFER_LEN 10000
  10. #define HEADER_IDX 0
  11. #define REALM_IDX 1
  12. #define HOST_IDX 2
  13. #define URL_IDX 3
  14. #define METHOD_IDX 4
  15. #define USER_IDX 5
  16. #define PASS_IDX 6
  17. #define NONCE_IDX 7
  18. #define NC_IDX 8
  19. #define HWND_IDX 9
  20. #define NUM_BUFF 10
  21. #define ISC_MODE_AUTH 0
  22. #define ISC_MODE_PREAUTH 1
  23. #define ISC_MODE_UI 2
  24. struct DIGEST_PKG_DATA
  25. {
  26. LPSTR szAppCtx;
  27. LPSTR szUserCtx;
  28. };
  29. /*-----------------------------------------------------------------------------
  30. DIGEST_CTX
  31. -----------------------------------------------------------------------------*/
  32. // Globals
  33. PSecurityFunctionTable DIGEST_CTX::g_pFuncTbl = NULL;
  34. CredHandle DIGEST_CTX::g_hCred;
  35. /*---------------------------------------------------------------------------
  36. DIGEST_CTX::GetFuncTbl
  37. ---------------------------------------------------------------------------*/
  38. VOID DIGEST_CTX::GetFuncTbl()
  39. {
  40. HINSTANCE hSecLib;
  41. INIT_SECURITY_INTERFACE addrProcISI = NULL;
  42. #ifndef UNIX
  43. OSVERSIONINFO VerInfo;
  44. VerInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
  45. GetVersionEx (&VerInfo);
  46. if (VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
  47. {
  48. hSecLib = LoadLibrary (SSP_SPM_NT_DLL);
  49. }
  50. else if (VerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  51. #endif /* UNIX */
  52. {
  53. hSecLib = LoadLibrary (SSP_SPM_WIN95_DLL);
  54. }
  55. addrProcISI = (INIT_SECURITY_INTERFACE) GetProcAddress(hSecLib,
  56. SECURITY_ENTRYPOINT_ANSI);
  57. g_pFuncTbl = (*addrProcISI)();
  58. }
  59. /*---------------------------------------------------------------------------
  60. DIGEST_CTX::GetRequestUri
  61. ---------------------------------------------------------------------------*/
  62. LPSTR DIGEST_CTX::GetRequestUri()
  63. {
  64. DEBUG_ENTER ((
  65. DBG_HTTPAUTH,
  66. String,
  67. "DIGEST_CTX::GetRequestUri",
  68. "this=%#x",
  69. this
  70. ));
  71. LPSTR szUrl;
  72. DWORD cbUrl;
  73. URL_COMPONENTS sUrl;
  74. memset(&sUrl, 0, sizeof(sUrl));
  75. sUrl.dwStructSize = sizeof(sUrl);
  76. sUrl.dwHostNameLength = -1;
  77. sUrl.dwUrlPathLength = -1;
  78. sUrl.dwExtraInfoLength = -1;
  79. szUrl = _pRequest->GetURL();
  80. // Generate request-uri
  81. if (InternetCrackUrl(szUrl, strlen(szUrl), 0, &sUrl))
  82. {
  83. cbUrl = sUrl.dwUrlPathLength;
  84. szUrl = new CHAR[cbUrl+1];
  85. if (!szUrl)
  86. {
  87. // Alloc failure. Return NULL. We will
  88. // use _pRequest->GetURL instead.
  89. DEBUG_LEAVE(NULL);
  90. return NULL;
  91. }
  92. memcpy(szUrl, sUrl.lpszUrlPath, cbUrl);
  93. szUrl[cbUrl] = '\0';
  94. }
  95. else
  96. {
  97. // ICU failed. Return NULL which
  98. // will cause _pRequest->GetURL
  99. // to be used.
  100. DEBUG_LEAVE(NULL);
  101. return NULL;
  102. }
  103. DEBUG_LEAVE(szUrl);
  104. return szUrl;
  105. }
  106. /*---------------------------------------------------------------------------
  107. DIGEST_CTX::InitSecurityBuffers
  108. ---------------------------------------------------------------------------*/
  109. VOID DIGEST_CTX::InitSecurityBuffers(LPSTR szOutBuf, DWORD cbOutBuf,
  110. LPDWORD pdwSecFlags, DWORD dwISCMode)
  111. {
  112. DEBUG_ENTER ((
  113. DBG_HTTPAUTH,
  114. None,
  115. "DIGEST_CTX::InitSecurityBuffers",
  116. "this=%#x szout=%#x cbout=%d secflags=%#x iscmode=%#x",
  117. this,
  118. szOutBuf,
  119. cbOutBuf,
  120. (pdwSecFlags ? *pdwSecFlags : NULL),
  121. dwISCMode
  122. ));
  123. LPSTR pszPass = NULL;
  124. // Input Buffer.
  125. _SecBuffInDesc.cBuffers = NUM_BUFF;
  126. _SecBuffInDesc.pBuffers = _SecBuffIn;
  127. // Set Header
  128. _SecBuffIn[HEADER_IDX].pvBuffer = _szData;
  129. _SecBuffIn[HEADER_IDX].cbBuffer = _cbData;
  130. _SecBuffIn[HEADER_IDX].BufferType = SECBUFFER_TOKEN;
  131. // If credentials are supplied will be set to
  132. // ISC_REQ_USE_SUPPLIED_CREDS.
  133. // If prompting for auth dialog will be set to
  134. // ISC_REQ_PROMPT_FOR_CREDS.
  135. *pdwSecFlags = 0;
  136. // Set realm if no header, otherwise NULL.
  137. if (_SecBuffIn[HEADER_IDX].pvBuffer)
  138. {
  139. _SecBuffIn[REALM_IDX].pvBuffer = NULL;
  140. _SecBuffIn[REALM_IDX].cbBuffer = 0;
  141. }
  142. else
  143. {
  144. // We are preauthenticating using the realm
  145. _SecBuffIn[REALM_IDX].pvBuffer = _pPWC->lpszRealm;
  146. _SecBuffIn[REALM_IDX].cbBuffer = strlen(_pPWC->lpszRealm);
  147. }
  148. // Host.
  149. _SecBuffIn[HOST_IDX].pvBuffer = _pPWC->lpszHost;
  150. _SecBuffIn[HOST_IDX].cbBuffer = strlen(_pPWC->lpszHost);
  151. _SecBuffIn[HOST_IDX].BufferType = SECBUFFER_TOKEN;
  152. // Request URI.
  153. if (!_szRequestUri)
  154. {
  155. _szRequestUri = GetRequestUri();
  156. if (_szRequestUri)
  157. _SecBuffIn[URL_IDX].pvBuffer = _szRequestUri;
  158. else
  159. _SecBuffIn[URL_IDX].pvBuffer = _pRequest->GetURL();
  160. }
  161. _SecBuffIn[URL_IDX].cbBuffer = strlen((LPSTR) _SecBuffIn[URL_IDX].pvBuffer);
  162. _SecBuffIn[URL_IDX].BufferType = SECBUFFER_TOKEN;
  163. // HTTP method.
  164. _SecBuffIn[METHOD_IDX].cbBuffer =
  165. MapHttpMethodType(_pRequest->GetMethodType(), (LPCSTR*) &_SecBuffIn[METHOD_IDX].pvBuffer);
  166. _SecBuffIn[METHOD_IDX].BufferType = SECBUFFER_TOKEN;
  167. if (_SecBuffIn[PASS_IDX].pvBuffer)
  168. {
  169. INET_ASSERT(strlen((LPCSTR)_SecBuffIn[PASS_IDX].pvBuffer) == _SecBuffIn[PASS_IDX].cbBuffer);
  170. SecureZeroMemory(_SecBuffIn[PASS_IDX].pvBuffer, _SecBuffIn[PASS_IDX].cbBuffer);
  171. FREE_MEMORY(_SecBuffIn[PASS_IDX].pvBuffer);
  172. }
  173. // User and pass might be provided from pwc entry. Use only if
  174. // we have a challenge header (we don't pre-auth using supplied creds).
  175. if (dwISCMode == ISC_MODE_AUTH && _pPWC->lpszUser && *_pPWC->lpszUser
  176. && (pszPass = _pPWC->GetPass()) && *pszPass)
  177. {
  178. // User.
  179. _SecBuffIn[USER_IDX].pvBuffer = _pPWC->lpszUser;
  180. _SecBuffIn[USER_IDX].cbBuffer = strlen(_pPWC->lpszUser);
  181. _SecBuffIn[USER_IDX].BufferType = SECBUFFER_TOKEN;
  182. // Pass.
  183. _SecBuffIn[PASS_IDX].pvBuffer = pszPass;
  184. _SecBuffIn[PASS_IDX].cbBuffer = strlen(pszPass);
  185. _SecBuffIn[PASS_IDX].BufferType = SECBUFFER_TOKEN;
  186. *pdwSecFlags = ISC_REQ_USE_SUPPLIED_CREDS;
  187. }
  188. else
  189. {
  190. // User.
  191. _SecBuffIn[USER_IDX].pvBuffer = NULL;
  192. _SecBuffIn[USER_IDX].cbBuffer = 0;
  193. _SecBuffIn[USER_IDX].BufferType = SECBUFFER_TOKEN;
  194. // Pass.
  195. _SecBuffIn[PASS_IDX].pvBuffer = NULL;
  196. _SecBuffIn[PASS_IDX].cbBuffer = 0;
  197. _SecBuffIn[PASS_IDX].BufferType = SECBUFFER_TOKEN;
  198. }
  199. // If the 'if' statement above caused the pszPass variable to be allocated
  200. // but it was not assigned to _SecBuffIn[PASS_IDX].pvBuffer, then free it.
  201. if (pszPass != NULL && _SecBuffIn[PASS_IDX].pvBuffer == NULL)
  202. {
  203. SecureZeroMemory(pszPass, strlen(pszPass));
  204. FREE_MEMORY(pszPass);
  205. }
  206. if (dwISCMode == ISC_MODE_UI)
  207. *pdwSecFlags = ISC_REQ_PROMPT_FOR_CREDS;
  208. // Out Buffer.
  209. _SecBuffOutDesc.cBuffers = 1;
  210. _SecBuffOutDesc.pBuffers = _SecBuffOut;
  211. _SecBuffOut[0].pvBuffer = szOutBuf;
  212. _SecBuffOut[0].cbBuffer = cbOutBuf;
  213. _SecBuffOut[0].BufferType = SECBUFFER_TOKEN;
  214. DEBUG_LEAVE(0);
  215. }
  216. /*---------------------------------------------------------------------------
  217. Constructor
  218. ---------------------------------------------------------------------------*/
  219. DIGEST_CTX::DIGEST_CTX(HTTP_REQUEST_HANDLE_OBJECT *pRequest, BOOL fIsProxy,
  220. SPMData *pSPM, PWC* pPWC)
  221. : AUTHCTX(pSPM, pPWC)
  222. {
  223. SECURITY_STATUS ssResult;
  224. _fIsProxy = fIsProxy;
  225. _pRequest = pRequest;
  226. _szAlloc = NULL;
  227. _szData = NULL;
  228. _pvContext = NULL;
  229. _szRequestUri = NULL;
  230. _cbData = 0;
  231. _cbContext = 0;
  232. _nRetries = 0;
  233. // Zero out the security buffers and request context.
  234. memset(&_SecBuffInDesc, 0, sizeof(_SecBuffInDesc));
  235. memset(&_SecBuffOutDesc, 0, sizeof(_SecBuffInDesc));
  236. memset(_SecBuffIn, 0, sizeof(_SecBuffIn));
  237. memset(_SecBuffOut, 0, sizeof(_SecBuffOut));
  238. memset(&_hCtxt, 0, sizeof(_hCtxt));
  239. // Is this the first time that the digest SSPI package
  240. // is being called for this process.
  241. if (!g_pFuncTbl)
  242. {
  243. // Get the global SSPI dispatch table.
  244. GetFuncTbl();
  245. DIGEST_PKG_DATA PkgData;
  246. SEC_WINNT_AUTH_IDENTITY_EXA SecIdExA;
  247. // Logon with szAppCtx = szUserCtx = NULL.
  248. PkgData.szAppCtx = PkgData.szUserCtx = NULL;
  249. memset(&SecIdExA, 0, sizeof(SEC_WINNT_AUTH_IDENTITY_EXA));
  250. SecIdExA.Version = sizeof(SEC_WINNT_AUTH_IDENTITY_EXA);
  251. SecIdExA.User = (unsigned char*) &PkgData;
  252. SecIdExA.UserLength = sizeof(DIGEST_PKG_DATA);
  253. // Get the global credentials handle.
  254. ssResult = (*(g_pFuncTbl->AcquireCredentialsHandleA))
  255. (NULL, "Digest", SECPKG_CRED_OUTBOUND, NULL, &SecIdExA, NULL, 0, &g_hCred, NULL);
  256. }
  257. }
  258. /*---------------------------------------------------------------------------
  259. DIGEST_CTX::PromptForCreds
  260. ---------------------------------------------------------------------------*/
  261. DWORD DIGEST_CTX::PromptForCreds(HWND hWnd)
  262. {
  263. DEBUG_ENTER ((
  264. DBG_HTTPAUTH,
  265. Dword,
  266. "DIGEST_CTX::PromptForCreds",
  267. "this=%#x hwnd=%#x",
  268. this,
  269. hWnd
  270. ));
  271. SECURITY_STATUS ssResult;
  272. // Prompt for the credentials.
  273. INET_ASSERT(_pvContext);
  274. _cbContext = OUTPUT_BUFFER_LEN;
  275. DWORD sf;
  276. InitSecurityBuffers((LPSTR) _pvContext, _cbContext, &sf, ISC_MODE_UI);
  277. _SecBuffIn[HWND_IDX].pvBuffer = &hWnd;
  278. _SecBuffIn[HWND_IDX].cbBuffer = sizeof(HWND);
  279. ssResult = (*(g_pFuncTbl->InitializeSecurityContextA))(&g_hCred, &_hCtxt, NULL, sf,
  280. 0, 0, &_SecBuffInDesc, 0, &_hCtxt, &_SecBuffOutDesc, NULL, NULL);
  281. _cbContext = _SecBuffOutDesc.pBuffers[0].cbBuffer;
  282. if (ssResult == SEC_E_NO_CREDENTIALS)
  283. {
  284. DEBUG_LEAVE(ERROR_CANCELLED);
  285. return ERROR_CANCELLED;
  286. }
  287. DEBUG_LEAVE((DWORD) ssResult);
  288. return (DWORD) ssResult;
  289. }
  290. /*---------------------------------------------------------------------------
  291. Destructor
  292. ---------------------------------------------------------------------------*/
  293. DIGEST_CTX::~DIGEST_CTX()
  294. {
  295. if (_szAlloc)
  296. delete _szAlloc;
  297. if (_pvContext)
  298. delete _pvContext;
  299. if (_szRequestUri)
  300. delete _szRequestUri;
  301. if (_SecBuffIn[PASS_IDX].pvBuffer)
  302. {
  303. INET_ASSERT(strlen((LPCSTR)_SecBuffIn[PASS_IDX].pvBuffer) == _SecBuffIn[PASS_IDX].cbBuffer);
  304. SecureZeroMemory(_SecBuffIn[PASS_IDX].pvBuffer, _SecBuffIn[PASS_IDX].cbBuffer);
  305. FREE_MEMORY(_SecBuffIn[PASS_IDX].pvBuffer);
  306. }
  307. }
  308. /*---------------------------------------------------------------------------
  309. PreAuthUser
  310. ---------------------------------------------------------------------------*/
  311. DWORD DIGEST_CTX::PreAuthUser(OUT LPSTR pBuff, IN OUT LPDWORD pcbBuff)
  312. {
  313. DEBUG_ENTER ((
  314. DBG_HTTPAUTH,
  315. Dword,
  316. "DIGEST_CTX::PreAuthUser",
  317. "this=%#x pBuf=%#x pcbBuf=%#x {%d}",
  318. this,
  319. pBuff,
  320. pcbBuff,
  321. *pcbBuff
  322. ));
  323. AuthLock();
  324. SECURITY_STATUS ssResult = SEC_E_OK;
  325. INET_ASSERT(_pSPMData == _pPWC->pSPM);
  326. // If a response has been generated copy into output buffer.
  327. if (_cbContext)
  328. {
  329. memcpy(pBuff, _pvContext, _cbContext);
  330. *pcbBuff = _cbContext;
  331. }
  332. // Otherwise attempt to preauthenticate.
  333. else
  334. {
  335. // Call into the SSPI package.
  336. DWORD sf;
  337. InitSecurityBuffers(pBuff, *pcbBuff, &sf, ISC_MODE_PREAUTH);
  338. ssResult = (*(g_pFuncTbl->InitializeSecurityContext))(&g_hCred, NULL, NULL, sf,
  339. 0, 0, &_SecBuffInDesc, 0, &_hCtxt, &_SecBuffOutDesc, NULL, NULL);
  340. *pcbBuff = _SecBuffOut[0].cbBuffer;
  341. }
  342. AuthUnlock();
  343. DEBUG_LEAVE((DWORD) ssResult);
  344. return (DWORD) ssResult;
  345. }
  346. /*---------------------------------------------------------------------------
  347. UpdateFromHeaders
  348. ---------------------------------------------------------------------------*/
  349. DWORD DIGEST_CTX::UpdateFromHeaders(HTTP_REQUEST_HANDLE_OBJECT *pRequest, BOOL fIsProxy)
  350. {
  351. DEBUG_ENTER ((
  352. DBG_HTTPAUTH,
  353. Dword,
  354. "DIGEST_CTX::UpdateFromHeaders",
  355. "this=%#x request=%#x isproxy=%B",
  356. this,
  357. pRequest,
  358. fIsProxy
  359. ));
  360. DWORD dwError, cbExtra, dwAuthIdx;
  361. LPSTR szAuthHeader, szExtra, szScheme;
  362. LPSTR szRealm;
  363. DWORD cbRealm;
  364. AuthLock();
  365. // Get the associated header.
  366. if ((dwError = FindHdrIdxFromScheme(&dwAuthIdx)) != ERROR_SUCCESS)
  367. goto exit;
  368. // If this auth ctx does not have pwc then it has been
  369. // just been constructed in response to a 401.
  370. if (!_pPWC)
  371. {
  372. // Get any realm.
  373. dwError = GetAuthHeaderData(pRequest, fIsProxy, "Realm",
  374. &szRealm, &cbRealm, ALLOCATE_BUFFER, dwAuthIdx);
  375. _pPWC = FindOrCreatePWC(pRequest, fIsProxy, _pSPMData, szRealm);
  376. if (_pPWC)
  377. {
  378. INET_ASSERT(_pPWC->pSPM == _pSPMData);
  379. _pPWC->nLockCount++;
  380. }
  381. else
  382. {
  383. dwError = ERROR_INTERNET_INTERNAL_ERROR;
  384. goto exit;
  385. }
  386. }
  387. // Updating the buffer - delete old one if necessary.
  388. if (_szAlloc)
  389. {
  390. delete _szAlloc;
  391. _szAlloc = _szData = NULL;
  392. _cbData = 0;
  393. }
  394. // Get the entire authentication header.
  395. dwError = GetAuthHeaderData(pRequest, fIsProxy, NULL,
  396. &_szAlloc, &_cbData, ALLOCATE_BUFFER, dwAuthIdx);
  397. // Point just past scheme
  398. _szData = _szAlloc;
  399. while (*_szData != ' ')
  400. {
  401. _szData++;
  402. _cbData--;
  403. }
  404. // The request will be retried.
  405. dwError = ERROR_SUCCESS;
  406. exit:
  407. AuthUnlock();
  408. DEBUG_LEAVE(dwError);
  409. return dwError;
  410. }
  411. /*---------------------------------------------------------------------------
  412. PostAuthUser
  413. ---------------------------------------------------------------------------*/
  414. DWORD DIGEST_CTX::PostAuthUser()
  415. {
  416. DEBUG_ENTER ((
  417. DBG_HTTPAUTH,
  418. Dword,
  419. "DIGEST_CTX::PostAuthUser",
  420. "this=%#x",
  421. this
  422. ));
  423. INET_ASSERT(_pSPMData == _pPWC->pSPM);
  424. AuthLock();
  425. DWORD dwError;
  426. SECURITY_STATUS ssResult;
  427. // Allocate an output buffer if not done so already.
  428. if (!_pvContext)
  429. {
  430. _pvContext = new CHAR[OUTPUT_BUFFER_LEN];
  431. if (!_pvContext)
  432. {
  433. dwError = ERROR_NOT_ENOUGH_MEMORY;
  434. goto exit;
  435. }
  436. }
  437. _cbContext = OUTPUT_BUFFER_LEN;
  438. if (_nRetries++ < MAX_SILENT_RETRIES)
  439. {
  440. // If we pre-authenticated, treat as second
  441. // or subsequent attempt. We depend on the
  442. // server correctly sending stale=FALSE (or no stale)
  443. // if the credentials sent during pre-auth were bad.
  444. // In this case the digest pkg will return SEC_E_NO_CREDENTIALS
  445. // and we will prompt for credentials.
  446. // BUGBUG - Use ApplyControlToken
  447. if (_nRetries == 1 && _pRequest->GetPWC())
  448. {
  449. // Increment num of retries to 2
  450. _nRetries++;
  451. // The dwLower member has to have the correct value
  452. // so that secur32.dll can route to correct provider.
  453. _hCtxt.dwLower = g_hCred.dwLower;
  454. }
  455. // Call into the SSPI package.
  456. DWORD sf;
  457. InitSecurityBuffers((LPSTR) _pvContext, _cbContext, &sf, ISC_MODE_AUTH);
  458. ssResult = (*(g_pFuncTbl->InitializeSecurityContext))
  459. (&g_hCred, (_nRetries == 1 ? NULL : &_hCtxt), NULL, sf,
  460. 0, 0, &_SecBuffInDesc, 0, &_hCtxt, &_SecBuffOutDesc, NULL, NULL);
  461. _cbContext = _SecBuffOutDesc.pBuffers[0].cbBuffer;
  462. switch(ssResult)
  463. {
  464. case SEC_E_OK:
  465. {
  466. dwError = ERROR_INTERNET_FORCE_RETRY;
  467. break;
  468. }
  469. case SEC_E_NO_CREDENTIALS:
  470. {
  471. dwError = ERROR_INTERNET_INCORRECT_PASSWORD;
  472. break;
  473. }
  474. case SEC_E_CONTEXT_EXPIRED:
  475. {
  476. dwError = ERROR_INTERNET_LOGIN_FAILURE_DISPLAY_ENTITY_BODY;
  477. break;
  478. }
  479. default:
  480. dwError = ERROR_INTERNET_LOGIN_FAILURE;
  481. }
  482. }
  483. else
  484. {
  485. _cbContext = 0;
  486. _nRetries = 0;
  487. dwError = ERROR_INTERNET_INCORRECT_PASSWORD;
  488. }
  489. exit:
  490. _pRequest->SetPWC(NULL);
  491. AuthUnlock();
  492. DEBUG_LEAVE(dwError);
  493. return dwError;
  494. }
  495. /*---------------------------------------------------------------------------
  496. Flush creds
  497. ---------------------------------------------------------------------------*/
  498. VOID DIGEST_CTX::FlushCreds()
  499. {
  500. DEBUG_ENTER ((
  501. DBG_HTTPAUTH,
  502. None,
  503. "DIGEST_CTX::FlushCreds",
  504. NULL
  505. ));
  506. DWORD ssResult;
  507. if (g_pFuncTbl)
  508. {
  509. DWORD sf = ISC_REQ_NULL_SESSION;
  510. ssResult = (*(g_pFuncTbl->InitializeSecurityContext))(&g_hCred, NULL, NULL, sf,
  511. 0, 0, NULL, 0, NULL, NULL, NULL, NULL);
  512. }
  513. DEBUG_LEAVE(0);
  514. }
  515. /*---------------------------------------------------------------------------
  516. Logoff
  517. ---------------------------------------------------------------------------*/
  518. VOID DIGEST_CTX::Logoff()
  519. {
  520. DEBUG_ENTER ((
  521. DBG_HTTPAUTH,
  522. None,
  523. "DIGEST_CTX::Logoff",
  524. NULL
  525. ));
  526. DWORD ssResult;
  527. if (g_pFuncTbl)
  528. {
  529. ssResult = (*(g_pFuncTbl->FreeCredentialsHandle))(&g_hCred);
  530. }
  531. DEBUG_LEAVE(0);
  532. }