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