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.

645 lines
19 KiB

  1. /*++
  2. Copyright (c) 1998 Microsoft Corporation
  3. Module Name:
  4. digest.cxx
  5. Abstract:
  6. Parses http digest challenges and generates http digest
  7. authorization headers for digest sspi package.
  8. Author:
  9. Adriaan Canter (adriaanc) 01-Aug-1998
  10. --*/
  11. #include "include.hxx"
  12. // HTTP related defines
  13. #define HEADER_IDX 0
  14. #define REALM_IDX 1
  15. #define HOST_IDX 2
  16. #define URL_IDX 3
  17. #define METHOD_IDX 4
  18. #define USER_IDX 5
  19. #define PASS_IDX 6
  20. #define NONCE_IDX 7
  21. #define NC_IDX 8
  22. #define HWND_IDX 9
  23. #define NUM_BUFF 10
  24. // POP related defines.
  25. #define POP_USER_IDX 1
  26. #define POP_PASS_IDX 2
  27. #define NUM_EXTENDED_POP_BUFFERS 3
  28. // Used for generating response line.
  29. #define FLAG_QUOTE 0x1
  30. #define FLAG_TERMINATE 0x2
  31. //--------------------------------------------------------------------
  32. // CDigest:: ToHex
  33. //
  34. // Routine Description:
  35. //
  36. // Convert binary data to ASCII hex representation
  37. //
  38. // Arguments:
  39. //
  40. // pSrc - binary data to convert
  41. // cSrc - length of binary data
  42. // pDst - buffer receiving ASCII representation of pSrc
  43. //
  44. //--------------------------------------------------------------------
  45. VOID CDigest::ToHex(LPBYTE pSrc, UINT cSrc, LPSTR pDst)
  46. {
  47. UINT x;
  48. UINT y;
  49. // BUGBUG - character case issue ?
  50. #define TOHEX(a) ((a)>=10 ? 'a'+(a)-10 : '0'+(a))
  51. for ( x = 0, y = 0 ; x < cSrc ; ++x )
  52. {
  53. UINT v;
  54. v = pSrc[x]>>4;
  55. pDst[y++] = TOHEX( v );
  56. v = pSrc[x]&0x0f;
  57. pDst[y++] = TOHEX( v );
  58. }
  59. pDst[y] = '\0';
  60. }
  61. //--------------------------------------------------------------------
  62. // AddDigestHeader
  63. //--------------------------------------------------------------------
  64. BOOL AddDigestHeader(LPSTR szHeader, LPDWORD pcbHeader,
  65. LPSTR szValue, LPSTR szData,
  66. DWORD cbAlloced, DWORD dwFlags)
  67. {
  68. DWORD cbValue, cbData, cbRequired;
  69. cbValue = strlen(szValue);
  70. cbData = strlen(szData);
  71. cbRequired = *pcbHeader
  72. + cbValue + cbData + sizeof('=') + 2 * sizeof('\"') + sizeof(", ") - 1;
  73. if (cbRequired > cbAlloced)
  74. return FALSE;
  75. memcpy(szHeader + *pcbHeader, szValue, cbValue);
  76. (*pcbHeader) += cbValue;
  77. memcpy(szHeader + *pcbHeader, "=", sizeof('='));
  78. (*pcbHeader) += sizeof('=');
  79. if (dwFlags & FLAG_QUOTE)
  80. {
  81. memcpy(szHeader + *pcbHeader, "\"", sizeof('\"'));
  82. (*pcbHeader) += sizeof('\"');
  83. }
  84. memcpy(szHeader + *pcbHeader, szData, cbData);
  85. (*pcbHeader) += cbData;
  86. if (dwFlags & FLAG_QUOTE)
  87. {
  88. memcpy(szHeader + *pcbHeader, "\"", sizeof('\"'));
  89. (*pcbHeader) += sizeof('\"');
  90. }
  91. if (!(dwFlags & FLAG_TERMINATE))
  92. {
  93. memcpy(szHeader + *pcbHeader, ", ", sizeof(", "));
  94. (*pcbHeader) += (sizeof(", ") - 1);
  95. }
  96. else
  97. {
  98. *(szHeader + *pcbHeader) = '\0';
  99. }
  100. return TRUE;
  101. }
  102. //--------------------------------------------------------------------
  103. // CDigest::CDigest()
  104. //--------------------------------------------------------------------
  105. CDigest::CDigest()
  106. {}
  107. //--------------------------------------------------------------------
  108. // CDigest::MakeCNonce
  109. //--------------------------------------------------------------------
  110. LPSTR CDigest::MakeCNonce()
  111. {
  112. DWORD dwRand;
  113. static DWORD dwCounter;
  114. LPSTR szCNonce;
  115. szCNonce = new CHAR[SIZE_MD5_DIGEST+1];
  116. if (!szCNonce)
  117. {
  118. DIGEST_ASSERT(FALSE);
  119. return NULL;
  120. }
  121. dwRand = (GetTickCount() * rand()) + dwCounter++;
  122. MD5_CTX md5ctx;
  123. MD5Init (&md5ctx);
  124. MD5Update(&md5ctx, (LPBYTE) &dwRand, sizeof(DWORD));
  125. MD5Final (&md5ctx);
  126. ToHex(md5ctx.digest, sizeof(md5ctx.digest), szCNonce);
  127. return szCNonce;
  128. }
  129. //--------------------------------------------------------------------
  130. // CDigest::ParseChallenge
  131. //--------------------------------------------------------------------
  132. DWORD CDigest::ParseChallenge(CSess * pSess, PSecBufferDesc pSecBufDesc,
  133. CParams **ppParams, DWORD fContextReq)
  134. {
  135. // SecBufferDesc looks like
  136. //
  137. // [ulversion][cbuffers][pbuffers]
  138. // |
  139. // --------------------------
  140. // |
  141. // |--> [cbBuffer][buffertype][lpvoid][cbBuffer]...
  142. //
  143. DWORD cbQop, cbAlgo, dwError = ERROR_SUCCESS;
  144. CHAR *szQop, *szAlgo, *ptr;
  145. HWND *phWnd;
  146. LPDWORD pcNC;
  147. LPSTR szHeader, szRealm, szHost, szUrl,
  148. szMethod, szUser, szPass, szNonce;
  149. BOOL fPreAuth = FALSE, fCredsSupplied = FALSE;
  150. // Identify buffer components.
  151. // Legacy client.
  152. if (!pSess->fHTTP)
  153. {
  154. szHeader = (LPSTR) pSecBufDesc->pBuffers[HEADER_IDX].pvBuffer;
  155. szRealm = NULL;
  156. szHost = "";
  157. szUrl = "";
  158. szMethod = "AUTHENTICATE";
  159. if (pSecBufDesc->cBuffers == NUM_EXTENDED_POP_BUFFERS)
  160. szUser = (LPSTR) pSecBufDesc->pBuffers[POP_USER_IDX].pvBuffer;
  161. else
  162. szUser = NULL;
  163. if (pSecBufDesc->cBuffers == NUM_EXTENDED_POP_BUFFERS)
  164. szPass = (LPSTR) pSecBufDesc->pBuffers [POP_PASS_IDX].pvBuffer;
  165. else
  166. szPass = NULL;
  167. szNonce = NULL;
  168. phWnd = NULL;
  169. }
  170. // Current client. Leave room for extra
  171. // param (pRequest).
  172. else if (pSess->fHTTP
  173. && (pSecBufDesc->cBuffers == NUM_BUFF
  174. || pSecBufDesc->cBuffers == NUM_BUFF+1))
  175. {
  176. szHeader = (LPSTR) pSecBufDesc->pBuffers[HEADER_IDX].pvBuffer;
  177. szRealm = (LPSTR) pSecBufDesc->pBuffers [REALM_IDX].pvBuffer;
  178. szHost = (LPSTR) pSecBufDesc->pBuffers [HOST_IDX].pvBuffer;
  179. szUrl = (LPSTR) pSecBufDesc->pBuffers [URL_IDX].pvBuffer;
  180. szMethod = (LPSTR) pSecBufDesc->pBuffers[METHOD_IDX].pvBuffer;
  181. szUser = (LPSTR) pSecBufDesc->pBuffers [USER_IDX].pvBuffer;
  182. szPass = (LPSTR) pSecBufDesc->pBuffers [PASS_IDX].pvBuffer;
  183. szNonce = (LPSTR) pSecBufDesc->pBuffers [NONCE_IDX].pvBuffer;
  184. pcNC =(DWORD*) pSecBufDesc->pBuffers [ NC_IDX].pvBuffer;
  185. phWnd = (HWND*) pSecBufDesc->pBuffers [HWND_IDX].pvBuffer;
  186. }
  187. else
  188. {
  189. dwError = ERROR_INVALID_PARAMETER;
  190. goto exit;
  191. }
  192. // Validate parameters.
  193. dwError = ERROR_INVALID_PARAMETER;
  194. if (szHost && szUrl && szMethod)
  195. {
  196. // Auth or UI prompt to challenge for any user.
  197. if (szHeader && !szRealm && !szUser && !szPass && !szNonce)
  198. dwError = ERROR_SUCCESS;
  199. // Auth or UI prompt to challenge for particular user.
  200. else if (szHeader && !szRealm && szUser && !szPass && !szNonce)
  201. dwError = ERROR_SUCCESS;
  202. // Auth to challenge with creds supplied.
  203. else if (szHeader && !szRealm && szUser
  204. && szPass && !szNonce && (fContextReq & ISC_REQ_USE_SUPPLIED_CREDS))
  205. {
  206. fCredsSupplied = TRUE;
  207. dwError = ERROR_SUCCESS;
  208. }
  209. // Preauth with realm supplied for any user.
  210. else if (!szHeader && szRealm && !szUser && !szPass && !szNonce)
  211. {
  212. fPreAuth = TRUE;
  213. dwError = ERROR_SUCCESS;
  214. }
  215. // Preauth with realm supplied for a particular user.
  216. else if (!szHeader && szRealm && szUser && !szPass && !szNonce)
  217. {
  218. fPreAuth = TRUE;
  219. dwError = ERROR_SUCCESS;
  220. }
  221. // Preauth with realm and creds supplied.
  222. else if (!szHeader && szRealm && szUser &&
  223. szPass && szNonce && pcNC && (fContextReq & ISC_REQ_USE_SUPPLIED_CREDS))
  224. {
  225. fPreAuth = TRUE;
  226. fCredsSupplied = TRUE;
  227. dwError = ERROR_SUCCESS;
  228. }
  229. }
  230. // Fail if buffers did not fall into one
  231. // of the acceptable formats.
  232. if (dwError != ERROR_SUCCESS)
  233. goto exit;
  234. // Construct the params object.
  235. *ppParams = new CParams(szHeader);
  236. if (!*ppParams)
  237. {
  238. DIGEST_ASSERT(FALSE);
  239. dwError = ERROR_NOT_ENOUGH_MEMORY;
  240. goto exit;
  241. }
  242. // Flag if this is preauth and/or if
  243. // credentials are supplied.
  244. (*ppParams)->SetPreAuth(fPreAuth);
  245. (*ppParams)->SetCredsSupplied(fCredsSupplied);
  246. // Only set algorithm if auth to challenge or UI prompting.
  247. if (!(*ppParams)->IsPreAuth())
  248. {
  249. // Algorithm should be MD5 or MD5-sess, default to MD5 if not specified.
  250. if (!(*ppParams)->GetParam(CParams::ALGORITHM, &szAlgo, &cbAlgo))
  251. {
  252. (*ppParams)->SetParam(CParams::ALGORITHM, "MD5", sizeof("MD5") - 1);
  253. (*ppParams)->SetMd5Sess(FALSE);
  254. }
  255. else if (szAlgo && !lstrcmpi(szAlgo, "Md5-sess"))
  256. {
  257. (*ppParams)->SetMd5Sess(TRUE);
  258. }
  259. else if (szAlgo && !lstrcmpi(szAlgo, "MD5"))
  260. {
  261. (*ppParams)->SetMd5Sess(FALSE);
  262. }
  263. else
  264. {
  265. // Not md5 or md5-sess
  266. // DIGEST_ASSERT(FALSE);
  267. dwError = ERROR_INVALID_PARAMETER;
  268. goto exit;
  269. }
  270. }
  271. // If this is an imap/pop client we require
  272. // md5-sess specified in the challenge.
  273. if (!pSess->fHTTP && !(*ppParams)->IsMd5Sess())
  274. {
  275. DIGEST_ASSERT(FALSE);
  276. dwError = ERROR_INVALID_PARAMETER;
  277. goto exit;
  278. }
  279. // Always set host, url, method (required). User and pass optional.
  280. if (! (*ppParams)->SetParam(CParams::HOST, szHost, szHost ? strlen(szHost) : 0)
  281. || !(*ppParams)->SetParam(CParams::URL, szUrl, szUrl ? strlen(szUrl) : 0)
  282. || !(*ppParams)->SetParam(CParams::METHOD, szMethod, szMethod ? strlen(szMethod) : 0)
  283. || !(*ppParams)->SetParam(CParams::USER, szUser, szUser ? strlen(szUser) : 0)
  284. || !(*ppParams)->SetParam(CParams::PASS, szPass, szPass ? strlen(szPass) : 0))
  285. {
  286. DIGEST_ASSERT(FALSE);
  287. dwError = ERROR_NOT_ENOUGH_MEMORY;
  288. goto exit;
  289. }
  290. // BUGBUG - do cleanup locally on failure.
  291. // If not preauthenticating we are authenticating in response
  292. // to a challenge or prompting for UI.
  293. if (!(*ppParams)->IsPreAuth())
  294. {
  295. // Check to see that qop=auth is specified
  296. (*ppParams)->GetParam(CParams::QOP, &szQop, &cbQop);
  297. if (!(szQop && (*ppParams)->FindToken(szQop, cbQop+1, AUTH_SZ, AUTH_LEN, NULL)))
  298. {
  299. DIGEST_ASSERT(FALSE);
  300. dwError = ERROR_INVALID_PARAMETER;
  301. goto exit;
  302. }
  303. // Save off any hWnd for UI (only needed for challenges).
  304. if (fContextReq & ISC_REQ_PROMPT_FOR_CREDS)
  305. {
  306. (*ppParams)->SetHwnd(phWnd);
  307. }
  308. }
  309. // Otherwise we are attempting to preauthenticate.
  310. else
  311. {
  312. // Set the realm for preauth.
  313. if (!(*ppParams)->SetParam(CParams::REALM, szRealm, strlen(szRealm)))
  314. {
  315. DIGEST_ASSERT(FALSE);
  316. dwError = ERROR_INVALID_PARAMETER;
  317. goto exit;
  318. }
  319. // Also set the nonce if preauth + use supplied creds.
  320. if (fContextReq & ISC_REQ_USE_SUPPLIED_CREDS)
  321. {
  322. (*ppParams)->SetNC(pcNC);
  323. if (!(*ppParams)->SetParam(CParams::NONCE, szNonce, strlen(szNonce)))
  324. {
  325. DIGEST_ASSERT(FALSE);
  326. dwError = ERROR_INVALID_PARAMETER;
  327. goto exit;
  328. }
  329. }
  330. }
  331. exit:
  332. return dwError;
  333. }
  334. //--------------------------------------------------------------------
  335. // CDigest::GenerateResponse
  336. //--------------------------------------------------------------------
  337. DWORD CDigest::GenerateResponse(CSess *pSess, CParams *pParams,
  338. CCredInfo *pInfo, PSecBufferDesc pSecBufDesc)
  339. {
  340. // bugbug - psz's on these.
  341. LPSTR szBuf, szMethod, szUrl, szNonce, szCNonce, szCNonceSess, szOpaque;
  342. DWORD *pcbBuf, cbMethod, cbUrl, cbNonce, cbCNonce, cbCNonceSess, cbOpaque;
  343. DWORD dwError, cbAlloced;
  344. BOOL fSess = FALSE;
  345. szBuf = (LPSTR) pSecBufDesc->pBuffers[0].pvBuffer;
  346. pcbBuf = &(pSecBufDesc->pBuffers[0].cbBuffer);
  347. cbAlloced = *pcbBuf;
  348. *pcbBuf = 0;
  349. szCNonce = NULL;
  350. if (!cbAlloced)
  351. {
  352. // Modern clients better pass in
  353. // the size of the output buffer.
  354. if (pSess->fHTTP)
  355. {
  356. DIGEST_ASSERT(FALSE);
  357. dwError = ERROR_INVALID_PARAMETER;
  358. goto quit;
  359. }
  360. else
  361. // Legacy clients like OE don't, so
  362. // we allow up to 64k.
  363. cbAlloced = PACKAGE_MAXTOKEN;
  364. }
  365. CHAR szA1[SIZE_MD5_DIGEST + 1],
  366. szA2[SIZE_MD5_DIGEST + 1],
  367. szH [SIZE_MD5_DIGEST + 1];
  368. MD5_CTX md5a1, md5a2, md5h;
  369. // Get method and request-uri.
  370. pParams->GetParam(CParams::METHOD, &szMethod, &cbMethod);
  371. if (pSess->fHTTP)
  372. pParams->GetParam(CParams::URL, &szUrl, &cbUrl);
  373. else
  374. {
  375. // request-uri is empty string for legacy clients.
  376. szUrl = "";
  377. cbUrl = 0;
  378. }
  379. // Must have both method and request-uri.
  380. if (!szMethod || !szUrl)
  381. {
  382. DIGEST_ASSERT(FALSE);
  383. dwError = ERROR_INVALID_PARAMETER;
  384. goto quit;
  385. }
  386. // Opaque is optional.
  387. pParams->GetParam(CParams::_OPAQUE, &szOpaque, &cbOpaque);
  388. // Unless we are preauthenticating it is possible that
  389. // the credential does not have a nonce value if the
  390. // credential was established via ApplyControlToken.
  391. // In this case we use the nonce received in the challenge.
  392. if (pInfo->szNonce)
  393. {
  394. szNonce = pInfo->szNonce;
  395. cbNonce = strlen(szNonce);
  396. }
  397. else if (!pParams->GetParam(CParams::NONCE, &szNonce, &cbNonce))
  398. {
  399. DIGEST_ASSERT(FALSE);
  400. dwError = ERROR_INVALID_PARAMETER;
  401. goto quit;
  402. }
  403. // Existance of a client nonce in the credential
  404. // implies md5-sess. Otherwise we need to create one.
  405. if (pInfo->szCNonce)
  406. {
  407. szCNonceSess = pInfo->szCNonce;
  408. cbCNonceSess = SIZE_MD5_DIGEST;
  409. if (pInfo->cCount == 1)
  410. szCNonce = pInfo->szCNonce;
  411. else
  412. szCNonce = MakeCNonce();
  413. cbCNonce = SIZE_MD5_DIGEST;
  414. fSess = TRUE;
  415. }
  416. // No client nonce means we simply
  417. // generate one now.
  418. else
  419. {
  420. szCNonce = MakeCNonce();
  421. cbCNonce = SIZE_MD5_DIGEST;
  422. szCNonceSess = NULL;
  423. cbCNonceSess = 0;
  424. fSess = FALSE;
  425. }
  426. // Encode nonce count.
  427. // BUGBUG - wsprintf returns strlen
  428. // and are any cruntime deps.
  429. CHAR szNC[16];
  430. DWORD cbNC;
  431. wsprintf(szNC, "%08x", pInfo->cCount);
  432. cbNC = strlen(szNC);
  433. // 1) Md5(user:realm:pass) or
  434. // Md5(Md5(user:realm:pass):nonce:cnoncesess)
  435. MD5Init (&md5a1);
  436. MD5Update(&md5a1, (LPBYTE) pInfo->szUser, strlen(pInfo->szUser));
  437. MD5Update(&md5a1, (LPBYTE) ":", 1);
  438. MD5Update(&md5a1, (LPBYTE) pInfo->szRealm, strlen(pInfo->szRealm));
  439. MD5Update(&md5a1, (LPBYTE) ":", 1);
  440. MD5Update(&md5a1, (LPBYTE) pInfo->szPass, strlen(pInfo->szPass));
  441. if (fSess)
  442. {
  443. // Md5(Md5(user:realm:pass):nonce:cnoncesess)
  444. MD5Final (&md5a1);
  445. ToHex(md5a1.digest, sizeof(md5a1.digest), szA1);
  446. MD5Init (&md5a1);
  447. MD5Update(&md5a1, (LPBYTE) szA1, SIZE_MD5_DIGEST);
  448. MD5Update(&md5a1, (LPBYTE) ":", 1);
  449. MD5Update(&md5a1, (LPBYTE) szNonce, cbNonce);
  450. MD5Update(&md5a1, (LPBYTE) ":", 1);
  451. MD5Update(&md5a1, (LPBYTE) szCNonceSess, cbCNonceSess);
  452. }
  453. MD5Final (&md5a1);
  454. ToHex(md5a1.digest, sizeof(md5a1.digest), szA1);
  455. // 2) Md5(method:url)
  456. MD5Init (&md5a2);
  457. MD5Update(&md5a2, (LPBYTE) szMethod, cbMethod);
  458. MD5Update(&md5a2, (LPBYTE) ":", 1);
  459. MD5Update(&md5a2, (LPBYTE) szUrl, cbUrl);
  460. MD5Final (&md5a2);
  461. ToHex(md5a2.digest, sizeof(md5a2.digest), szA2);
  462. // 3) Md5(A1:nonce:nc:cnonce:qop:A2)
  463. MD5Init (&md5h);
  464. MD5Update(&md5h, (LPBYTE) szA1, SIZE_MD5_DIGEST);
  465. MD5Update(&md5h, (LPBYTE) ":", 1);
  466. MD5Update(&md5h, (LPBYTE) szNonce, cbNonce);
  467. MD5Update(&md5h, (LPBYTE) ":", 1);
  468. MD5Update(&md5h, (LPBYTE) szNC, cbNC);
  469. MD5Update(&md5h, (LPBYTE) ":", 1);
  470. MD5Update(&md5h, (LPBYTE) szCNonce, cbCNonce);
  471. MD5Update(&md5h, (LPBYTE) ":", 1);
  472. MD5Update(&md5h, (LPBYTE) AUTH_SZ, AUTH_LEN);
  473. MD5Update(&md5h, (LPBYTE) ":", 1);
  474. MD5Update(&md5h, (LPBYTE) szA2, SIZE_MD5_DIGEST);
  475. MD5Final (&md5h);
  476. ToHex(md5h.digest, sizeof(md5h.digest), szH);
  477. // http digest.
  478. if (pSess->fHTTP)
  479. {
  480. if ( AddDigestHeader(szBuf, pcbBuf, "Digest username",
  481. pInfo->szUser, cbAlloced, FLAG_QUOTE)
  482. && AddDigestHeader(szBuf, pcbBuf, "realm",
  483. pInfo->szRealm, cbAlloced, FLAG_QUOTE)
  484. && AddDigestHeader(szBuf, pcbBuf, "qop",
  485. "auth", cbAlloced, FLAG_QUOTE)
  486. && AddDigestHeader(szBuf, pcbBuf,
  487. "algorithm", (pInfo->szCNonce ? "MD5-sess" : "MD5"), cbAlloced, FLAG_QUOTE)
  488. && AddDigestHeader(szBuf, pcbBuf, "uri", szUrl, cbAlloced, FLAG_QUOTE)
  489. && AddDigestHeader(szBuf, pcbBuf, "nonce", szNonce, cbAlloced, FLAG_QUOTE)
  490. && AddDigestHeader(szBuf, pcbBuf, "nc", szNC, cbAlloced, 0)
  491. && AddDigestHeader(szBuf, pcbBuf, "cnonce", szCNonce, cbAlloced, FLAG_QUOTE)
  492. && (!szOpaque || AddDigestHeader(szBuf, pcbBuf, "opaque", szOpaque, cbAlloced, FLAG_QUOTE))
  493. && AddDigestHeader(szBuf, pcbBuf,
  494. "response", szH, cbAlloced, FLAG_QUOTE | FLAG_TERMINATE)
  495. )
  496. {
  497. dwError = ERROR_SUCCESS;
  498. }
  499. else
  500. {
  501. dwError = ERROR_NOT_ENOUGH_MEMORY;
  502. *pcbBuf = 0;
  503. }
  504. }
  505. else
  506. {
  507. if ( AddDigestHeader(szBuf, pcbBuf, "Digest username",
  508. pInfo->szUser, cbAlloced, FLAG_QUOTE)
  509. && AddDigestHeader(szBuf, pcbBuf, "realm",
  510. pInfo->szRealm, cbAlloced, FLAG_QUOTE)
  511. && AddDigestHeader(szBuf, pcbBuf, "qop",
  512. "auth", cbAlloced, FLAG_QUOTE)
  513. && AddDigestHeader(szBuf, pcbBuf,
  514. "algorithm", (pInfo->szCNonce ? "MD5-sess" : "MD5"), cbAlloced, FLAG_QUOTE)
  515. && AddDigestHeader(szBuf, pcbBuf, "nonce", szNonce, cbAlloced, FLAG_QUOTE)
  516. && AddDigestHeader(szBuf, pcbBuf, "cnonce", szCNonce, cbAlloced, FLAG_QUOTE)
  517. && AddDigestHeader(szBuf, pcbBuf,
  518. "response", szH, cbAlloced, FLAG_QUOTE | FLAG_TERMINATE)
  519. )
  520. {
  521. dwError = ERROR_SUCCESS;
  522. }
  523. else
  524. {
  525. dwError = ERROR_NOT_ENOUGH_MEMORY;
  526. *pcbBuf = 0;
  527. }
  528. }
  529. quit:
  530. if (szCNonce && szCNonce != pInfo->szCNonce)
  531. delete szCNonce;
  532. return dwError;
  533. }