2 * Copyright (C) 1996-2015 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/digest/Config.h"
17 #include "auth/digest/Scheme.h"
18 #include "auth/digest/User.h"
19 #include "auth/digest/UserRequest.h"
20 #include "auth/Gadgets.h"
21 #include "auth/State.h"
26 #include "HttpHeaderTools.h"
27 #include "HttpReply.h"
28 #include "HttpRequest.h"
29 #include "mgr/Registration.h"
32 #include "SquidTime.h"
39 /* digest_nonce_h still uses explicit alloc()/freeOne() MemPool calls.
40 * XXX: convert to MEMPROXY_CLASS() API
46 static AUTHSSTATS authenticateDigestStats
;
48 helper
*digestauthenticators
= NULL
;
50 static hash_table
*digest_nonce_cache
;
52 static int authdigest_initialised
= 0;
53 static MemAllocator
*digest_nonce_pool
= NULL
;
55 enum http_digest_attr_type
{
67 struct HttpDigestFieldAttrs
{
69 http_digest_attr_type id
;
70 HttpDigestFieldAttrs() : name(nullptr), id(DIGEST_ENUM_END
) {}
71 HttpDigestFieldAttrs(const char *aName
, http_digest_attr_type anId
) :
75 static const HttpDigestFieldAttrs DigestAttrs
[DIGEST_ENUM_END
] = {
76 HttpDigestFieldAttrs("username", DIGEST_USERNAME
),
77 HttpDigestFieldAttrs("realm", DIGEST_REALM
),
78 HttpDigestFieldAttrs("qop", DIGEST_QOP
),
79 HttpDigestFieldAttrs("algorithm", DIGEST_ALGORITHM
),
80 HttpDigestFieldAttrs("uri", DIGEST_URI
),
81 HttpDigestFieldAttrs("nonce", DIGEST_NONCE
),
82 HttpDigestFieldAttrs("nc", DIGEST_NC
),
83 HttpDigestFieldAttrs("cnonce", DIGEST_CNONCE
),
84 HttpDigestFieldAttrs("response", DIGEST_RESPONSE
),
87 /* a SBuf -> http_digest_attr_type lookup table.
88 * Implementaiton can be improved by using an unordered_map with custom hasher
89 * but the focus here is API correctness.
91 class DigestFieldsLookupTable_t
{
93 DigestFieldsLookupTable_t();
94 http_digest_attr_type
lookup(const SBuf
&key
) const;
96 /* could be unordered_map but let's skip the requirement to hash for now */
97 typedef std::map
<const SBuf
, http_digest_attr_type
> lookupTable_t
;
98 lookupTable_t lookupTable
;
100 DigestFieldsLookupTable_t::DigestFieldsLookupTable_t() {
101 for (int i
= 0; i
< DIGEST_ENUM_END
; ++i
) {
102 const SBuf
s(DigestAttrs
[i
].name
);
103 lookupTable
[s
] = DigestAttrs
[i
].id
;
106 inline http_digest_attr_type
107 DigestFieldsLookupTable_t::lookup(const SBuf
&key
) const
109 auto r
= lookupTable
.find(key
);
110 if (r
== lookupTable
.end())
111 return DIGEST_ENUM_END
; // original returns HDR_BAD_HDR(-1)
114 DigestFieldsLookupTable_t DigestFieldsLookupTable
;
122 static void authenticateDigestNonceCacheCleanup(void *data
);
123 static digest_nonce_h
*authenticateDigestNonceFindNonce(const char *nonceb64
);
124 static void authenticateDigestNonceDelete(digest_nonce_h
* nonce
);
125 static void authenticateDigestNonceSetup(void);
126 static void authDigestNonceEncode(digest_nonce_h
* nonce
);
127 static void authDigestNonceLink(digest_nonce_h
* nonce
);
129 static int authDigestNonceLinks(digest_nonce_h
* nonce
);
131 static void authDigestNonceUserUnlink(digest_nonce_h
* nonce
);
134 authDigestNonceEncode(digest_nonce_h
* nonce
)
142 nonce
->key
= xcalloc(base64_encode_len(sizeof(digest_nonce_data
)), 1);
143 struct base64_encode_ctx ctx
;
144 base64_encode_init(&ctx
);
145 size_t blen
= base64_encode_update(&ctx
, reinterpret_cast<uint8_t*>(nonce
->key
), sizeof(digest_nonce_data
), reinterpret_cast<const uint8_t*>(&(nonce
->noncedata
)));
146 blen
+= base64_encode_final(&ctx
, reinterpret_cast<uint8_t*>(nonce
->key
)+blen
);
150 authenticateDigestNonceNew(void)
152 digest_nonce_h
*newnonce
= static_cast < digest_nonce_h
* >(digest_nonce_pool
->alloc());
154 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
155 * === EXCERPT FROM RFC 2617 ===
156 * The contents of the nonce are implementation dependent. The quality
157 * of the implementation depends on a good choice. A nonce might, for
158 * example, be constructed as the base 64 encoding of
160 * time-stamp H(time-stamp ":" ETag ":" private-key)
162 * where time-stamp is a server-generated time or other non-repeating
163 * value, ETag is the value of the HTTP ETag header associated with
164 * the requested entity, and private-key is data known only to the
165 * server. With a nonce of this form a server would recalculate the
166 * hash portion after receiving the client authentication header and
167 * reject the request if it did not match the nonce from that header
168 * or if the time-stamp value is not recent enough. In this way the
169 * server can limit the time of the nonce's validity. The inclusion of
170 * the ETag prevents a replay request for an updated version of the
171 * resource. (Note: including the IP address of the client in the
172 * nonce would appear to offer the server the ability to limit the
173 * reuse of the nonce to the same client that originally got it.
174 * However, that would break proxy farms, where requests from a single
175 * user often go through different proxies in the farm. Also, IP
176 * address spoofing is not that hard.)
179 * Now for my reasoning:
180 * We will not accept a unrecognised nonce->we have all recognisable
181 * nonces stored. If we send out unique base64 encodings we guarantee
182 * that a given nonce applies to only one user (barring attacks or
183 * really bad timing with expiry and creation). Using a random
184 * component in the nonce allows us to loop to find a unique nonce.
185 * We use H(nonce_data) so the nonce is meaningless to the reciever.
186 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
187 * And even if our randomness is not very random we don't really care
188 * - the timestamp and memory pointer also guarantee local uniqueness
189 * in the input to the hash function.
191 // NP: this will likely produce the same randomness sequences for each worker
192 // since they should all start within the 1-second resolution of seed value.
193 static std::mt19937
mt(static_cast<uint32_t>(getCurrentTime() & 0xFFFFFFFF));
194 static std::uniform_int_distribution
<uint32_t> newRandomData
;
196 /* create a new nonce */
198 newnonce
->flags
.valid
= true;
199 newnonce
->noncedata
.self
= newnonce
;
200 newnonce
->noncedata
.creationtime
= current_time
.tv_sec
;
201 newnonce
->noncedata
.randomdata
= newRandomData(mt
);
203 authDigestNonceEncode(newnonce
);
205 // ensure temporal uniqueness by checking for existing nonce
206 while (authenticateDigestNonceFindNonce((char const *) (newnonce
->key
))) {
207 /* create a new nonce */
208 newnonce
->noncedata
.randomdata
= newRandomData(mt
);
209 authDigestNonceEncode(newnonce
);
212 hash_join(digest_nonce_cache
, newnonce
);
213 /* the cache's link */
214 authDigestNonceLink(newnonce
);
215 newnonce
->flags
.incache
= true;
216 debugs(29, 5, "created nonce " << newnonce
<< " at " << newnonce
->noncedata
.creationtime
);
221 authenticateDigestNonceDelete(digest_nonce_h
* nonce
)
224 assert(nonce
->references
== 0);
227 if (nonce
->flags
.incache
)
228 hash_remove_link(digest_nonce_cache
, nonce
);
232 assert(!nonce
->flags
.incache
);
234 safe_free(nonce
->key
);
236 digest_nonce_pool
->freeOne(nonce
);
241 authenticateDigestNonceSetup(void)
243 if (!digest_nonce_pool
)
244 digest_nonce_pool
= memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h
));
246 if (!digest_nonce_cache
) {
247 digest_nonce_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
248 assert(digest_nonce_cache
);
249 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->nonceGCInterval
, 1);
254 authenticateDigestNonceShutdown(void)
257 * We empty the cache of any nonces left in there.
259 digest_nonce_h
*nonce
;
261 if (digest_nonce_cache
) {
262 debugs(29, 2, "Shutting down nonce cache");
263 hash_first(digest_nonce_cache
);
265 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
266 assert(nonce
->flags
.incache
);
267 authDigestNoncePurge(nonce
);
272 if (digest_nonce_pool
) {
273 delete digest_nonce_pool
;
274 digest_nonce_pool
= NULL
;
278 debugs(29, 2, "Nonce cache shutdown");
282 authenticateDigestNonceCacheCleanup(void *)
285 * We walk the hash by nonceb64 as that is the unique key we
286 * use. For big hash tables we could consider stepping through
287 * the cache, 100/200 entries at a time. Lets see how it flies
290 digest_nonce_h
*nonce
;
291 debugs(29, 3, "Cleaning the nonce cache now");
292 debugs(29, 3, "Current time: " << current_time
.tv_sec
);
293 hash_first(digest_nonce_cache
);
295 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
296 debugs(29, 3, "nonce entry : " << nonce
<< " '" << (char *) nonce
->key
<< "'");
297 debugs(29, 4, "Creation time: " << nonce
->noncedata
.creationtime
);
299 if (authDigestNonceIsStale(nonce
)) {
300 debugs(29, 4, "Removing nonce " << (char *) nonce
->key
<< " from cache due to timeout.");
301 assert(nonce
->flags
.incache
);
302 /* invalidate nonce so future requests fail */
303 nonce
->flags
.valid
= false;
304 /* if it is tied to a auth_user, remove the tie */
305 authDigestNonceUserUnlink(nonce
);
306 authDigestNoncePurge(nonce
);
310 debugs(29, 3, "Finished cleaning the nonce cache.");
312 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->active())
313 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->nonceGCInterval
, 1);
317 authDigestNonceLink(digest_nonce_h
* nonce
)
319 assert(nonce
!= NULL
);
321 debugs(29, 9, "nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
326 authDigestNonceLinks(digest_nonce_h
* nonce
)
331 return nonce
->references
;
337 authDigestNonceUnlink(digest_nonce_h
* nonce
)
339 assert(nonce
!= NULL
);
341 if (nonce
->references
> 0) {
342 -- nonce
->references
;
344 debugs(29, DBG_IMPORTANT
, "Attempt to lower nonce " << nonce
<< " refcount below 0!");
347 debugs(29, 9, "nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
349 if (nonce
->references
== 0)
350 authenticateDigestNonceDelete(nonce
);
354 authenticateDigestNonceNonceb64(const digest_nonce_h
* nonce
)
359 return (char const *) nonce
->key
;
362 static digest_nonce_h
*
363 authenticateDigestNonceFindNonce(const char *nonceb64
)
365 digest_nonce_h
*nonce
= NULL
;
367 if (nonceb64
== NULL
)
370 debugs(29, 9, "looking for nonceb64 '" << nonceb64
<< "' in the nonce cache.");
372 nonce
= static_cast < digest_nonce_h
* >(hash_lookup(digest_nonce_cache
, nonceb64
));
374 if ((nonce
== NULL
) || (strcmp(authenticateDigestNonceNonceb64(nonce
), nonceb64
)))
377 debugs(29, 9, "Found nonce '" << nonce
<< "'");
383 authDigestNonceIsValid(digest_nonce_h
* nonce
, char nc
[9])
386 /* do we have a nonce ? */
391 intnc
= strtol(nc
, NULL
, 16);
393 /* has it already been invalidated ? */
394 if (!nonce
->flags
.valid
) {
395 debugs(29, 4, "Nonce already invalidated");
399 /* is the nonce-count ok ? */
400 if (!static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->CheckNonceCount
) {
401 /* Ignore client supplied NC */
402 intnc
= nonce
->nc
+ 1;
405 if ((static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->NonceStrictness
&& intnc
!= nonce
->nc
+ 1) ||
406 intnc
< nonce
->nc
+ 1) {
407 debugs(29, 4, "Nonce count doesn't match");
408 nonce
->flags
.valid
= false;
412 /* increment the nonce count - we've already checked that intnc is a
413 * valid representation for us, so we don't need the test here.
417 return !authDigestNonceIsStale(nonce
);
421 authDigestNonceIsStale(digest_nonce_h
* nonce
)
423 /* do we have a nonce ? */
428 /* Is it already invalidated? */
429 if (!nonce
->flags
.valid
)
432 /* has it's max duration expired? */
433 if (nonce
->noncedata
.creationtime
+ static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxduration
< current_time
.tv_sec
) {
434 debugs(29, 4, "Nonce is too old. " <<
435 nonce
->noncedata
.creationtime
<< " " <<
436 static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxduration
<< " " <<
437 current_time
.tv_sec
);
439 nonce
->flags
.valid
= false;
443 if (nonce
->nc
> 99999998) {
444 debugs(29, 4, "Nonce count overflow");
445 nonce
->flags
.valid
= false;
449 if (nonce
->nc
> static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxuses
) {
450 debugs(29, 4, "Nonce count over user limit");
451 nonce
->flags
.valid
= false;
460 * \retval 0 the digest is not stale yet
461 * \retval -1 the digest will be stale on the next request
464 authDigestNonceLastRequest(digest_nonce_h
* nonce
)
469 if (nonce
->nc
== 99999997) {
470 debugs(29, 4, "Nonce count about to overflow");
474 if (nonce
->nc
>= static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxuses
- 1) {
475 debugs(29, 4, "Nonce count about to hit user limit");
479 /* and other tests are possible. */
484 authDigestNoncePurge(digest_nonce_h
* nonce
)
489 if (!nonce
->flags
.incache
)
492 hash_remove_link(digest_nonce_cache
, nonce
);
494 nonce
->flags
.incache
= false;
496 /* the cache's link */
497 authDigestNonceUnlink(nonce
);
501 Auth::Digest::Config::rotateHelpers()
503 /* schedule closure of existing helpers */
504 if (digestauthenticators
) {
505 helperShutdown(digestauthenticators
);
508 /* NP: dynamic helper restart will ensure they start up again as needed. */
512 Auth::Digest::Config::dump(StoreEntry
* entry
, const char *name
, Auth::Config
* scheme
) const
514 if (!Auth::Config::dump(entry
, name
, scheme
))
517 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",
518 name
, "digest", noncemaxuses
,
519 name
, "digest", (int) noncemaxduration
,
520 name
, "digest", (int) nonceGCInterval
);
521 storeAppendPrintf(entry
, "%s digest utf8 %s\n", name
, utf8
? "on" : "off");
526 Auth::Digest::Config::active() const
528 return authdigest_initialised
== 1;
532 Auth::Digest::Config::configured() const
534 if ((authenticateProgram
!= NULL
) &&
535 (authenticateChildren
.n_max
!= 0) &&
536 !realm
.isEmpty() && (noncemaxduration
> -1))
542 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
544 Auth::Digest::Config::fixHeader(Auth::UserRequest::Pointer auth_user_request
, HttpReply
*rep
, http_hdr_type hdrType
, HttpRequest
*)
546 if (!authenticateProgram
)
550 digest_nonce_h
*nonce
= NULL
;
552 /* on a 407 or 401 we always use a new nonce */
553 if (auth_user_request
!= NULL
) {
554 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
557 stale
= digest_user
->credentials() == Auth::Handshake
;
559 nonce
= digest_user
->currentNonce();
564 nonce
= authenticateDigestNonceNew();
567 debugs(29, 9, "Sending type:" << hdrType
<<
568 " header: 'Digest realm=\"" << realm
<< "\", nonce=\"" <<
569 authenticateDigestNonceNonceb64(nonce
) << "\", qop=\"" << QOP_AUTH
<<
570 "\", stale=" << (stale
? "true" : "false"));
572 /* in the future, for WWW auth we may want to support the domain entry */
573 httpHeaderPutStrf(&rep
->header
, hdrType
, "Digest realm=\"" SQUIDSBUFPH
"\", nonce=\"%s\", qop=\"%s\", stale=%s",
574 SQUIDSBUFPRINT(realm
), authenticateDigestNonceNonceb64(nonce
), QOP_AUTH
, stale
? "true" : "false");
577 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
580 Auth::Digest::Config::init(Auth::Config
*)
582 if (authenticateProgram
) {
583 authenticateDigestNonceSetup();
584 authdigest_initialised
= 1;
586 if (digestauthenticators
== NULL
)
587 digestauthenticators
= new helper("digestauthenticator");
589 digestauthenticators
->cmdline
= authenticateProgram
;
591 digestauthenticators
->childs
.updateLimits(authenticateChildren
);
593 digestauthenticators
->ipc_type
= IPC_STREAM
;
595 helperOpenServers(digestauthenticators
);
600 Auth::Digest::Config::registerWithCacheManager(void)
602 Mgr::RegisterAction("digestauthenticator",
603 "Digest User Authenticator Stats",
604 authenticateDigestStats
, 0, 1);
607 /* free any allocated configuration details */
609 Auth::Digest::Config::done()
611 Auth::Config::done();
613 authdigest_initialised
= 0;
615 if (digestauthenticators
)
616 helperShutdown(digestauthenticators
);
621 delete digestauthenticators
;
622 digestauthenticators
= NULL
;
624 if (authenticateProgram
)
625 wordlistDestroy(&authenticateProgram
);
628 Auth::Digest::Config::Config() :
629 nonceGCInterval(5*60),
630 noncemaxduration(30*60),
639 Auth::Digest::Config::parse(Auth::Config
* scheme
, int n_configured
, char *param_str
)
641 if (strcmp(param_str
, "program") == 0) {
642 if (authenticateProgram
)
643 wordlistDestroy(&authenticateProgram
);
645 parse_wordlist(&authenticateProgram
);
647 requirePathnameExists("auth_param digest program", authenticateProgram
->key
);
648 } else if (strcmp(param_str
, "nonce_garbage_interval") == 0) {
649 parse_time_t(&nonceGCInterval
);
650 } else if (strcmp(param_str
, "nonce_max_duration") == 0) {
651 parse_time_t(&noncemaxduration
);
652 } else if (strcmp(param_str
, "nonce_max_count") == 0) {
653 parse_int((int *) &noncemaxuses
);
654 } else if (strcmp(param_str
, "nonce_strictness") == 0) {
655 parse_onoff(&NonceStrictness
);
656 } else if (strcmp(param_str
, "check_nonce_count") == 0) {
657 parse_onoff(&CheckNonceCount
);
658 } else if (strcmp(param_str
, "post_workaround") == 0) {
659 parse_onoff(&PostWorkaround
);
660 } else if (strcmp(param_str
, "utf8") == 0) {
663 Auth::Config::parse(scheme
, n_configured
, param_str
);
667 Auth::Digest::Config::type() const
669 return Auth::Digest::Scheme::GetInstance()->type();
673 authenticateDigestStats(StoreEntry
* sentry
)
675 if (digestauthenticators
)
676 digestauthenticators
->packStatsInto(sentry
, "Digest Authenticator Statistics");
679 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
682 authDigestNonceUserUnlink(digest_nonce_h
* nonce
)
684 Auth::Digest::User
*digest_user
;
685 dlink_node
*link
, *tmplink
;
693 digest_user
= nonce
->user
;
695 /* unlink from the user list. Yes we're crossing structures but this is the only
696 * time this code is needed
698 link
= digest_user
->nonces
.head
;
704 if (tmplink
->data
== nonce
) {
705 dlinkDelete(tmplink
, &digest_user
->nonces
);
706 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
707 dlinkNodeDelete(tmplink
);
712 /* this reference to user was not locked because freeeing the user frees
718 /* authDigesteserLinkNonce: add a nonce to a given user's struct */
720 authDigestUserLinkNonce(Auth::Digest::User
* user
, digest_nonce_h
* nonce
)
724 if (!user
|| !nonce
|| !nonce
->user
)
727 Auth::Digest::User
*digest_user
= user
;
729 node
= digest_user
->nonces
.head
;
731 while (node
&& (node
->data
!= nonce
))
737 node
= dlinkNodeNew();
739 dlinkAddTail(nonce
, node
, &digest_user
->nonces
);
741 authDigestNonceLink(nonce
);
743 /* ping this nonce to this auth user */
744 assert((nonce
->user
== NULL
) || (nonce
->user
== user
));
746 /* we don't lock this reference because removing the user removes the
747 * hash too. Of course if that changes we're stuffed so read the code huh?
752 /* setup the necessary info to log the username */
753 static Auth::UserRequest::Pointer
754 authDigestLogUsername(char *username
, Auth::UserRequest::Pointer auth_user_request
, const char *requestRealm
)
756 assert(auth_user_request
!= NULL
);
758 /* log the username */
759 debugs(29, 9, "Creating new user for logging '" << (username
?username
:"[no username]") << "'");
760 Auth::User::Pointer digest_user
= new Auth::Digest::User(static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest")), requestRealm
);
761 /* save the credentials */
762 digest_user
->username(username
);
763 /* set the auth_user type */
764 digest_user
->auth_type
= Auth::AUTH_BROKEN
;
765 /* link the request to the user */
766 auth_user_request
->user(digest_user
);
767 return auth_user_request
;
771 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
772 * Auth_user structure.
774 Auth::UserRequest::Pointer
775 Auth::Digest::Config::decode(char const *proxy_auth
, const char *aRequestRealm
)
779 const char *pos
= NULL
;
780 char *username
= NULL
;
781 digest_nonce_h
*nonce
;
784 debugs(29, 9, "beginning");
786 Auth::Digest::UserRequest
*digest_request
= new Auth::Digest::UserRequest();
788 /* trim DIGEST from string */
790 while (xisgraph(*proxy_auth
))
793 /* Trim leading whitespace before decoding */
794 while (xisspace(*proxy_auth
))
797 String
temp(proxy_auth
);
799 while (strListGetItem(&temp
, ',', &item
, &ilen
, &pos
)) {
800 /* isolate directive name & value */
803 if ((p
= (const char *)memchr(item
, '=', ilen
)) && (p
- item
< ilen
)) {
806 vlen
= ilen
- (p
- item
);
812 SBuf
keyName(item
, nlen
);
816 // see RFC 2617 section 3.2.1 and 3.2.2 for details on the BNF
818 if (keyName
== SBuf("domain",6) || keyName
== SBuf("uri",3)) {
819 // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"'
820 // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain
821 if (*p
== '"' && *(p
+ vlen
-1) == '"') {
822 value
.limitInit(p
+1, vlen
-2);
824 } else if (keyName
== SBuf("qop",3)) {
825 // qop is more special.
826 // On request this must not be quoted-string de-quoted. But is several values wrapped in '"'
827 // On response this is a single un-quoted token.
828 if (*p
== '"' && *(p
+ vlen
-1) == '"') {
829 value
.limitInit(p
+1, vlen
-2);
831 value
.limitInit(p
, vlen
);
833 } else if (*p
== '"') {
834 if (!httpHeaderParseQuotedString(p
, vlen
, &value
)) {
835 debugs(29, 9, "Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
839 value
.limitInit(p
, vlen
);
842 debugs(29, 9, "Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
847 const http_digest_attr_type t
= DigestFieldsLookupTable
.lookup(keyName
);
850 case DIGEST_USERNAME
:
852 if (value
.size() != 0)
853 username
= xstrndup(value
.rawBuf(), value
.size() + 1);
854 debugs(29, 9, "Found Username '" << username
<< "'");
858 safe_free(digest_request
->realm
);
859 if (value
.size() != 0)
860 digest_request
->realm
= xstrndup(value
.rawBuf(), value
.size() + 1);
861 debugs(29, 9, "Found realm '" << digest_request
->realm
<< "'");
865 safe_free(digest_request
->qop
);
866 if (value
.size() != 0)
867 digest_request
->qop
= xstrndup(value
.rawBuf(), value
.size() + 1);
868 debugs(29, 9, "Found qop '" << digest_request
->qop
<< "'");
871 case DIGEST_ALGORITHM
:
872 safe_free(digest_request
->algorithm
);
873 if (value
.size() != 0)
874 digest_request
->algorithm
= xstrndup(value
.rawBuf(), value
.size() + 1);
875 debugs(29, 9, "Found algorithm '" << digest_request
->algorithm
<< "'");
879 safe_free(digest_request
->uri
);
880 if (value
.size() != 0)
881 digest_request
->uri
= xstrndup(value
.rawBuf(), value
.size() + 1);
882 debugs(29, 9, "Found uri '" << digest_request
->uri
<< "'");
886 safe_free(digest_request
->nonceb64
);
887 if (value
.size() != 0)
888 digest_request
->nonceb64
= xstrndup(value
.rawBuf(), value
.size() + 1);
889 debugs(29, 9, "Found nonce '" << digest_request
->nonceb64
<< "'");
893 if (value
.size() != 8) {
894 debugs(29, 9, "Invalid nc '" << value
<< "' in '" << temp
<< "'");
896 xstrncpy(digest_request
->nc
, value
.rawBuf(), value
.size() + 1);
897 debugs(29, 9, "Found noncecount '" << digest_request
->nc
<< "'");
901 safe_free(digest_request
->cnonce
);
902 if (value
.size() != 0)
903 digest_request
->cnonce
= xstrndup(value
.rawBuf(), value
.size() + 1);
904 debugs(29, 9, "Found cnonce '" << digest_request
->cnonce
<< "'");
907 case DIGEST_RESPONSE
:
908 safe_free(digest_request
->response
);
909 if (value
.size() != 0)
910 digest_request
->response
= xstrndup(value
.rawBuf(), value
.size() + 1);
911 debugs(29, 9, "Found response '" << digest_request
->response
<< "'");
915 debugs(29, 3, "Unknown attribute '" << item
<< "' in '" << temp
<< "'");
922 /* now we validate the data given to us */
925 * TODO: on invalid parameters we should return 400, not 407.
926 * Find some clean way of doing this. perhaps return a valid
927 * struct, and set the direction to clientwards combined with
928 * a change to the clientwards handling code (ie let the
929 * clientwards call set the error type (but limited to known
930 * correct values - 400/401/407
933 /* 2069 requirements */
936 Auth::UserRequest::Pointer rv
;
937 /* do we have a username ? */
938 if (!username
|| username
[0] == '\0') {
939 debugs(29, 2, "Empty or not present username");
940 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
945 /* Sanity check of the username.
946 * " can not be allowed in usernames until * the digest helper protocol
949 if (strchr(username
, '"')) {
950 debugs(29, 2, "Unacceptable username '" << username
<< "'");
951 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
956 /* do we have a realm ? */
957 if (!digest_request
->realm
|| digest_request
->realm
[0] == '\0') {
958 debugs(29, 2, "Empty or not present realm");
959 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
965 if (!digest_request
->nonceb64
|| digest_request
->nonceb64
[0] == '\0') {
966 debugs(29, 2, "Empty or not present nonce");
967 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
972 /* we can't check the URI just yet. We'll check it in the
973 * authenticate phase, but needs to be given */
974 if (!digest_request
->uri
|| digest_request
->uri
[0] == '\0') {
975 debugs(29, 2, "Missing URI field");
976 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
981 /* is the response the correct length? */
982 if (!digest_request
->response
|| strlen(digest_request
->response
) != 32) {
983 debugs(29, 2, "Response length invalid");
984 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
989 /* check the algorithm is present and supported */
990 if (!digest_request
->algorithm
)
991 digest_request
->algorithm
= xstrndup("MD5", 4);
992 else if (strcmp(digest_request
->algorithm
, "MD5")
993 && strcmp(digest_request
->algorithm
, "MD5-sess")) {
994 debugs(29, 2, "invalid algorithm specified!");
995 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1000 /* 2617 requirements, indicated by qop */
1001 if (digest_request
->qop
) {
1003 /* check the qop is what we expected. */
1004 if (strcmp(digest_request
->qop
, QOP_AUTH
) != 0) {
1005 /* we received a qop option we didn't send */
1006 debugs(29, 2, "Invalid qop option received");
1007 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1008 safe_free(username
);
1013 if (!digest_request
->cnonce
|| digest_request
->cnonce
[0] == '\0') {
1014 debugs(29, 2, "Missing cnonce field");
1015 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1016 safe_free(username
);
1021 if (strlen(digest_request
->nc
) != 8 || strspn(digest_request
->nc
, "0123456789abcdefABCDEF") != 8) {
1022 debugs(29, 2, "invalid nonce count");
1023 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1024 safe_free(username
);
1028 /* cnonce and nc both require qop */
1029 if (digest_request
->cnonce
|| digest_request
->nc
[0] != '\0') {
1030 debugs(29, 2, "missing qop!");
1031 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1032 safe_free(username
);
1037 /** below nonce state dependent **/
1040 nonce
= authenticateDigestNonceFindNonce(digest_request
->nonceb64
);
1041 /* check that we're not being hacked / the username hasn't changed */
1042 if (nonce
&& nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
1043 debugs(29, 2, "Username for the nonce does not equal the username for the request");
1048 /* we couldn't find a matching nonce! */
1049 debugs(29, 2, "Unexpected or invalid nonce received from " << username
);
1050 Auth::UserRequest::Pointer auth_request
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1051 auth_request
->user()->credentials(Auth::Handshake
);
1052 safe_free(username
);
1053 return auth_request
;
1056 digest_request
->nonce
= nonce
;
1057 authDigestNonceLink(nonce
);
1059 /* check that we're not being hacked / the username hasn't changed */
1060 if (nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
1061 debugs(29, 2, "Username for the nonce does not equal the username for the request");
1062 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1063 safe_free(username
);
1067 /* the method we'll check at the authenticate step as well */
1069 /* we don't send or parse opaques. Ok so we're flexable ... */
1072 Auth::Digest::User
*digest_user
;
1074 Auth::User::Pointer auth_user
;
1076 SBuf key
= Auth::User::BuildUserKey(username
, aRequestRealm
);
1077 if (key
.isEmpty() || (auth_user
= findUserInCache(key
.c_str(), Auth::AUTH_DIGEST
)) == NULL
) {
1078 /* the user doesn't exist in the username cache yet */
1079 debugs(29, 9, "Creating new digest user '" << username
<< "'");
1080 digest_user
= new Auth::Digest::User(this, aRequestRealm
);
1081 /* auth_user is a parent */
1082 auth_user
= digest_user
;
1083 /* save the username */
1084 digest_user
->username(username
);
1085 /* set the user type */
1086 digest_user
->auth_type
= Auth::AUTH_DIGEST
;
1087 /* this auth_user struct is the one to get added to the
1089 /* store user in hash's */
1090 digest_user
->addToNameCache();
1093 * Add the digest to the user so we can tell if a hacking
1094 * or spoofing attack is taking place. We do this by assuming
1095 * the user agent won't change user name without warning.
1097 authDigestUserLinkNonce(digest_user
, nonce
);
1099 debugs(29, 9, "Found user '" << username
<< "' in the user cache as '" << auth_user
<< "'");
1100 digest_user
= static_cast<Auth::Digest::User
*>(auth_user
.getRaw());
1101 digest_user
->credentials(Auth::Unchecked
);
1105 /*link the request and the user */
1106 assert(digest_request
!= NULL
);
1108 digest_request
->user(digest_user
);
1109 debugs(29, 9, "username = '" << digest_user
->username() << "'\nrealm = '" <<
1110 digest_request
->realm
<< "'\nqop = '" << digest_request
->qop
<<
1111 "'\nalgorithm = '" << digest_request
->algorithm
<< "'\nuri = '" <<
1112 digest_request
->uri
<< "'\nnonce = '" << digest_request
->nonceb64
<<
1113 "'\nnc = '" << digest_request
->nc
<< "'\ncnonce = '" <<
1114 digest_request
->cnonce
<< "'\nresponse = '" <<
1115 digest_request
->response
<< "'\ndigestnonce = '" << nonce
<< "'");
1117 return digest_request
;