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