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