2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 29 Authenticator */
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 */
16 #include "auth/CredentialsCache.h"
17 #include "auth/digest/Config.h"
18 #include "auth/digest/Scheme.h"
19 #include "auth/digest/User.h"
20 #include "auth/digest/UserRequest.h"
21 #include "auth/Gadgets.h"
22 #include "auth/State.h"
23 #include "auth/toUtf.h"
24 #include "base/LookupTable.h"
25 #include "base/Random.h"
29 #include "HttpHeaderTools.h"
30 #include "HttpReply.h"
31 #include "HttpRequest.h"
33 #include "mgr/Registration.h"
35 #include "sbuf/SBuf.h"
36 #include "sbuf/StringConvert.h"
41 /* digest_nonce_h still uses explicit alloc()/freeOne() MemPool calls.
42 * XXX: convert to MEMPROXY_CLASS() API
44 #include "mem/Allocator.h"
47 static AUTHSSTATS authenticateDigestStats
;
49 Helper::ClientPointer digestauthenticators
;
51 static hash_table
*digest_nonce_cache
;
53 static int authdigest_initialised
= 0;
54 static Mem::Allocator
*digest_nonce_pool
= nullptr;
56 enum http_digest_attr_type
{
69 static const LookupTable
<http_digest_attr_type
>::Record
71 {"username", DIGEST_USERNAME
},
72 {"realm", DIGEST_REALM
},
74 {"algorithm", DIGEST_ALGORITHM
},
76 {"nonce", DIGEST_NONCE
},
78 {"cnonce", DIGEST_CNONCE
},
79 {"response", DIGEST_RESPONSE
},
80 {nullptr, DIGEST_INVALID_ATTR
}
83 LookupTable
<http_digest_attr_type
>
84 DigestFieldsLookupTable(DIGEST_INVALID_ATTR
, DigestAttrs
);
92 static void authenticateDigestNonceCacheCleanup(void *data
);
93 static digest_nonce_h
*authenticateDigestNonceFindNonce(const char *noncehex
);
94 static void authenticateDigestNonceDelete(digest_nonce_h
* nonce
);
95 static void authenticateDigestNonceSetup(void);
96 static void authDigestNonceEncode(digest_nonce_h
* nonce
);
97 static void authDigestNonceLink(digest_nonce_h
* nonce
);
98 static void authDigestNonceUserUnlink(digest_nonce_h
* nonce
);
101 authDigestNonceEncode(digest_nonce_h
* nonce
)
111 SquidMD5Init(&Md5Ctx
);
112 SquidMD5Update(&Md5Ctx
, reinterpret_cast<const uint8_t *>(&nonce
->noncedata
), sizeof(nonce
->noncedata
));
113 SquidMD5Final(reinterpret_cast<uint8_t *>(H
), &Md5Ctx
);
115 nonce
->key
= xcalloc(sizeof(HASHHEX
), 1);
116 CvtHex(H
, static_cast<char *>(nonce
->key
));
120 authenticateDigestNonceNew(void)
122 digest_nonce_h
*newnonce
= static_cast < digest_nonce_h
* >(digest_nonce_pool
->alloc());
124 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
125 * === EXCERPT FROM RFC 2617 ===
126 * The contents of the nonce are implementation dependent. The quality
127 * of the implementation depends on a good choice. A nonce might, for
128 * example, be constructed as the base 64 encoding of
130 * time-stamp H(time-stamp ":" ETag ":" private-key)
132 * where time-stamp is a server-generated time or other non-repeating
133 * value, ETag is the value of the HTTP ETag header associated with
134 * the requested entity, and private-key is data known only to the
135 * server. With a nonce of this form a server would recalculate the
136 * hash portion after receiving the client authentication header and
137 * reject the request if it did not match the nonce from that header
138 * or if the time-stamp value is not recent enough. In this way the
139 * server can limit the time of the nonce's validity. The inclusion of
140 * the ETag prevents a replay request for an updated version of the
141 * resource. (Note: including the IP address of the client in the
142 * nonce would appear to offer the server the ability to limit the
143 * reuse of the nonce to the same client that originally got it.
144 * However, that would break proxy farms, where requests from a single
145 * user often go through different proxies in the farm. Also, IP
146 * address spoofing is not that hard.)
149 * Now for my reasoning:
150 * We will not accept a unrecognised nonce->we have all recognisable
151 * nonces stored. If we send out unique encodings we guarantee
152 * that a given nonce applies to only one user (barring attacks or
153 * really bad timing with expiry and creation). Using a random
154 * component in the nonce allows us to loop to find a unique nonce.
155 * We use H(nonce_data) so the nonce is meaningless to the receiver.
156 * So our nonce looks like hex(H(timestamp,randomdata))
157 * And even if our randomness is not very random we don't really care
158 * - the timestamp also guarantees local uniqueness in the input to
161 static std::mt19937
mt(RandomSeed32());
162 static std::uniform_int_distribution
<uint32_t> newRandomData
;
164 /* create a new nonce */
166 newnonce
->flags
.valid
= true;
167 newnonce
->noncedata
.creationtime
= current_time
.tv_sec
;
168 newnonce
->noncedata
.randomdata
= newRandomData(mt
);
170 authDigestNonceEncode(newnonce
);
172 // ensure temporal uniqueness by checking for existing nonce
173 while (authenticateDigestNonceFindNonce((char const *) (newnonce
->key
))) {
174 /* create a new nonce */
175 newnonce
->noncedata
.randomdata
= newRandomData(mt
);
176 authDigestNonceEncode(newnonce
);
179 hash_join(digest_nonce_cache
, newnonce
);
180 /* the cache's link */
181 authDigestNonceLink(newnonce
);
182 newnonce
->flags
.incache
= true;
183 debugs(29, 5, "created nonce " << newnonce
<< " at " << newnonce
->noncedata
.creationtime
);
188 authenticateDigestNonceDelete(digest_nonce_h
* nonce
)
191 assert(nonce
->references
== 0);
192 assert(!nonce
->flags
.incache
);
194 safe_free(nonce
->key
);
196 digest_nonce_pool
->freeOne(nonce
);
201 authenticateDigestNonceSetup(void)
203 if (!digest_nonce_pool
)
204 digest_nonce_pool
= memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h
));
206 if (!digest_nonce_cache
) {
207 digest_nonce_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
208 assert(digest_nonce_cache
);
209 eventAdd("Digest nonce cache maintenance", authenticateDigestNonceCacheCleanup
, nullptr, static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->nonceGCInterval
, 1);
214 authenticateDigestNonceShutdown(void)
217 * We empty the cache of any nonces left in there.
219 digest_nonce_h
*nonce
;
221 if (digest_nonce_cache
) {
222 debugs(29, 2, "Shutting down nonce cache");
223 hash_first(digest_nonce_cache
);
225 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
226 assert(nonce
->flags
.incache
);
227 authDigestNoncePurge(nonce
);
231 debugs(29, 2, "Nonce cache shutdown");
235 authenticateDigestNonceCacheCleanup(void *)
238 * We walk the hash by noncehex as that is the unique key we
239 * use. For big hash tables we could consider stepping through
240 * the cache, 100/200 entries at a time. Lets see how it flies
243 digest_nonce_h
*nonce
;
244 debugs(29, 3, "Cleaning the nonce cache now");
245 debugs(29, 3, "Current time: " << current_time
.tv_sec
);
246 hash_first(digest_nonce_cache
);
248 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
249 debugs(29, 3, "nonce entry : " << nonce
<< " '" << (char *) nonce
->key
<< "'");
250 debugs(29, 4, "Creation time: " << nonce
->noncedata
.creationtime
);
252 if (authDigestNonceIsStale(nonce
)) {
253 debugs(29, 4, "Removing nonce " << (char *) nonce
->key
<< " from cache due to timeout.");
254 assert(nonce
->flags
.incache
);
255 /* invalidate nonce so future requests fail */
256 nonce
->flags
.valid
= false;
257 /* if it is tied to a auth_user, remove the tie */
258 authDigestNonceUserUnlink(nonce
);
259 authDigestNoncePurge(nonce
);
263 debugs(29, 3, "Finished cleaning the nonce cache.");
265 if (static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->active())
266 eventAdd("Digest nonce cache maintenance", authenticateDigestNonceCacheCleanup
, nullptr, static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->nonceGCInterval
, 1);
270 authDigestNonceLink(digest_nonce_h
* nonce
)
272 assert(nonce
!= nullptr);
274 assert(nonce
->references
!= 0); // no overflows
275 debugs(29, 9, "nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
279 authDigestNonceUnlink(digest_nonce_h
* nonce
)
281 assert(nonce
!= nullptr);
283 if (nonce
->references
> 0) {
284 -- nonce
->references
;
286 debugs(29, DBG_IMPORTANT
, "Attempt to lower nonce " << nonce
<< " refcount below 0!");
289 debugs(29, 9, "nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
291 if (nonce
->references
== 0)
292 authenticateDigestNonceDelete(nonce
);
296 authenticateDigestNonceNonceHex(const digest_nonce_h
* nonce
)
301 return (char const *) nonce
->key
;
304 static digest_nonce_h
*
305 authenticateDigestNonceFindNonce(const char *noncehex
)
307 digest_nonce_h
*nonce
= nullptr;
309 if (noncehex
== nullptr)
312 debugs(29, 9, "looking for noncehex '" << noncehex
<< "' in the nonce cache.");
314 nonce
= static_cast < digest_nonce_h
* >(hash_lookup(digest_nonce_cache
, noncehex
));
316 if ((nonce
== nullptr) || (strcmp(authenticateDigestNonceNonceHex(nonce
), noncehex
)))
319 debugs(29, 9, "Found nonce '" << nonce
<< "'");
325 authDigestNonceIsValid(digest_nonce_h
* nonce
, char nc
[9])
328 /* do we have a nonce ? */
333 intnc
= strtol(nc
, nullptr, 16);
335 /* has it already been invalidated ? */
336 if (!nonce
->flags
.valid
) {
337 debugs(29, 4, "Nonce already invalidated");
341 /* is the nonce-count ok ? */
342 if (!static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->CheckNonceCount
) {
343 /* Ignore client supplied NC */
344 intnc
= nonce
->nc
+ 1;
347 if ((static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->NonceStrictness
&& intnc
!= nonce
->nc
+ 1) ||
348 intnc
< nonce
->nc
+ 1) {
349 debugs(29, 4, "Nonce count doesn't match");
350 nonce
->flags
.valid
= false;
354 /* increment the nonce count - we've already checked that intnc is a
355 * valid representation for us, so we don't need the test here.
359 return !authDigestNonceIsStale(nonce
);
363 authDigestNonceIsStale(digest_nonce_h
* nonce
)
365 /* do we have a nonce ? */
370 /* Is it already invalidated? */
371 if (!nonce
->flags
.valid
)
374 /* has it's max duration expired? */
375 if (nonce
->noncedata
.creationtime
+ static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->noncemaxduration
< current_time
.tv_sec
) {
376 debugs(29, 4, "Nonce is too old. " <<
377 nonce
->noncedata
.creationtime
<< " " <<
378 static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->noncemaxduration
<< " " <<
379 current_time
.tv_sec
);
381 nonce
->flags
.valid
= false;
385 if (nonce
->nc
> 99999998) {
386 debugs(29, 4, "Nonce count overflow");
387 nonce
->flags
.valid
= false;
391 if (nonce
->nc
> static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->noncemaxuses
) {
392 debugs(29, 4, "Nonce count over user limit");
393 nonce
->flags
.valid
= false;
402 * \retval 0 the digest is not stale yet
403 * \retval -1 the digest will be stale on the next request
406 authDigestNonceLastRequest(digest_nonce_h
* nonce
)
411 if (nonce
->nc
== 99999997) {
412 debugs(29, 4, "Nonce count about to overflow");
416 if (nonce
->nc
>= static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->noncemaxuses
- 1) {
417 debugs(29, 4, "Nonce count about to hit user limit");
421 /* and other tests are possible. */
426 authDigestNoncePurge(digest_nonce_h
* nonce
)
431 if (!nonce
->flags
.incache
)
434 hash_remove_link(digest_nonce_cache
, nonce
);
436 nonce
->flags
.incache
= false;
438 /* the cache's link */
439 authDigestNonceUnlink(nonce
);
443 Auth::Digest::Config::rotateHelpers()
445 /* schedule closure of existing helpers */
446 if (digestauthenticators
) {
447 helperShutdown(digestauthenticators
);
450 /* NP: dynamic helper restart will ensure they start up again as needed. */
454 Auth::Digest::Config::dump(StoreEntry
* entry
, const char *name
, Auth::SchemeConfig
* scheme
) const
456 if (!Auth::SchemeConfig::dump(entry
, name
, scheme
))
459 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",
460 name
, "digest", noncemaxuses
,
461 name
, "digest", (int) noncemaxduration
,
462 name
, "digest", (int) nonceGCInterval
);
467 Auth::Digest::Config::active() const
469 return authdigest_initialised
== 1;
473 Auth::Digest::Config::configured() const
475 if ((authenticateProgram
!= nullptr) &&
476 (authenticateChildren
.n_max
!= 0) &&
477 !realm
.isEmpty() && (noncemaxduration
> -1))
483 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
485 Auth::Digest::Config::fixHeader(Auth::UserRequest::Pointer auth_user_request
, HttpReply
*rep
, Http::HdrType hdrType
, HttpRequest
*)
487 if (!authenticateProgram
)
491 digest_nonce_h
*nonce
= nullptr;
493 /* on a 407 or 401 we always use a new nonce */
494 if (auth_user_request
!= nullptr) {
495 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
498 stale
= digest_user
->credentials() == Auth::Handshake
;
500 nonce
= digest_user
->currentNonce();
505 nonce
= authenticateDigestNonceNew();
508 debugs(29, 9, "Sending type:" << hdrType
<<
509 " header: 'Digest realm=\"" << realm
<< "\", nonce=\"" <<
510 authenticateDigestNonceNonceHex(nonce
) << "\", qop=\"" << QOP_AUTH
<<
511 "\", stale=" << (stale
? "true" : "false"));
513 /* in the future, for WWW auth we may want to support the domain entry */
514 httpHeaderPutStrf(&rep
->header
, hdrType
, "Digest realm=\"" SQUIDSBUFPH
"\", nonce=\"%s\", qop=\"%s\", stale=%s",
515 SQUIDSBUFPRINT(realm
), authenticateDigestNonceNonceHex(nonce
), QOP_AUTH
, stale
? "true" : "false");
518 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
521 Auth::Digest::Config::init(Auth::SchemeConfig
*)
523 if (authenticateProgram
) {
524 authenticateDigestNonceSetup();
525 authdigest_initialised
= 1;
527 if (digestauthenticators
== nullptr)
528 digestauthenticators
= Helper::Client::Make("digestauthenticator");
530 digestauthenticators
->cmdline
= authenticateProgram
;
532 digestauthenticators
->childs
.updateLimits(authenticateChildren
);
534 digestauthenticators
->ipc_type
= IPC_STREAM
;
536 helperOpenServers(digestauthenticators
);
541 Auth::Digest::Config::registerWithCacheManager(void)
543 Mgr::RegisterAction("digestauthenticator",
544 "Digest User Authenticator Stats",
545 authenticateDigestStats
, 0, 1);
548 /* free any allocated configuration details */
550 Auth::Digest::Config::done()
552 Auth::SchemeConfig::done();
554 authdigest_initialised
= 0;
556 if (digestauthenticators
)
557 helperShutdown(digestauthenticators
);
562 digestauthenticators
= nullptr;
564 if (authenticateProgram
)
565 wordlistDestroy(&authenticateProgram
);
568 Auth::Digest::Config::Config() :
569 nonceGCInterval(5*60),
570 noncemaxduration(30*60),
578 Auth::Digest::Config::parse(Auth::SchemeConfig
* scheme
, int n_configured
, char *param_str
)
580 if (strcmp(param_str
, "nonce_garbage_interval") == 0) {
581 parse_time_t(&nonceGCInterval
);
582 } else if (strcmp(param_str
, "nonce_max_duration") == 0) {
583 parse_time_t(&noncemaxduration
);
584 } else if (strcmp(param_str
, "nonce_max_count") == 0) {
585 parse_int((int *) &noncemaxuses
);
586 } else if (strcmp(param_str
, "nonce_strictness") == 0) {
587 parse_onoff(&NonceStrictness
);
588 } else if (strcmp(param_str
, "check_nonce_count") == 0) {
589 parse_onoff(&CheckNonceCount
);
590 } else if (strcmp(param_str
, "post_workaround") == 0) {
591 parse_onoff(&PostWorkaround
);
593 Auth::SchemeConfig::parse(scheme
, n_configured
, param_str
);
597 Auth::Digest::Config::type() const
599 return Auth::Digest::Scheme::GetInstance()->type();
603 authenticateDigestStats(StoreEntry
* sentry
)
605 if (digestauthenticators
)
606 digestauthenticators
->packStatsInto(sentry
, "Digest Authenticator Statistics");
609 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
612 authDigestNonceUserUnlink(digest_nonce_h
* nonce
)
614 Auth::Digest::User
*digest_user
;
615 dlink_node
*link
, *tmplink
;
623 digest_user
= nonce
->user
;
625 /* unlink from the user list. Yes we're crossing structures but this is the only
626 * time this code is needed
628 link
= digest_user
->nonces
.head
;
634 if (tmplink
->data
== nonce
) {
635 dlinkDelete(tmplink
, &digest_user
->nonces
);
636 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
642 /* this reference to user was not locked because freeeing the user frees
645 nonce
->user
= nullptr;
648 /* authDigesteserLinkNonce: add a nonce to a given user's struct */
650 authDigestUserLinkNonce(Auth::Digest::User
* user
, digest_nonce_h
* nonce
)
654 if (!user
|| !nonce
|| !nonce
->user
)
657 Auth::Digest::User
*digest_user
= user
;
659 node
= digest_user
->nonces
.head
;
661 while (node
&& (node
->data
!= nonce
))
667 node
= new dlink_node
;
669 dlinkAddTail(nonce
, node
, &digest_user
->nonces
);
671 authDigestNonceLink(nonce
);
673 /* ping this nonce to this auth user */
674 assert((nonce
->user
== nullptr) || (nonce
->user
== user
));
676 /* we don't lock this reference because removing the user removes the
677 * hash too. Of course if that changes we're stuffed so read the code huh?
682 /* setup the necessary info to log the username */
683 static Auth::UserRequest::Pointer
684 authDigestLogUsername(char *username
, Auth::UserRequest::Pointer auth_user_request
, const char *requestRealm
)
686 assert(auth_user_request
!= nullptr);
688 /* log the username */
689 debugs(29, 9, "Creating new user for logging '" << (username
?username
:"[no username]") << "'");
690 Auth::User::Pointer digest_user
= new Auth::Digest::User(static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest")), requestRealm
);
691 /* save the credentials */
692 digest_user
->username(username
);
693 /* set the auth_user type */
694 digest_user
->auth_type
= Auth::AUTH_BROKEN
;
695 /* link the request to the user */
696 auth_user_request
->user(digest_user
);
697 return auth_user_request
;
701 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
702 * Auth_user structure.
704 Auth::UserRequest::Pointer
705 Auth::Digest::Config::decode(char const *proxy_auth
, const HttpRequest
*request
, const char *aRequestRealm
)
709 const char *pos
= nullptr;
710 char *username
= nullptr;
711 digest_nonce_h
*nonce
;
714 debugs(29, 9, "beginning");
716 Auth::Digest::UserRequest
*digest_request
= new Auth::Digest::UserRequest();
718 /* trim DIGEST from string */
720 while (xisgraph(*proxy_auth
))
723 /* Trim leading whitespace before decoding */
724 while (xisspace(*proxy_auth
))
727 String
temp(proxy_auth
);
729 while (strListGetItem(&temp
, ',', &item
, &ilen
, &pos
)) {
730 /* isolate directive name & value */
733 if ((p
= (const char *)memchr(item
, '=', ilen
)) && (p
- item
< ilen
)) {
736 vlen
= ilen
- (p
- item
);
742 SBuf
keyName(item
, nlen
);
746 // see RFC 2617 section 3.2.1 and 3.2.2 for details on the BNF
748 if (keyName
== SBuf("domain",6) || keyName
== SBuf("uri",3)) {
749 // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"'
750 // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain
751 if (vlen
> 1 && *p
== '"' && *(p
+ vlen
-1) == '"') {
752 value
.assign(p
+1, vlen
-2);
754 } else if (keyName
== SBuf("qop",3)) {
755 // qop is more special.
756 // On request this must not be quoted-string de-quoted. But is several values wrapped in '"'
757 // On response this is a single un-quoted token.
758 if (vlen
> 1 && *p
== '"' && *(p
+ vlen
-1) == '"') {
759 value
.assign(p
+1, vlen
-2);
761 value
.assign(p
, vlen
);
763 } else if (*p
== '"') {
764 if (!httpHeaderParseQuotedString(p
, vlen
, &value
)) {
765 debugs(29, 9, "Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
769 value
.assign(p
, vlen
);
772 debugs(29, 9, "Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
777 const http_digest_attr_type t
= DigestFieldsLookupTable
.lookup(keyName
);
780 case DIGEST_USERNAME
:
782 if (value
.size() != 0) {
783 const auto v
= value
.termedBuf();
784 if (utf8
&& !isValidUtf8String(v
, v
+ value
.size())) {
785 auto str
= isCP1251EncodingAllowed(request
) ? Cp1251ToUtf8(v
) : Latin1ToUtf8(v
);
786 value
= SBufToString(str
);
788 username
= xstrndup(value
.rawBuf(), value
.size() + 1);
790 debugs(29, 9, "Found Username '" << username
<< "'");
794 safe_free(digest_request
->realm
);
795 if (value
.size() != 0)
796 digest_request
->realm
= xstrndup(value
.rawBuf(), value
.size() + 1);
797 debugs(29, 9, "Found realm '" << digest_request
->realm
<< "'");
801 safe_free(digest_request
->qop
);
802 if (value
.size() != 0)
803 digest_request
->qop
= xstrndup(value
.rawBuf(), value
.size() + 1);
804 debugs(29, 9, "Found qop '" << digest_request
->qop
<< "'");
807 case DIGEST_ALGORITHM
:
808 safe_free(digest_request
->algorithm
);
809 if (value
.size() != 0)
810 digest_request
->algorithm
= xstrndup(value
.rawBuf(), value
.size() + 1);
811 debugs(29, 9, "Found algorithm '" << digest_request
->algorithm
<< "'");
815 safe_free(digest_request
->uri
);
816 if (value
.size() != 0)
817 digest_request
->uri
= xstrndup(value
.rawBuf(), value
.size() + 1);
818 debugs(29, 9, "Found uri '" << digest_request
->uri
<< "'");
822 safe_free(digest_request
->noncehex
);
823 if (value
.size() != 0)
824 digest_request
->noncehex
= xstrndup(value
.rawBuf(), value
.size() + 1);
825 debugs(29, 9, "Found nonce '" << digest_request
->noncehex
<< "'");
829 if (value
.size() != 8) {
830 debugs(29, 9, "Invalid nc '" << value
<< "' in '" << temp
<< "'");
832 xstrncpy(digest_request
->nc
, value
.rawBuf(), value
.size() + 1);
833 debugs(29, 9, "Found noncecount '" << digest_request
->nc
<< "'");
837 safe_free(digest_request
->cnonce
);
838 if (value
.size() != 0)
839 digest_request
->cnonce
= xstrndup(value
.rawBuf(), value
.size() + 1);
840 debugs(29, 9, "Found cnonce '" << digest_request
->cnonce
<< "'");
843 case DIGEST_RESPONSE
:
844 safe_free(digest_request
->response
);
845 if (value
.size() != 0)
846 digest_request
->response
= xstrndup(value
.rawBuf(), value
.size() + 1);
847 debugs(29, 9, "Found response '" << digest_request
->response
<< "'");
851 debugs(29, 3, "Unknown attribute '" << item
<< "' in '" << temp
<< "'");
858 /* now we validate the data given to us */
861 * TODO: on invalid parameters we should return 400, not 407.
862 * Find some clean way of doing this. perhaps return a valid
863 * struct, and set the direction to clientwards combined with
864 * a change to the clientwards handling code (ie let the
865 * clientwards call set the error type (but limited to known
866 * correct values - 400/401/407
869 /* 2069 requirements */
872 Auth::UserRequest::Pointer rv
;
873 /* do we have a username ? */
874 if (!username
|| username
[0] == '\0') {
875 debugs(29, 2, "Empty or not present username");
876 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
881 /* Sanity check of the username.
882 * " can not be allowed in usernames until * the digest helper protocol
885 if (strchr(username
, '"')) {
886 debugs(29, 2, "Unacceptable username '" << username
<< "'");
887 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
892 /* do we have a realm ? */
893 if (!digest_request
->realm
|| digest_request
->realm
[0] == '\0') {
894 debugs(29, 2, "Empty or not present realm");
895 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
901 if (!digest_request
->noncehex
|| digest_request
->noncehex
[0] == '\0') {
902 debugs(29, 2, "Empty or not present nonce");
903 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
908 /* we can't check the URI just yet. We'll check it in the
909 * authenticate phase, but needs to be given */
910 if (!digest_request
->uri
|| digest_request
->uri
[0] == '\0') {
911 debugs(29, 2, "Missing URI field");
912 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
917 /* is the response the correct length? */
918 if (!digest_request
->response
|| strlen(digest_request
->response
) != 32) {
919 debugs(29, 2, "Response length invalid");
920 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
925 /* check the algorithm is present and supported */
926 if (!digest_request
->algorithm
)
927 digest_request
->algorithm
= xstrndup("MD5", 4);
928 else if (strcmp(digest_request
->algorithm
, "MD5")
929 && strcmp(digest_request
->algorithm
, "MD5-sess")) {
930 debugs(29, 2, "invalid algorithm specified!");
931 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
936 /* 2617 requirements, indicated by qop */
937 if (digest_request
->qop
) {
939 /* check the qop is what we expected. */
940 if (strcmp(digest_request
->qop
, QOP_AUTH
) != 0) {
941 /* we received a qop option we didn't send */
942 debugs(29, 2, "Invalid qop option received");
943 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
949 if (!digest_request
->cnonce
|| digest_request
->cnonce
[0] == '\0') {
950 debugs(29, 2, "Missing cnonce field");
951 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
957 if (strlen(digest_request
->nc
) != 8 || strspn(digest_request
->nc
, "0123456789abcdefABCDEF") != 8) {
958 debugs(29, 2, "invalid nonce count");
959 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
964 /* cnonce and nc both require qop */
965 if (digest_request
->cnonce
|| digest_request
->nc
[0] != '\0') {
966 debugs(29, 2, "missing qop!");
967 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
973 /** below nonce state dependent **/
976 nonce
= authenticateDigestNonceFindNonce(digest_request
->noncehex
);
977 /* check that we're not being hacked / the username hasn't changed */
978 if (nonce
&& nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
979 debugs(29, 2, "Username for the nonce does not equal the username for the request");
984 /* we couldn't find a matching nonce! */
985 debugs(29, 2, "Unexpected or invalid nonce received from " << username
);
986 Auth::UserRequest::Pointer auth_request
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
987 auth_request
->user()->credentials(Auth::Handshake
);
992 digest_request
->nonce
= nonce
;
993 authDigestNonceLink(nonce
);
995 /* check that we're not being hacked / the username hasn't changed */
996 if (nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
997 debugs(29, 2, "Username for the nonce does not equal the username for the request");
998 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1003 /* the method we'll check at the authenticate step as well */
1005 /* we don't send or parse opaques. Ok so we're flexible ... */
1008 Auth::Digest::User
*digest_user
;
1010 Auth::User::Pointer auth_user
;
1012 SBuf key
= Auth::User::BuildUserKey(username
, aRequestRealm
);
1013 if (key
.isEmpty() || !(auth_user
= Auth::Digest::User::Cache()->lookup(key
))) {
1014 /* the user doesn't exist in the username cache yet */
1015 debugs(29, 9, "Creating new digest user '" << username
<< "'");
1016 digest_user
= new Auth::Digest::User(this, aRequestRealm
);
1017 /* auth_user is a parent */
1018 auth_user
= digest_user
;
1019 /* save the username */
1020 digest_user
->username(username
);
1021 /* set the user type */
1022 digest_user
->auth_type
= Auth::AUTH_DIGEST
;
1023 /* this auth_user struct is the one to get added to the
1025 /* store user in hash's */
1026 digest_user
->addToNameCache();
1029 * Add the digest to the user so we can tell if a hacking
1030 * or spoofing attack is taking place. We do this by assuming
1031 * the user agent won't change user name without warning.
1033 authDigestUserLinkNonce(digest_user
, nonce
);
1035 /* auth_user is now linked, we reset these values
1036 * after external auth occurs anyway */
1037 auth_user
->expiretime
= current_time
.tv_sec
;
1039 debugs(29, 9, "Found user '" << username
<< "' in the user cache as '" << auth_user
<< "'");
1040 digest_user
= static_cast<Auth::Digest::User
*>(auth_user
.getRaw());
1041 digest_user
->credentials(Auth::Unchecked
);
1045 /*link the request and the user */
1046 assert(digest_request
!= nullptr);
1048 digest_request
->user(digest_user
);
1049 debugs(29, 9, "username = '" << digest_user
->username() << "'\nrealm = '" <<
1050 digest_request
->realm
<< "'\nqop = '" << digest_request
->qop
<<
1051 "'\nalgorithm = '" << digest_request
->algorithm
<< "'\nuri = '" <<
1052 digest_request
->uri
<< "'\nnonce = '" << digest_request
->noncehex
<<
1053 "'\nnc = '" << digest_request
->nc
<< "'\ncnonce = '" <<
1054 digest_request
->cnonce
<< "'\nresponse = '" <<
1055 digest_request
->response
<< "'\ndigestnonce = '" << nonce
<< "'");
1057 return digest_request
;