]> git.ipfire.org Git - thirdparty/squid.git/blame - src/auth/digest/auth_digest.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / auth / digest / auth_digest.cc
CommitLineData
2d70df72 1/*
262a0e14 2 * $Id$
2d70df72 3 *
4 * DEBUG: section 29 Authenticator
5 * AUTHOR: Robert Collins
6 *
7 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
8 * ----------------------------------------------------------
9 *
10 * Squid is the result of efforts by numerous individuals from the
11 * Internet community. Development is led by Duane Wessels of the
12 * National Laboratory for Applied Network Research and funded by the
13 * National Science Foundation. Squid is Copyrighted (C) 1998 by
14 * the Regents of the University of California. Please see the
15 * COPYRIGHT file for full details. Squid incorporates software
16 * developed and/or copyrighted by other sources. Please see the
17 * CREDITS file for full details.
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
26ac0430 23 *
2d70df72 24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
26ac0430 28 *
2d70df72 29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
32 *
33 */
34
35/* The functions in this file handle authentication.
36 * They DO NOT perform access control or auditing.
37 * See acl.c for access control and client_side.c for auditing */
38
582c2af2 39#include "squid.h"
2d70df72 40#include "rfc2617.h"
5817ee13 41#include "auth/digest/auth_digest.h"
616cfc4c 42#include "auth/digest/Scheme.h"
aa110616 43#include "auth/digest/User.h"
616cfc4c 44#include "auth/digest/UserRequest.h"
3ad63615 45#include "auth/Gadgets.h"
aa110616 46#include "auth/State.h"
25f98340 47#include "base64.h"
6a90c2d1 48#include "base/StringArea.h"
a553a5a3 49#include "event.h"
8822ebee 50#include "mgr/Registration.h"
e6ccf245 51#include "Store.h"
a2ac85d9 52#include "HttpRequest.h"
924f73bc 53#include "HttpReply.h"
582c2af2 54#include "protos.h"
d295d770 55#include "wordlist.h"
cc192b50 56#include "SquidTime.h"
c78aa667 57
2d70df72 58/* Digest Scheme */
59
2d70df72 60static AUTHSSTATS authenticateDigestStats;
2d70df72 61
928f3421 62helper *digestauthenticators = NULL;
2d70df72 63
64static hash_table *digest_nonce_cache;
65
2d70df72 66static int authdigest_initialised = 0;
a3efa961 67static MemAllocator *digest_nonce_pool = NULL;
2d70df72 68
9abd1514 69enum http_digest_attr_type {
d6b7a3c4 70 DIGEST_USERNAME,
cb14509d
HN
71 DIGEST_REALM,
72 DIGEST_QOP,
73 DIGEST_ALGORITHM,
74 DIGEST_URI,
75 DIGEST_NONCE,
76 DIGEST_NC,
77 DIGEST_CNONCE,
78 DIGEST_RESPONSE,
79 DIGEST_ENUM_END
80};
81
82static const HttpHeaderFieldAttrs DigestAttrs[DIGEST_ENUM_END] = {
83 {"username", (http_hdr_type)DIGEST_USERNAME},
84 {"realm", (http_hdr_type)DIGEST_REALM},
85 {"qop", (http_hdr_type)DIGEST_QOP},
86 {"algorithm", (http_hdr_type)DIGEST_ALGORITHM},
87 {"uri", (http_hdr_type)DIGEST_URI},
88 {"nonce", (http_hdr_type)DIGEST_NONCE},
89 {"nc", (http_hdr_type)DIGEST_NC},
90 {"cnonce", (http_hdr_type)DIGEST_CNONCE},
91 {"response", (http_hdr_type)DIGEST_RESPONSE},
92};
93
94static HttpHeaderFieldInfo *DigestFieldsInfo = NULL;
95
2d70df72 96/*
97 *
98 * Nonce Functions
99 *
100 */
101
102static void authenticateDigestNonceCacheCleanup(void *data);
103static digest_nonce_h *authenticateDigestNonceFindNonce(const char *nonceb64);
c193c972 104static digest_nonce_h *authenticateDigestNonceNew(void);
c78aa667 105static void authenticateDigestNonceDelete(digest_nonce_h * nonce);
c193c972 106static void authenticateDigestNonceSetup(void);
c78aa667 107static int authDigestNonceIsStale(digest_nonce_h * nonce);
108static void authDigestNonceEncode(digest_nonce_h * nonce);
c78aa667 109static void authDigestNonceLink(digest_nonce_h * nonce);
c78aa667 110#if NOT_USED
111static int authDigestNonceLinks(digest_nonce_h * nonce);
112#endif
113static void authDigestNonceUserUnlink(digest_nonce_h * nonce);
2d70df72 114
c78aa667 115static void
2d70df72 116authDigestNonceEncode(digest_nonce_h * nonce)
117{
118 if (!nonce)
62e76326 119 return;
120
4a8b20e8 121 if (nonce->key)
62e76326 122 xfree(nonce->key);
123
4a8b20e8 124 nonce->key = xstrdup(base64_encode_bin((char *) &(nonce->noncedata), sizeof(digest_nonce_data)));
2d70df72 125}
126
c78aa667 127static digest_nonce_h *
c193c972 128authenticateDigestNonceNew(void)
2d70df72 129{
b001e822 130 digest_nonce_h *newnonce = static_cast < digest_nonce_h * >(digest_nonce_pool->alloc());
2d70df72 131 digest_nonce_h *temp;
132
62e76326 133 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
134 * === EXCERPT FROM RFC 2617 ===
135 * The contents of the nonce are implementation dependent. The quality
136 * of the implementation depends on a good choice. A nonce might, for
137 * example, be constructed as the base 64 encoding of
26ac0430 138 *
62e76326 139 * time-stamp H(time-stamp ":" ETag ":" private-key)
26ac0430 140 *
62e76326 141 * where time-stamp is a server-generated time or other non-repeating
142 * value, ETag is the value of the HTTP ETag header associated with
143 * the requested entity, and private-key is data known only to the
144 * server. With a nonce of this form a server would recalculate the
145 * hash portion after receiving the client authentication header and
146 * reject the request if it did not match the nonce from that header
147 * or if the time-stamp value is not recent enough. In this way the
148 * server can limit the time of the nonce's validity. The inclusion of
149 * the ETag prevents a replay request for an updated version of the
150 * resource. (Note: including the IP address of the client in the
151 * nonce would appear to offer the server the ability to limit the
152 * reuse of the nonce to the same client that originally got it.
153 * However, that would break proxy farms, where requests from a single
154 * user often go through different proxies in the farm. Also, IP
155 * address spoofing is not that hard.)
156 * ====
26ac0430 157 *
62e76326 158 * Now for my reasoning:
159 * We will not accept a unrecognised nonce->we have all recognisable
82b045dc 160 * nonces stored. If we send out unique base64 encodings we guarantee
62e76326 161 * that a given nonce applies to only one user (barring attacks or
162 * really bad timing with expiry and creation). Using a random
163 * component in the nonce allows us to loop to find a unique nonce.
164 * We use H(nonce_data) so the nonce is meaningless to the reciever.
165 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
166 * And even if our randomness is not very random (probably due to
167 * bad coding on my part) we don't really care - the timestamp and
82b045dc 168 * memory pointer also guarantee local uniqueness in the input to the hash
169 * function.
62e76326 170 */
2d70df72 171
172 /* create a new nonce */
173 newnonce->nc = 0;
174 newnonce->flags.valid = 1;
175 newnonce->noncedata.self = newnonce;
176 newnonce->noncedata.creationtime = current_time.tv_sec;
b600a19d 177 newnonce->noncedata.randomdata = squid_random();
2d70df72 178
179 authDigestNonceEncode(newnonce);
74830fc8 180 /*
181 * loop until we get a unique nonce. The nonce creation must
182 * have a random factor
183 */
62e76326 184
2f44bd34 185 while ((temp = authenticateDigestNonceFindNonce((char const *) (newnonce->key)))) {
62e76326 186 /* create a new nonce */
187 newnonce->noncedata.randomdata = squid_random();
58171e19 188 /* Bug 3526 high performance fix: add 1 second to creationtime to avoid duplication */
742a021b 189 ++newnonce->noncedata.creationtime;
62e76326 190 authDigestNonceEncode(newnonce);
2d70df72 191 }
62e76326 192
4a8b20e8 193 hash_join(digest_nonce_cache, newnonce);
2d70df72 194 /* the cache's link */
195 authDigestNonceLink(newnonce);
196 newnonce->flags.incache = 1;
4a7a3d56 197 debugs(29, 5, "authenticateDigestNonceNew: created nonce " << newnonce << " at " << newnonce->noncedata.creationtime);
2d70df72 198 return newnonce;
199}
200
c78aa667 201static void
2d70df72 202authenticateDigestNonceDelete(digest_nonce_h * nonce)
203{
204 if (nonce) {
62e76326 205 assert(nonce->references == 0);
2d70df72 206#if UNREACHABLECODE
62e76326 207
208 if (nonce->flags.incache)
209 hash_remove_link(digest_nonce_cache, nonce);
210
2d70df72 211#endif
62e76326 212
213 assert(nonce->flags.incache == 0);
214
215 safe_free(nonce->key);
216
dc47f531 217 digest_nonce_pool->freeOne(nonce);
2d70df72 218 }
219}
220
c78aa667 221static void
c193c972 222authenticateDigestNonceSetup(void)
2d70df72 223{
224 if (!digest_nonce_pool)
04eb0689 225 digest_nonce_pool = memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h));
62e76326 226
2d70df72 227 if (!digest_nonce_cache) {
30abd221 228 digest_nonce_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
62e76326 229 assert(digest_nonce_cache);
372fccd6 230 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->nonceGCInterval, 1);
2d70df72 231 }
232}
233
d6374be6 234void
c193c972 235authenticateDigestNonceShutdown(void)
2d70df72 236{
62e76326 237 /*
2d70df72 238 * We empty the cache of any nonces left in there.
239 */
240 digest_nonce_h *nonce;
62e76326 241
2d70df72 242 if (digest_nonce_cache) {
bf8fe701 243 debugs(29, 2, "authenticateDigestNonceShutdown: Shutting down nonce cache ");
62e76326 244 hash_first(digest_nonce_cache);
245
246 while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) {
247 assert(nonce->flags.incache);
248 authDigestNoncePurge(nonce);
249 }
2d70df72 250 }
62e76326 251
2f44bd34 252#if DEBUGSHUTDOWN
2d70df72 253 if (digest_nonce_pool) {
b001e822 254 delete digest_nonce_pool;
255 digest_nonce_pool = NULL;
2d70df72 256 }
62e76326 257
2f44bd34 258#endif
bf8fe701 259 debugs(29, 2, "authenticateDigestNonceShutdown: Nonce cache shutdown");
2d70df72 260}
261
c78aa667 262static void
2d70df72 263authenticateDigestNonceCacheCleanup(void *data)
264{
265 /*
74830fc8 266 * We walk the hash by nonceb64 as that is the unique key we
267 * use. For big hash tables we could consider stepping through
268 * the cache, 100/200 entries at a time. Lets see how it flies
269 * first.
2d70df72 270 */
271 digest_nonce_h *nonce;
bf8fe701 272 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now");
4a7a3d56 273 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Current time: " << current_time.tv_sec);
2d70df72 274 hash_first(digest_nonce_cache);
62e76326 275
2d70df72 276 while ((nonce = ((digest_nonce_h *) hash_next(digest_nonce_cache)))) {
bf8fe701 277 debugs(29, 3, "authenticateDigestNonceCacheCleanup: nonce entry : " << nonce << " '" << (char *) nonce->key << "'");
4a7a3d56 278 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Creation time: " << nonce->noncedata.creationtime);
62e76326 279
280 if (authDigestNonceIsStale(nonce)) {
bf8fe701 281 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Removing nonce " << (char *) nonce->key << " from cache due to timeout.");
62e76326 282 assert(nonce->flags.incache);
283 /* invalidate nonce so future requests fail */
284 nonce->flags.valid = 0;
285 /* if it is tied to a auth_user, remove the tie */
286 authDigestNonceUserUnlink(nonce);
287 authDigestNoncePurge(nonce);
288 }
2d70df72 289 }
62e76326 290
bf8fe701 291 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.");
62e76326 292
372fccd6
AJ
293 if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->active())
294 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->nonceGCInterval, 1);
2d70df72 295}
296
c78aa667 297static void
2d70df72 298authDigestNonceLink(digest_nonce_h * nonce)
299{
300 assert(nonce != NULL);
742a021b 301 ++nonce->references;
bf8fe701 302 debugs(29, 9, "authDigestNonceLink: nonce '" << nonce << "' now at '" << nonce->references << "'.");
2d70df72 303}
304
c78aa667 305#if NOT_USED
306static int
2d70df72 307authDigestNonceLinks(digest_nonce_h * nonce)
308{
309 if (!nonce)
62e76326 310 return -1;
311
2d70df72 312 return nonce->references;
313}
62e76326 314
c78aa667 315#endif
2d70df72 316
928f3421 317void
2d70df72 318authDigestNonceUnlink(digest_nonce_h * nonce)
319{
320 assert(nonce != NULL);
62e76326 321
2d70df72 322 if (nonce->references > 0) {
a2f5277a 323 -- nonce->references;
2d70df72 324 } else {
e0236918 325 debugs(29, DBG_IMPORTANT, "authDigestNonceUnlink; Attempt to lower nonce " << nonce << " refcount below 0!");
2d70df72 326 }
62e76326 327
bf8fe701 328 debugs(29, 9, "authDigestNonceUnlink: nonce '" << nonce << "' now at '" << nonce->references << "'.");
62e76326 329
2d70df72 330 if (nonce->references == 0)
62e76326 331 authenticateDigestNonceDelete(nonce);
2d70df72 332}
333
928f3421
AJ
334const char *
335authenticateDigestNonceNonceb64(const digest_nonce_h * nonce)
2d70df72 336{
337 if (!nonce)
62e76326 338 return NULL;
339
2f44bd34 340 return (char const *) nonce->key;
2d70df72 341}
342
c78aa667 343static digest_nonce_h *
2d70df72 344authenticateDigestNonceFindNonce(const char *nonceb64)
345{
346 digest_nonce_h *nonce = NULL;
62e76326 347
2d70df72 348 if (nonceb64 == NULL)
62e76326 349 return NULL;
350
26ac0430 351 debugs(29, 9, "authDigestNonceFindNonce:looking for nonceb64 '" << nonceb64 << "' in the nonce cache.");
62e76326 352
2f44bd34 353 nonce = static_cast < digest_nonce_h * >(hash_lookup(digest_nonce_cache, nonceb64));
62e76326 354
e6ccf245 355 if ((nonce == NULL) || (strcmp(authenticateDigestNonceNonceb64(nonce), nonceb64)))
62e76326 356 return NULL;
357
bf8fe701 358 debugs(29, 9, "authDigestNonceFindNonce: Found nonce '" << nonce << "'");
62e76326 359
2d70df72 360 return nonce;
361}
362
928f3421 363int
2d70df72 364authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9])
365{
d205783b 366 unsigned long intnc;
2d70df72 367 /* do we have a nonce ? */
62e76326 368
2d70df72 369 if (!nonce)
62e76326 370 return 0;
371
d205783b 372 intnc = strtol(nc, NULL, 16);
62e76326 373
f5292c64 374 /* has it already been invalidated ? */
375 if (!nonce->flags.valid) {
bf8fe701 376 debugs(29, 4, "authDigestNonceIsValid: Nonce already invalidated");
f5292c64 377 return 0;
378 }
379
380 /* is the nonce-count ok ? */
372fccd6 381 if (!static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->CheckNonceCount) {
742a021b 382 ++nonce->nc;
f5292c64 383 return -1; /* forced OK by configuration */
384 }
385
372fccd6 386 if ((static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->NonceStrictness && intnc != nonce->nc + 1) ||
62e76326 387 intnc < nonce->nc + 1) {
bf8fe701 388 debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match");
62e76326 389 nonce->flags.valid = 0;
390 return 0;
2d70df72 391 }
62e76326 392
2d70df72 393 /* seems ok */
d205783b 394 /* increment the nonce count - we've already checked that intnc is a
395 * valid representation for us, so we don't need the test here.
396 */
397 nonce->nc = intnc;
62e76326 398
2d70df72 399 return -1;
400}
401
c78aa667 402static int
2d70df72 403authDigestNonceIsStale(digest_nonce_h * nonce)
404{
405 /* do we have a nonce ? */
62e76326 406
2d70df72 407 if (!nonce)
62e76326 408 return -1;
409
2d70df72 410 /* has it's max duration expired? */
372fccd6 411 if (nonce->noncedata.creationtime + static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxduration < current_time.tv_sec) {
bf8fe701 412 debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " <<
4a7a3d56 413 nonce->noncedata.creationtime << " " <<
372fccd6 414 static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxduration << " " <<
4a7a3d56 415 current_time.tv_sec);
bf8fe701 416
62e76326 417 nonce->flags.valid = 0;
418 return -1;
2d70df72 419 }
62e76326 420
2d70df72 421 if (nonce->nc > 99999998) {
bf8fe701 422 debugs(29, 4, "authDigestNonceIsStale: Nonce count overflow");
62e76326 423 nonce->flags.valid = 0;
424 return -1;
2d70df72 425 }
62e76326 426
372fccd6 427 if (nonce->nc > static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxuses) {
bf8fe701 428 debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit");
62e76326 429 nonce->flags.valid = 0;
430 return -1;
2d70df72 431 }
62e76326 432
2d70df72 433 /* seems ok */
434 return 0;
435}
436
928f3421
AJ
437/**
438 * \retval 0 the digest is not stale yet
439 * \retval -1 the digest will be stale on the next request
440 */
1dc746da 441int
2d70df72 442authDigestNonceLastRequest(digest_nonce_h * nonce)
443{
444 if (!nonce)
62e76326 445 return -1;
446
2d70df72 447 if (nonce->nc == 99999997) {
bf8fe701 448 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to overflow");
62e76326 449 return -1;
2d70df72 450 }
62e76326 451
372fccd6 452 if (nonce->nc >= static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->noncemaxuses - 1) {
bf8fe701 453 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit");
62e76326 454 return -1;
2d70df72 455 }
62e76326 456
2d70df72 457 /* and other tests are possible. */
458 return 0;
459}
460
aa110616 461void
2d70df72 462authDigestNoncePurge(digest_nonce_h * nonce)
463{
464 if (!nonce)
62e76326 465 return;
466
2d70df72 467 if (!nonce->flags.incache)
62e76326 468 return;
469
4a8b20e8 470 hash_remove_link(digest_nonce_cache, nonce);
62e76326 471
2d70df72 472 nonce->flags.incache = 0;
62e76326 473
2d70df72 474 /* the cache's link */
475 authDigestNonceUnlink(nonce);
476}
477
62e76326 478/* USER related functions */
d87154ee 479static Auth::User::Pointer
2d70df72 480authDigestUserFindUsername(const char *username)
481{
e1f7507e 482 AuthUserHashPointer *usernamehash;
e1f7507e 483 debugs(29, 9, HERE << "Looking for user '" << username << "'");
62e76326 484
f51dcaf7 485 if (username && (usernamehash = static_cast < AuthUserHashPointer * >(hash_lookup(proxy_auth_username_cache, username)))) {
616cfc4c 486 while ((usernamehash->user()->auth_type != Auth::AUTH_DIGEST) && (usernamehash->next))
56a49fda 487 usernamehash = static_cast<AuthUserHashPointer *>(usernamehash->next);
62e76326 488
616cfc4c 489 if (usernamehash->user()->auth_type == Auth::AUTH_DIGEST) {
56a49fda 490 return usernamehash->user();
62e76326 491 }
2d70df72 492 }
62e76326 493
2d70df72 494 return NULL;
495}
496
0bcb6908 497void
372fccd6 498Auth::Digest::Config::rotateHelpers()
0bcb6908
AJ
499{
500 /* schedule closure of existing helpers */
501 if (digestauthenticators) {
502 helperShutdown(digestauthenticators);
503 }
504
505 /* NP: dynamic helper restart will ensure they start up again as needed. */
506}
507
f5691f9c 508void
372fccd6 509Auth::Digest::Config::dump(StoreEntry * entry, const char *name, Auth::Config * scheme)
2d70df72 510{
58ee2093 511 wordlist *list = authenticateProgram;
bf8fe701 512 debugs(29, 9, "authDigestCfgDump: Dumping configuration");
2d70df72 513 storeAppendPrintf(entry, "%s %s", name, "digest");
62e76326 514
2d70df72 515 while (list != NULL) {
62e76326 516 storeAppendPrintf(entry, " %s", list->key);
517 list = list->next;
2d70df72 518 }
62e76326 519
404cfda1 520 storeAppendPrintf(entry, "\n%s %s realm %s\n%s %s children %d startup=%d idle=%d concurrency=%d\n%s %s nonce_max_count %d\n%s %s nonce_max_duration %d seconds\n%s %s nonce_garbage_interval %d seconds\n",
f5691f9c 521 name, "digest", digestAuthRealm,
404cfda1 522 name, "digest", authenticateChildren.n_max, authenticateChildren.n_startup, authenticateChildren.n_idle, authenticateChildren.concurrency,
f5691f9c 523 name, "digest", noncemaxuses,
524 name, "digest", (int) noncemaxduration,
525 name, "digest", (int) nonceGCInterval);
2d70df72 526}
527
f5691f9c 528bool
372fccd6 529Auth::Digest::Config::active() const
2d70df72 530{
f5691f9c 531 return authdigest_initialised == 1;
2d70df72 532}
62e76326 533
f5691f9c 534bool
372fccd6 535Auth::Digest::Config::configured() const
2d70df72 536{
58ee2093 537 if ((authenticateProgram != NULL) &&
48d54e4d 538 (authenticateChildren.n_max != 0) &&
f5691f9c 539 (digestAuthRealm != NULL) && (noncemaxduration > -1))
540 return true;
62e76326 541
f5691f9c 542 return false;
2d70df72 543}
544
2d70df72 545/* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
546void
c7baff40 547Auth::Digest::Config::fixHeader(Auth::UserRequest::Pointer auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
2d70df72 548{
58ee2093 549 if (!authenticateProgram)
82b045dc 550 return;
551
a598b010 552 int stale = 0;
62e76326 553
a33a428a 554 if (auth_user_request != NULL) {
c7baff40 555 Auth::Digest::UserRequest *digest_request = dynamic_cast<Auth::Digest::UserRequest*>(auth_user_request.getRaw());
7da7b01f 556 assert (digest_request != NULL);
62e76326 557
9f709824 558 stale = !digest_request->flags.invalid_password;
2d70df72 559 }
82b045dc 560
561 /* on a 407 or 401 we always use a new nonce */
562 digest_nonce_h *nonce = authenticateDigestNonceNew();
563
c7baff40 564 debugs(29, 9, HERE << "Sending type:" << hdrType <<
bf8fe701 565 " header: 'Digest realm=\"" << digestAuthRealm << "\", nonce=\"" <<
566 authenticateDigestNonceNonceb64(nonce) << "\", qop=\"" << QOP_AUTH <<
567 "\", stale=" << (stale ? "true" : "false"));
82b045dc 568
569 /* in the future, for WWW auth we may want to support the domain entry */
18ec8500 570 httpHeaderPutStrf(&rep->header, hdrType, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestAuthRealm, authenticateDigestNonceNonceb64(nonce), QOP_AUTH, stale ? "true" : "false");
2d70df72 571}
572
2d70df72 573/* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
574 * config file */
f5691f9c 575void
372fccd6 576Auth::Digest::Config::init(Auth::Config * scheme)
2d70df72 577{
58ee2093 578 if (authenticateProgram) {
6d97f5f1 579 DigestFieldsInfo = httpHeaderBuildFieldsInfo(DigestAttrs, DIGEST_ENUM_END);
62e76326 580 authenticateDigestNonceSetup();
581 authdigest_initialised = 1;
582
583 if (digestauthenticators == NULL)
48d54e4d 584 digestauthenticators = new helper("digestauthenticator");
62e76326 585
58ee2093 586 digestauthenticators->cmdline = authenticateProgram;
62e76326 587
1af735c7 588 digestauthenticators->childs.updateLimits(authenticateChildren);
62e76326 589
590 digestauthenticators->ipc_type = IPC_STREAM;
591
592 helperOpenServers(digestauthenticators);
2d70df72 593 }
594}
595
62ee09ca 596void
372fccd6 597Auth::Digest::Config::registerWithCacheManager(void)
62ee09ca 598{
8822ebee 599 Mgr::RegisterAction("digestauthenticator",
d9fc6862
A
600 "Digest User Authenticator Stats",
601 authenticateDigestStats, 0, 1);
62ee09ca 602}
2d70df72 603
604/* free any allocated configuration details */
605void
372fccd6 606Auth::Digest::Config::done()
2d70df72 607{
d6374be6
AJ
608 authdigest_initialised = 0;
609
610 if (digestauthenticators)
611 helperShutdown(digestauthenticators);
612
613 if (DigestFieldsInfo) {
614 httpHeaderDestroyFieldsInfo(DigestFieldsInfo, DIGEST_ENUM_END);
615 DigestFieldsInfo = NULL;
616 }
c6cf8dee 617
d6374be6
AJ
618 if (!shutting_down)
619 return;
620
621 delete digestauthenticators;
622 digestauthenticators = NULL;
623
58ee2093
AJ
624 if (authenticateProgram)
625 wordlistDestroy(&authenticateProgram);
62e76326 626
f5691f9c 627 safe_free(digestAuthRealm);
628}
62e76326 629
d13b829b
AJ
630Auth::Digest::Config::Config() :
631 digestAuthRealm(NULL),
632 nonceGCInterval(5*60),
633 noncemaxduration(30*60),
634 noncemaxuses(50),
635 NonceStrictness(0),
636 CheckNonceCount(1),
637 PostWorkaround(0),
638 utf8(0)
639{}
2d70df72 640
f5691f9c 641void
372fccd6 642Auth::Digest::Config::parse(Auth::Config * scheme, int n_configured, char *param_str)
2d70df72 643{
2d70df72 644 if (strcasecmp(param_str, "program") == 0) {
58ee2093
AJ
645 if (authenticateProgram)
646 wordlistDestroy(&authenticateProgram);
62e76326 647
58ee2093 648 parse_wordlist(&authenticateProgram);
62e76326 649
58ee2093 650 requirePathnameExists("auth_param digest program", authenticateProgram->key);
2d70df72 651 } else if (strcasecmp(param_str, "children") == 0) {
48d54e4d 652 authenticateChildren.parseConfig();
2d70df72 653 } else if (strcasecmp(param_str, "realm") == 0) {
f5691f9c 654 parse_eol(&digestAuthRealm);
2d70df72 655 } else if (strcasecmp(param_str, "nonce_garbage_interval") == 0) {
f5691f9c 656 parse_time_t(&nonceGCInterval);
2d70df72 657 } else if (strcasecmp(param_str, "nonce_max_duration") == 0) {
f5691f9c 658 parse_time_t(&noncemaxduration);
2d70df72 659 } else if (strcasecmp(param_str, "nonce_max_count") == 0) {
f5691f9c 660 parse_int((int *) &noncemaxuses);
d205783b 661 } else if (strcasecmp(param_str, "nonce_strictness") == 0) {
f5691f9c 662 parse_onoff(&NonceStrictness);
f5292c64 663 } else if (strcasecmp(param_str, "check_nonce_count") == 0) {
f5691f9c 664 parse_onoff(&CheckNonceCount);
f5292c64 665 } else if (strcasecmp(param_str, "post_workaround") == 0) {
f5691f9c 666 parse_onoff(&PostWorkaround);
f741d2f6
HN
667 } else if (strcasecmp(param_str, "utf8") == 0) {
668 parse_onoff(&utf8);
2d70df72 669 } else {
fa84c01d 670 debugs(29, DBG_CRITICAL, "unrecognised digest auth scheme parameter '" << param_str << "'");
2d70df72 671 }
672}
673
f5691f9c 674const char *
372fccd6 675Auth::Digest::Config::type() const
f5691f9c 676{
d6374be6 677 return Auth::Digest::Scheme::GetInstance()->type();
f5691f9c 678}
679
2d70df72 680static void
681authenticateDigestStats(StoreEntry * sentry)
682{
9522b380 683 helperStats(sentry, digestauthenticators, "Digest Authenticator Statistics");
2d70df72 684}
685
686/* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
687
c78aa667 688static void
2d70df72 689authDigestNonceUserUnlink(digest_nonce_h * nonce)
690{
aa110616 691 Auth::Digest::User *digest_user;
2d70df72 692 dlink_node *link, *tmplink;
62e76326 693
2d70df72 694 if (!nonce)
62e76326 695 return;
696
f5691f9c 697 if (!nonce->user)
62e76326 698 return;
699
f5691f9c 700 digest_user = nonce->user;
62e76326 701
702 /* unlink from the user list. Yes we're crossing structures but this is the only
2d70df72 703 * time this code is needed
704 */
705 link = digest_user->nonces.head;
62e76326 706
2d70df72 707 while (link) {
62e76326 708 tmplink = link;
709 link = link->next;
710
711 if (tmplink->data == nonce) {
712 dlinkDelete(tmplink, &digest_user->nonces);
713 authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data));
714 dlinkNodeDelete(tmplink);
715 link = NULL;
716 }
2d70df72 717 }
62e76326 718
f5691f9c 719 /* this reference to user was not locked because freeeing the user frees
26ac0430 720 * the nonce too.
2d70df72 721 */
f5691f9c 722 nonce->user = NULL;
2d70df72 723}
724
725/* authDigestUserLinkNonce: add a nonce to a given user's struct */
c78aa667 726static void
aa110616 727authDigestUserLinkNonce(Auth::Digest::User * user, digest_nonce_h * nonce)
2d70df72 728{
729 dlink_node *node;
62e76326 730
f5691f9c 731 if (!user || !nonce)
62e76326 732 return;
733
aa110616 734 Auth::Digest::User *digest_user = user;
62e76326 735
2d70df72 736 node = digest_user->nonces.head;
62e76326 737
2d70df72 738 while (node && (node->data != nonce))
62e76326 739 node = node->next;
740
2d70df72 741 if (node)
62e76326 742 return;
743
2d70df72 744 node = dlinkNodeNew();
62e76326 745
2d70df72 746 dlinkAddTail(nonce, node, &digest_user->nonces);
62e76326 747
2d70df72 748 authDigestNonceLink(nonce);
62e76326 749
2d70df72 750 /* ping this nonce to this auth user */
4e9d4067 751 assert((nonce->user == NULL) || (nonce->user == user));
62e76326 752
f5691f9c 753 /* we don't lock this reference because removing the user removes the
2d70df72 754 * hash too. Of course if that changes we're stuffed so read the code huh?
755 */
f5691f9c 756 nonce->user = user;
2d70df72 757}
758
759/* setup the necessary info to log the username */
c7baff40
AJ
760static Auth::UserRequest::Pointer
761authDigestLogUsername(char *username, Auth::UserRequest::Pointer auth_user_request)
2d70df72 762{
f5691f9c 763 assert(auth_user_request != NULL);
2d70df72 764
765 /* log the username */
bf8fe701 766 debugs(29, 9, "authDigestLogUsername: Creating new user for logging '" << username << "'");
aa110616 767 Auth::User::Pointer digest_user = new Auth::Digest::User(static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest")));
2d70df72 768 /* save the credentials */
f5691f9c 769 digest_user->username(username);
2d70df72 770 /* set the auth_user type */
616cfc4c 771 digest_user->auth_type = Auth::AUTH_BROKEN;
2d70df72 772 /* link the request to the user */
f5691f9c 773 auth_user_request->user(digest_user);
f5691f9c 774 return auth_user_request;
2d70df72 775}
776
777/*
778 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
779 * Auth_user structure.
780 */
c7baff40 781Auth::UserRequest::Pointer
372fccd6 782Auth::Digest::Config::decode(char const *proxy_auth)
2d70df72 783{
2d70df72 784 const char *item;
785 const char *p;
786 const char *pos = NULL;
787 char *username = NULL;
788 digest_nonce_h *nonce;
789 int ilen;
2d70df72 790
bf8fe701 791 debugs(29, 9, "authenticateDigestDecodeAuth: beginning");
2d70df72 792
c7baff40 793 Auth::Digest::UserRequest *digest_request = new Auth::Digest::UserRequest();
2d70df72 794
795 /* trim DIGEST from string */
62e76326 796
ba53f4b8 797 while (xisgraph(*proxy_auth))
742a021b 798 ++proxy_auth;
2d70df72 799
800 /* Trim leading whitespace before decoding */
801 while (xisspace(*proxy_auth))
742a021b 802 ++proxy_auth;
2d70df72 803
30abd221 804 String temp(proxy_auth);
62e76326 805
2d70df72 806 while (strListGetItem(&temp, ',', &item, &ilen, &pos)) {
df604ac0 807 /* isolate directive name & value */
6d97f5f1 808 size_t nlen;
a0133f10 809 size_t vlen;
6d97f5f1 810 if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) {
f207fe64
FC
811 nlen = p - item;
812 ++p;
a0133f10 813 vlen = ilen - (p - item);
df604ac0 814 } else {
6d97f5f1 815 nlen = ilen;
a0133f10
A
816 vlen = 0;
817 }
9abd1514 818
6a90c2d1 819 StringArea keyName(item, nlen-1);
df604ac0 820 String value;
6a90c2d1 821
a0133f10 822 if (vlen > 0) {
6a90c2d1
AJ
823 // see RFC 2617 section 3.2.1 and 3.2.2 for details on the BNF
824
825 if (keyName == StringArea("domain",6) || keyName == StringArea("uri",3)) {
826 // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"'
827 // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain
828 if (*p == '"' && *(p + vlen -1) == '"') {
829 value.limitInit(p+1, vlen-2);
830 }
831 } else if (keyName == StringArea("qop",3)) {
832 // qop is more special.
833 // On request this must not be quoted-string de-quoted. But is several values wrapped in '"'
834 // On response this is a single un-quoted token.
835 if (*p == '"' && *(p + vlen -1) == '"') {
836 value.limitInit(p+1, vlen-2);
837 } else {
838 value.limitInit(p, vlen);
839 }
840 } else if (*p == '"') {
34460e19 841 if (!httpHeaderParseQuotedString(p, vlen, &value)) {
c7baff40 842 debugs(29, 9, HERE << "Failed to parse attribute '" << item << "' in '" << temp << "'");
a0133f10
A
843 continue;
844 }
845 } else {
846 value.limitInit(p, vlen);
847 }
848 } else {
c7baff40 849 debugs(29, 9, HERE << "Failed to parse attribute '" << item << "' in '" << temp << "'");
6d97f5f1
A
850 continue;
851 }
9abd1514 852
6d97f5f1
A
853 /* find type */
854 http_digest_attr_type type = (http_digest_attr_type)httpHeaderIdByName(item, nlen, DigestFieldsInfo, DIGEST_ENUM_END);
9abd1514 855
6d97f5f1
A
856 switch (type) {
857 case DIGEST_USERNAME:
bbe0ed86 858 safe_free(username);
9abd1514 859 username = xstrndup(value.rawBuf(), value.size() + 1);
c7baff40 860 debugs(29, 9, HERE << "Found Username '" << username << "'");
6d97f5f1 861 break;
62e76326 862
6d97f5f1 863 case DIGEST_REALM:
bbe0ed86 864 safe_free(digest_request->realm);
9abd1514 865 digest_request->realm = xstrndup(value.rawBuf(), value.size() + 1);
c7baff40 866 debugs(29, 9, HERE << "Found realm '" << digest_request->realm << "'");
6d97f5f1 867 break;
62e76326 868
6d97f5f1 869 case DIGEST_QOP:
bbe0ed86 870 safe_free(digest_request->qop);
9abd1514 871 digest_request->qop = xstrndup(value.rawBuf(), value.size() + 1);
c7baff40 872 debugs(29, 9, HERE << "Found qop '" << digest_request->qop << "'");
6d97f5f1 873 break;
62e76326 874
6d97f5f1 875 case DIGEST_ALGORITHM:
bbe0ed86 876 safe_free(digest_request->algorithm);
9abd1514 877 digest_request->algorithm = xstrndup(value.rawBuf(), value.size() + 1);
c7baff40 878 debugs(29, 9, HERE << "Found algorithm '" << digest_request->algorithm << "'");
6d97f5f1 879 break;
62e76326 880
6d97f5f1 881 case DIGEST_URI:
bbe0ed86 882 safe_free(digest_request->uri);
9abd1514 883 digest_request->uri = xstrndup(value.rawBuf(), value.size() + 1);
c7baff40 884 debugs(29, 9, HERE << "Found uri '" << digest_request->uri << "'");
6d97f5f1 885 break;
62e76326 886
6d97f5f1 887 case DIGEST_NONCE:
bbe0ed86 888 safe_free(digest_request->nonceb64);
9abd1514 889 digest_request->nonceb64 = xstrndup(value.rawBuf(), value.size() + 1);
c7baff40 890 debugs(29, 9, HERE << "Found nonce '" << digest_request->nonceb64 << "'");
6d97f5f1 891 break;
62e76326 892
6d97f5f1
A
893 case DIGEST_NC:
894 if (value.size() != 8) {
c7baff40 895 debugs(29, 9, HERE << "Invalid nc '" << value << "' in '" << temp << "'");
6d97f5f1 896 }
9abd1514 897 xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1);
c7baff40 898 debugs(29, 9, HERE << "Found noncecount '" << digest_request->nc << "'");
6d97f5f1 899 break;
62e76326 900
6d97f5f1 901 case DIGEST_CNONCE:
bbe0ed86 902 safe_free(digest_request->cnonce);
9abd1514 903 digest_request->cnonce = xstrndup(value.rawBuf(), value.size() + 1);
c7baff40 904 debugs(29, 9, HERE << "Found cnonce '" << digest_request->cnonce << "'");
6d97f5f1 905 break;
62e76326 906
6d97f5f1 907 case DIGEST_RESPONSE:
bbe0ed86 908 safe_free(digest_request->response);
9abd1514 909 digest_request->response = xstrndup(value.rawBuf(), value.size() + 1);
c7baff40 910 debugs(29, 9, HERE << "Found response '" << digest_request->response << "'");
6d97f5f1 911 break;
9abd1514 912
6d97f5f1 913 default:
c7baff40 914 debugs(29, 3, HERE << "Unknown attribute '" << item << "' in '" << temp << "'");
0e134176 915 break;
62e76326 916 }
2d70df72 917 }
62e76326 918
30abd221 919 temp.clean();
2d70df72 920
2d70df72 921 /* now we validate the data given to us */
922
74830fc8 923 /*
924 * TODO: on invalid parameters we should return 400, not 407.
925 * Find some clean way of doing this. perhaps return a valid
926 * struct, and set the direction to clientwards combined with
927 * a change to the clientwards handling code (ie let the
928 * clientwards call set the error type (but limited to known
929 * correct values - 400/401/407
930 */
2d70df72 931
59a98343 932 /* 2069 requirements */
62e76326 933
59a98343
HN
934 /* do we have a username ? */
935 if (!username || username[0] == '\0') {
c7baff40 936 debugs(29, 2, HERE << "Empty or not present username");
9f709824 937 return authDigestLogUsername(username, digest_request);
2d70df72 938 }
62e76326 939
920d1c9d
HN
940 /* Sanity check of the username.
941 * " can not be allowed in usernames until * the digest helper protocol
942 * have been redone
943 */
944 if (strchr(username, '"')) {
c7baff40 945 debugs(29, 2, HERE << "Unacceptable username '" << username << "'");
920d1c9d 946 return authDigestLogUsername(username, digest_request);
2d70df72 947 }
62e76326 948
59a98343
HN
949 /* do we have a realm ? */
950 if (!digest_request->realm || digest_request->realm[0] == '\0') {
c7baff40 951 debugs(29, 2, HERE << "Empty or not present realm");
9f709824 952 return authDigestLogUsername(username, digest_request);
2d70df72 953 }
62e76326 954
59a98343
HN
955 /* and a nonce? */
956 if (!digest_request->nonceb64 || digest_request->nonceb64[0] == '\0') {
c7baff40 957 debugs(29, 2, HERE << "Empty or not present nonce");
9f709824 958 return authDigestLogUsername(username, digest_request);
2d70df72 959 }
62e76326 960
74830fc8 961 /* we can't check the URI just yet. We'll check it in the
6649f955 962 * authenticate phase, but needs to be given */
59a98343 963 if (!digest_request->uri || digest_request->uri[0] == '\0') {
c7baff40 964 debugs(29, 2, HERE << "Missing URI field");
6649f955
HN
965 return authDigestLogUsername(username, digest_request);
966 }
2d70df72 967
968 /* is the response the correct length? */
2d70df72 969 if (!digest_request->response || strlen(digest_request->response) != 32) {
c7baff40 970 debugs(29, 2, HERE << "Response length invalid");
9f709824 971 return authDigestLogUsername(username, digest_request);
2d70df72 972 }
62e76326 973
59a98343
HN
974 /* check the algorithm is present and supported */
975 if (!digest_request->algorithm)
976 digest_request->algorithm = xstrndup("MD5", 4);
977 else if (strcmp(digest_request->algorithm, "MD5")
978 && strcmp(digest_request->algorithm, "MD5-sess")) {
c7baff40 979 debugs(29, 2, HERE << "invalid algorithm specified!");
9f709824 980 return authDigestLogUsername(username, digest_request);
2d70df72 981 }
62e76326 982
59a98343
HN
983 /* 2617 requirements, indicated by qop */
984 if (digest_request->qop) {
985
6d97f5f1
A
986 /* check the qop is what we expected. */
987 if (strcmp(digest_request->qop, QOP_AUTH) != 0) {
988 /* we received a qop option we didn't send */
c7baff40 989 debugs(29, 2, HERE << "Invalid qop option received");
6d97f5f1
A
990 return authDigestLogUsername(username, digest_request);
991 }
992
993 /* check cnonce */
994 if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') {
c7baff40 995 debugs(29, 2, HERE << "Missing cnonce field");
6d97f5f1
A
996 return authDigestLogUsername(username, digest_request);
997 }
998
999 /* check nc */
1000 if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) {
c7baff40 1001 debugs(29, 2, HERE << "invalid nonce count");
6d97f5f1
A
1002 return authDigestLogUsername(username, digest_request);
1003 }
59a98343 1004 } else {
6d97f5f1
A
1005 /* cnonce and nc both require qop */
1006 if (digest_request->cnonce || digest_request->nc) {
c7baff40 1007 debugs(29, 2, HERE << "missing qop!");
6d97f5f1
A
1008 return authDigestLogUsername(username, digest_request);
1009 }
2d70df72 1010 }
62e76326 1011
59a98343
HN
1012 /** below nonce state dependent **/
1013
1014 /* now the nonce */
1015 nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64);
1016 if (!nonce) {
1017 /* we couldn't find a matching nonce! */
c7baff40 1018 debugs(29, 2, HERE << "Unexpected or invalid nonce received");
52b3c6e8 1019 if (digest_request->user() != NULL)
d87154ee 1020 digest_request->user()->credentials(Auth::Failed);
9f709824 1021 return authDigestLogUsername(username, digest_request);
2d70df72 1022 }
62e76326 1023
59a98343
HN
1024 digest_request->nonce = nonce;
1025 authDigestNonceLink(nonce);
1026
1027 /* check that we're not being hacked / the username hasn't changed */
1028 if (nonce->user && strcmp(username, nonce->user->username())) {
c7baff40 1029 debugs(29, 2, HERE << "Username for the nonce does not equal the username for the request");
9f709824 1030 return authDigestLogUsername(username, digest_request);
2d70df72 1031 }
62e76326 1032
2d70df72 1033 /* the method we'll check at the authenticate step as well */
1034
2d70df72 1035 /* we don't send or parse opaques. Ok so we're flexable ... */
1036
1037 /* find the user */
aa110616 1038 Auth::Digest::User *digest_user;
f5691f9c 1039
d87154ee 1040 Auth::User::Pointer auth_user;
2d70df72 1041
1042 if ((auth_user = authDigestUserFindUsername(username)) == NULL) {
62e76326 1043 /* the user doesn't exist in the username cache yet */
c7baff40 1044 debugs(29, 9, HERE << "Creating new digest user '" << username << "'");
aa110616 1045 digest_user = new Auth::Digest::User(this);
f5691f9c 1046 /* auth_user is a parent */
1047 auth_user = digest_user;
62e76326 1048 /* save the username */
f5691f9c 1049 digest_user->username(username);
62e76326 1050 /* set the user type */
616cfc4c 1051 digest_user->auth_type = Auth::AUTH_DIGEST;
62e76326 1052 /* this auth_user struct is the one to get added to the
1053 * username cache */
1054 /* store user in hash's */
f5691f9c 1055 digest_user->addToNameCache();
df80d445 1056
62e76326 1057 /*
1058 * Add the digest to the user so we can tell if a hacking
1059 * or spoofing attack is taking place. We do this by assuming
1060 * the user agent won't change user name without warning.
1061 */
f5691f9c 1062 authDigestUserLinkNonce(digest_user, nonce);
2d70df72 1063 } else {
c7baff40 1064 debugs(29, 9, HERE << "Found user '" << username << "' in the user cache as '" << auth_user << "'");
aa110616 1065 digest_user = static_cast<Auth::Digest::User *>(auth_user.getRaw());
156c3aae 1066 digest_user->credentials(Auth::Unchecked);
62e76326 1067 xfree(username);
2d70df72 1068 }
62e76326 1069
2d70df72 1070 /*link the request and the user */
f5691f9c 1071 assert(digest_request != NULL);
82b045dc 1072
f5691f9c 1073 digest_request->user(digest_user);
c7baff40 1074 debugs(29, 9, HERE << "username = '" << digest_user->username() << "'\nrealm = '" <<
bf8fe701 1075 digest_request->realm << "'\nqop = '" << digest_request->qop <<
1076 "'\nalgorithm = '" << digest_request->algorithm << "'\nuri = '" <<
1077 digest_request->uri << "'\nnonce = '" << digest_request->nonceb64 <<
1078 "'\nnc = '" << digest_request->nc << "'\ncnonce = '" <<
1079 digest_request->cnonce << "'\nresponse = '" <<
1080 digest_request->response << "'\ndigestnonce = '" << nonce << "'");
2d70df72 1081
f5691f9c 1082 return digest_request;
2d70df72 1083}