2 * Copyright (C) 1996-2014 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/auth_digest.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"
25 #include "HttpHeaderTools.h"
26 #include "HttpReply.h"
27 #include "HttpRequest.h"
28 #include "mgr/Registration.h"
31 #include "SquidTime.h"
38 static AUTHSSTATS authenticateDigestStats
;
40 helper
*digestauthenticators
= NULL
;
42 static hash_table
*digest_nonce_cache
;
44 static int authdigest_initialised
= 0;
45 static MemAllocator
*digest_nonce_pool
= NULL
;
47 enum http_digest_attr_type
{
60 static const HttpHeaderFieldAttrs DigestAttrs
[DIGEST_ENUM_END
] = {
61 {"username", (http_hdr_type
)DIGEST_USERNAME
},
62 {"realm", (http_hdr_type
)DIGEST_REALM
},
63 {"qop", (http_hdr_type
)DIGEST_QOP
},
64 {"algorithm", (http_hdr_type
)DIGEST_ALGORITHM
},
65 {"uri", (http_hdr_type
)DIGEST_URI
},
66 {"nonce", (http_hdr_type
)DIGEST_NONCE
},
67 {"nc", (http_hdr_type
)DIGEST_NC
},
68 {"cnonce", (http_hdr_type
)DIGEST_CNONCE
},
69 {"response", (http_hdr_type
)DIGEST_RESPONSE
},
72 class HttpHeaderFieldInfo
;
73 static HttpHeaderFieldInfo
*DigestFieldsInfo
= NULL
;
81 static void authenticateDigestNonceCacheCleanup(void *data
);
82 static digest_nonce_h
*authenticateDigestNonceFindNonce(const char *nonceb64
);
83 static void authenticateDigestNonceDelete(digest_nonce_h
* nonce
);
84 static void authenticateDigestNonceSetup(void);
85 static void authDigestNonceEncode(digest_nonce_h
* nonce
);
86 static void authDigestNonceLink(digest_nonce_h
* nonce
);
88 static int authDigestNonceLinks(digest_nonce_h
* nonce
);
90 static void authDigestNonceUserUnlink(digest_nonce_h
* nonce
);
93 authDigestNonceEncode(digest_nonce_h
* nonce
)
101 nonce
->key
= xstrdup(base64_encode_bin((char *) &(nonce
->noncedata
), sizeof(digest_nonce_data
)));
105 authenticateDigestNonceNew(void)
107 digest_nonce_h
*newnonce
= static_cast < digest_nonce_h
* >(digest_nonce_pool
->alloc());
109 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
110 * === EXCERPT FROM RFC 2617 ===
111 * The contents of the nonce are implementation dependent. The quality
112 * of the implementation depends on a good choice. A nonce might, for
113 * example, be constructed as the base 64 encoding of
115 * time-stamp H(time-stamp ":" ETag ":" private-key)
117 * where time-stamp is a server-generated time or other non-repeating
118 * value, ETag is the value of the HTTP ETag header associated with
119 * the requested entity, and private-key is data known only to the
120 * server. With a nonce of this form a server would recalculate the
121 * hash portion after receiving the client authentication header and
122 * reject the request if it did not match the nonce from that header
123 * or if the time-stamp value is not recent enough. In this way the
124 * server can limit the time of the nonce's validity. The inclusion of
125 * the ETag prevents a replay request for an updated version of the
126 * resource. (Note: including the IP address of the client in the
127 * nonce would appear to offer the server the ability to limit the
128 * reuse of the nonce to the same client that originally got it.
129 * However, that would break proxy farms, where requests from a single
130 * user often go through different proxies in the farm. Also, IP
131 * address spoofing is not that hard.)
134 * Now for my reasoning:
135 * We will not accept a unrecognised nonce->we have all recognisable
136 * nonces stored. If we send out unique base64 encodings we guarantee
137 * that a given nonce applies to only one user (barring attacks or
138 * really bad timing with expiry and creation). Using a random
139 * component in the nonce allows us to loop to find a unique nonce.
140 * We use H(nonce_data) so the nonce is meaningless to the reciever.
141 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
142 * And even if our randomness is not very random (probably due to
143 * bad coding on my part) we don't really care - the timestamp and
144 * memory pointer also guarantee local uniqueness in the input to the hash
148 /* create a new nonce */
150 newnonce
->flags
.valid
= true;
151 newnonce
->noncedata
.self
= newnonce
;
152 newnonce
->noncedata
.creationtime
= current_time
.tv_sec
;
153 newnonce
->noncedata
.randomdata
= squid_random();
155 authDigestNonceEncode(newnonce
);
157 * loop until we get a unique nonce. The nonce creation must
158 * have a random factor
161 while (authenticateDigestNonceFindNonce((char const *) (newnonce
->key
))) {
162 /* create a new nonce */
163 newnonce
->noncedata
.randomdata
= squid_random();
164 /* Bug 3526 high performance fix: add 1 second to creationtime to avoid duplication */
165 ++newnonce
->noncedata
.creationtime
;
166 authDigestNonceEncode(newnonce
);
169 hash_join(digest_nonce_cache
, newnonce
);
170 /* the cache's link */
171 authDigestNonceLink(newnonce
);
172 newnonce
->flags
.incache
= true;
173 debugs(29, 5, "created nonce " << newnonce
<< " at " << newnonce
->noncedata
.creationtime
);
178 authenticateDigestNonceDelete(digest_nonce_h
* nonce
)
181 assert(nonce
->references
== 0);
184 if (nonce
->flags
.incache
)
185 hash_remove_link(digest_nonce_cache
, nonce
);
189 assert(!nonce
->flags
.incache
);
191 safe_free(nonce
->key
);
193 digest_nonce_pool
->freeOne(nonce
);
198 authenticateDigestNonceSetup(void)
200 if (!digest_nonce_pool
)
201 digest_nonce_pool
= memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h
));
203 if (!digest_nonce_cache
) {
204 digest_nonce_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
205 assert(digest_nonce_cache
);
206 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->nonceGCInterval
, 1);
211 authenticateDigestNonceShutdown(void)
214 * We empty the cache of any nonces left in there.
216 digest_nonce_h
*nonce
;
218 if (digest_nonce_cache
) {
219 debugs(29, 2, "Shutting down nonce cache");
220 hash_first(digest_nonce_cache
);
222 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
223 assert(nonce
->flags
.incache
);
224 authDigestNoncePurge(nonce
);
229 if (digest_nonce_pool
) {
230 delete digest_nonce_pool
;
231 digest_nonce_pool
= NULL
;
235 debugs(29, 2, "Nonce cache shutdown");
239 authenticateDigestNonceCacheCleanup(void *data
)
242 * We walk the hash by nonceb64 as that is the unique key we
243 * use. For big hash tables we could consider stepping through
244 * the cache, 100/200 entries at a time. Lets see how it flies
247 digest_nonce_h
*nonce
;
248 debugs(29, 3, "Cleaning the nonce cache now");
249 debugs(29, 3, "Current time: " << current_time
.tv_sec
);
250 hash_first(digest_nonce_cache
);
252 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
253 debugs(29, 3, "nonce entry : " << nonce
<< " '" << (char *) nonce
->key
<< "'");
254 debugs(29, 4, "Creation time: " << nonce
->noncedata
.creationtime
);
256 if (authDigestNonceIsStale(nonce
)) {
257 debugs(29, 4, "Removing nonce " << (char *) nonce
->key
<< " from cache due to timeout.");
258 assert(nonce
->flags
.incache
);
259 /* invalidate nonce so future requests fail */
260 nonce
->flags
.valid
= false;
261 /* if it is tied to a auth_user, remove the tie */
262 authDigestNonceUserUnlink(nonce
);
263 authDigestNoncePurge(nonce
);
267 debugs(29, 3, "Finished cleaning the nonce cache.");
269 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->active())
270 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->nonceGCInterval
, 1);
274 authDigestNonceLink(digest_nonce_h
* nonce
)
276 assert(nonce
!= NULL
);
278 debugs(29, 9, "nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
283 authDigestNonceLinks(digest_nonce_h
* nonce
)
288 return nonce
->references
;
294 authDigestNonceUnlink(digest_nonce_h
* nonce
)
296 assert(nonce
!= NULL
);
298 if (nonce
->references
> 0) {
299 -- nonce
->references
;
301 debugs(29, DBG_IMPORTANT
, "Attempt to lower nonce " << nonce
<< " refcount below 0!");
304 debugs(29, 9, "nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
306 if (nonce
->references
== 0)
307 authenticateDigestNonceDelete(nonce
);
311 authenticateDigestNonceNonceb64(const digest_nonce_h
* nonce
)
316 return (char const *) nonce
->key
;
319 static digest_nonce_h
*
320 authenticateDigestNonceFindNonce(const char *nonceb64
)
322 digest_nonce_h
*nonce
= NULL
;
324 if (nonceb64
== NULL
)
327 debugs(29, 9, "looking for nonceb64 '" << nonceb64
<< "' in the nonce cache.");
329 nonce
= static_cast < digest_nonce_h
* >(hash_lookup(digest_nonce_cache
, nonceb64
));
331 if ((nonce
== NULL
) || (strcmp(authenticateDigestNonceNonceb64(nonce
), nonceb64
)))
334 debugs(29, 9, "Found nonce '" << nonce
<< "'");
340 authDigestNonceIsValid(digest_nonce_h
* nonce
, char nc
[9])
343 /* do we have a nonce ? */
348 intnc
= strtol(nc
, NULL
, 16);
350 /* has it already been invalidated ? */
351 if (!nonce
->flags
.valid
) {
352 debugs(29, 4, "Nonce already invalidated");
356 /* is the nonce-count ok ? */
357 if (!static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->CheckNonceCount
) {
358 /* Ignore client supplied NC */
359 intnc
= nonce
->nc
+ 1;
362 if ((static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->NonceStrictness
&& intnc
!= nonce
->nc
+ 1) ||
363 intnc
< nonce
->nc
+ 1) {
364 debugs(29, 4, "Nonce count doesn't match");
365 nonce
->flags
.valid
= false;
369 /* increment the nonce count - we've already checked that intnc is a
370 * valid representation for us, so we don't need the test here.
374 return !authDigestNonceIsStale(nonce
);
378 authDigestNonceIsStale(digest_nonce_h
* nonce
)
380 /* do we have a nonce ? */
385 /* Is it already invalidated? */
386 if (!nonce
->flags
.valid
)
389 /* has it's max duration expired? */
390 if (nonce
->noncedata
.creationtime
+ static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxduration
< current_time
.tv_sec
) {
391 debugs(29, 4, "Nonce is too old. " <<
392 nonce
->noncedata
.creationtime
<< " " <<
393 static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxduration
<< " " <<
394 current_time
.tv_sec
);
396 nonce
->flags
.valid
= false;
400 if (nonce
->nc
> 99999998) {
401 debugs(29, 4, "Nonce count overflow");
402 nonce
->flags
.valid
= false;
406 if (nonce
->nc
> static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxuses
) {
407 debugs(29, 4, "Nonce count over user limit");
408 nonce
->flags
.valid
= false;
417 * \retval 0 the digest is not stale yet
418 * \retval -1 the digest will be stale on the next request
421 authDigestNonceLastRequest(digest_nonce_h
* nonce
)
426 if (nonce
->nc
== 99999997) {
427 debugs(29, 4, "Nonce count about to overflow");
431 if (nonce
->nc
>= static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxuses
- 1) {
432 debugs(29, 4, "Nonce count about to hit user limit");
436 /* and other tests are possible. */
441 authDigestNoncePurge(digest_nonce_h
* nonce
)
446 if (!nonce
->flags
.incache
)
449 hash_remove_link(digest_nonce_cache
, nonce
);
451 nonce
->flags
.incache
= false;
453 /* the cache's link */
454 authDigestNonceUnlink(nonce
);
458 Auth::Digest::Config::rotateHelpers()
460 /* schedule closure of existing helpers */
461 if (digestauthenticators
) {
462 helperShutdown(digestauthenticators
);
465 /* NP: dynamic helper restart will ensure they start up again as needed. */
469 Auth::Digest::Config::dump(StoreEntry
* entry
, const char *name
, Auth::Config
* scheme
) const
471 if (!Auth::Config::dump(entry
, name
, scheme
))
474 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",
475 name
, "digest", noncemaxuses
,
476 name
, "digest", (int) noncemaxduration
,
477 name
, "digest", (int) nonceGCInterval
);
478 storeAppendPrintf(entry
, "%s digest utf8 %s\n", name
, utf8
? "on" : "off");
483 Auth::Digest::Config::active() const
485 return authdigest_initialised
== 1;
489 Auth::Digest::Config::configured() const
491 if ((authenticateProgram
!= NULL
) &&
492 (authenticateChildren
.n_max
!= 0) &&
493 !realm
.isEmpty() && (noncemaxduration
> -1))
499 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
501 Auth::Digest::Config::fixHeader(Auth::UserRequest::Pointer auth_user_request
, HttpReply
*rep
, http_hdr_type hdrType
, HttpRequest
* request
)
503 if (!authenticateProgram
)
507 digest_nonce_h
*nonce
= NULL
;
509 /* on a 407 or 401 we always use a new nonce */
510 if (auth_user_request
!= NULL
) {
511 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
514 stale
= digest_user
->credentials() == Auth::Handshake
;
516 nonce
= digest_user
->currentNonce();
521 nonce
= authenticateDigestNonceNew();
524 debugs(29, 9, "Sending type:" << hdrType
<<
525 " header: 'Digest realm=\"" << realm
<< "\", nonce=\"" <<
526 authenticateDigestNonceNonceb64(nonce
) << "\", qop=\"" << QOP_AUTH
<<
527 "\", stale=" << (stale
? "true" : "false"));
529 /* in the future, for WWW auth we may want to support the domain entry */
530 httpHeaderPutStrf(&rep
->header
, hdrType
, "Digest realm=\"" SQUIDSBUFPH
"\", nonce=\"%s\", qop=\"%s\", stale=%s",
531 SQUIDSBUFPRINT(realm
), authenticateDigestNonceNonceb64(nonce
), QOP_AUTH
, stale
? "true" : "false");
534 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
537 Auth::Digest::Config::init(Auth::Config
* scheme
)
539 if (authenticateProgram
) {
540 DigestFieldsInfo
= httpHeaderBuildFieldsInfo(DigestAttrs
, DIGEST_ENUM_END
);
541 authenticateDigestNonceSetup();
542 authdigest_initialised
= 1;
544 if (digestauthenticators
== NULL
)
545 digestauthenticators
= new helper("digestauthenticator");
547 digestauthenticators
->cmdline
= authenticateProgram
;
549 digestauthenticators
->childs
.updateLimits(authenticateChildren
);
551 digestauthenticators
->ipc_type
= IPC_STREAM
;
553 helperOpenServers(digestauthenticators
);
558 Auth::Digest::Config::registerWithCacheManager(void)
560 Mgr::RegisterAction("digestauthenticator",
561 "Digest User Authenticator Stats",
562 authenticateDigestStats
, 0, 1);
565 /* free any allocated configuration details */
567 Auth::Digest::Config::done()
569 Auth::Config::done();
571 authdigest_initialised
= 0;
573 if (digestauthenticators
)
574 helperShutdown(digestauthenticators
);
576 if (DigestFieldsInfo
) {
577 httpHeaderDestroyFieldsInfo(DigestFieldsInfo
, DIGEST_ENUM_END
);
578 DigestFieldsInfo
= NULL
;
584 delete digestauthenticators
;
585 digestauthenticators
= NULL
;
587 if (authenticateProgram
)
588 wordlistDestroy(&authenticateProgram
);
591 Auth::Digest::Config::Config() :
592 nonceGCInterval(5*60),
593 noncemaxduration(30*60),
602 Auth::Digest::Config::parse(Auth::Config
* scheme
, int n_configured
, char *param_str
)
604 if (strcmp(param_str
, "program") == 0) {
605 if (authenticateProgram
)
606 wordlistDestroy(&authenticateProgram
);
608 parse_wordlist(&authenticateProgram
);
610 requirePathnameExists("auth_param digest program", authenticateProgram
->key
);
611 } else if (strcmp(param_str
, "nonce_garbage_interval") == 0) {
612 parse_time_t(&nonceGCInterval
);
613 } else if (strcmp(param_str
, "nonce_max_duration") == 0) {
614 parse_time_t(&noncemaxduration
);
615 } else if (strcmp(param_str
, "nonce_max_count") == 0) {
616 parse_int((int *) &noncemaxuses
);
617 } else if (strcmp(param_str
, "nonce_strictness") == 0) {
618 parse_onoff(&NonceStrictness
);
619 } else if (strcmp(param_str
, "check_nonce_count") == 0) {
620 parse_onoff(&CheckNonceCount
);
621 } else if (strcmp(param_str
, "post_workaround") == 0) {
622 parse_onoff(&PostWorkaround
);
623 } else if (strcmp(param_str
, "utf8") == 0) {
626 Auth::Config::parse(scheme
, n_configured
, param_str
);
630 Auth::Digest::Config::type() const
632 return Auth::Digest::Scheme::GetInstance()->type();
636 authenticateDigestStats(StoreEntry
* sentry
)
638 helperStats(sentry
, digestauthenticators
, "Digest Authenticator Statistics");
641 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
644 authDigestNonceUserUnlink(digest_nonce_h
* nonce
)
646 Auth::Digest::User
*digest_user
;
647 dlink_node
*link
, *tmplink
;
655 digest_user
= nonce
->user
;
657 /* unlink from the user list. Yes we're crossing structures but this is the only
658 * time this code is needed
660 link
= digest_user
->nonces
.head
;
666 if (tmplink
->data
== nonce
) {
667 dlinkDelete(tmplink
, &digest_user
->nonces
);
668 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
669 dlinkNodeDelete(tmplink
);
674 /* this reference to user was not locked because freeeing the user frees
680 /* authDigesteserLinkNonce: add a nonce to a given user's struct */
682 authDigestUserLinkNonce(Auth::Digest::User
* user
, digest_nonce_h
* nonce
)
686 if (!user
|| !nonce
|| !nonce
->user
)
689 Auth::Digest::User
*digest_user
= user
;
691 node
= digest_user
->nonces
.head
;
693 while (node
&& (node
->data
!= nonce
))
699 node
= dlinkNodeNew();
701 dlinkAddTail(nonce
, node
, &digest_user
->nonces
);
703 authDigestNonceLink(nonce
);
705 /* ping this nonce to this auth user */
706 assert((nonce
->user
== NULL
) || (nonce
->user
== user
));
708 /* we don't lock this reference because removing the user removes the
709 * hash too. Of course if that changes we're stuffed so read the code huh?
714 /* setup the necessary info to log the username */
715 static Auth::UserRequest::Pointer
716 authDigestLogUsername(char *username
, Auth::UserRequest::Pointer auth_user_request
, const char *requestRealm
)
718 assert(auth_user_request
!= NULL
);
720 /* log the username */
721 debugs(29, 9, "Creating new user for logging '" << (username
?username
:"[no username]") << "'");
722 Auth::User::Pointer digest_user
= new Auth::Digest::User(static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest")), requestRealm
);
723 /* save the credentials */
724 digest_user
->username(username
);
725 /* set the auth_user type */
726 digest_user
->auth_type
= Auth::AUTH_BROKEN
;
727 /* link the request to the user */
728 auth_user_request
->user(digest_user
);
729 return auth_user_request
;
733 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
734 * Auth_user structure.
736 Auth::UserRequest::Pointer
737 Auth::Digest::Config::decode(char const *proxy_auth
, const char *aRequestRealm
)
741 const char *pos
= NULL
;
742 char *username
= NULL
;
743 digest_nonce_h
*nonce
;
746 debugs(29, 9, "beginning");
748 Auth::Digest::UserRequest
*digest_request
= new Auth::Digest::UserRequest();
750 /* trim DIGEST from string */
752 while (xisgraph(*proxy_auth
))
755 /* Trim leading whitespace before decoding */
756 while (xisspace(*proxy_auth
))
759 String
temp(proxy_auth
);
761 while (strListGetItem(&temp
, ',', &item
, &ilen
, &pos
)) {
762 /* isolate directive name & value */
765 if ((p
= (const char *)memchr(item
, '=', ilen
)) && (p
- item
< ilen
)) {
768 vlen
= ilen
- (p
- item
);
774 SBuf
keyName(item
, nlen
);
778 // see RFC 2617 section 3.2.1 and 3.2.2 for details on the BNF
780 if (keyName
== SBuf("domain",6) || keyName
== SBuf("uri",3)) {
781 // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"'
782 // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain
783 if (*p
== '"' && *(p
+ vlen
-1) == '"') {
784 value
.limitInit(p
+1, vlen
-2);
786 } else if (keyName
== SBuf("qop",3)) {
787 // qop is more special.
788 // On request this must not be quoted-string de-quoted. But is several values wrapped in '"'
789 // On response this is a single un-quoted token.
790 if (*p
== '"' && *(p
+ vlen
-1) == '"') {
791 value
.limitInit(p
+1, vlen
-2);
793 value
.limitInit(p
, vlen
);
795 } else if (*p
== '"') {
796 if (!httpHeaderParseQuotedString(p
, vlen
, &value
)) {
797 debugs(29, 9, "Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
801 value
.limitInit(p
, vlen
);
804 debugs(29, 9, "Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
809 http_digest_attr_type t
= (http_digest_attr_type
)httpHeaderIdByName(item
, nlen
, DigestFieldsInfo
, DIGEST_ENUM_END
);
812 case DIGEST_USERNAME
:
814 if (value
.size() != 0)
815 username
= xstrndup(value
.rawBuf(), value
.size() + 1);
816 debugs(29, 9, "Found Username '" << username
<< "'");
820 safe_free(digest_request
->realm
);
821 if (value
.size() != 0)
822 digest_request
->realm
= xstrndup(value
.rawBuf(), value
.size() + 1);
823 debugs(29, 9, "Found realm '" << digest_request
->realm
<< "'");
827 safe_free(digest_request
->qop
);
828 if (value
.size() != 0)
829 digest_request
->qop
= xstrndup(value
.rawBuf(), value
.size() + 1);
830 debugs(29, 9, "Found qop '" << digest_request
->qop
<< "'");
833 case DIGEST_ALGORITHM
:
834 safe_free(digest_request
->algorithm
);
835 if (value
.size() != 0)
836 digest_request
->algorithm
= xstrndup(value
.rawBuf(), value
.size() + 1);
837 debugs(29, 9, "Found algorithm '" << digest_request
->algorithm
<< "'");
841 safe_free(digest_request
->uri
);
842 if (value
.size() != 0)
843 digest_request
->uri
= xstrndup(value
.rawBuf(), value
.size() + 1);
844 debugs(29, 9, "Found uri '" << digest_request
->uri
<< "'");
848 safe_free(digest_request
->nonceb64
);
849 if (value
.size() != 0)
850 digest_request
->nonceb64
= xstrndup(value
.rawBuf(), value
.size() + 1);
851 debugs(29, 9, "Found nonce '" << digest_request
->nonceb64
<< "'");
855 if (value
.size() != 8) {
856 debugs(29, 9, "Invalid nc '" << value
<< "' in '" << temp
<< "'");
858 xstrncpy(digest_request
->nc
, value
.rawBuf(), value
.size() + 1);
859 debugs(29, 9, "Found noncecount '" << digest_request
->nc
<< "'");
863 safe_free(digest_request
->cnonce
);
864 if (value
.size() != 0)
865 digest_request
->cnonce
= xstrndup(value
.rawBuf(), value
.size() + 1);
866 debugs(29, 9, "Found cnonce '" << digest_request
->cnonce
<< "'");
869 case DIGEST_RESPONSE
:
870 safe_free(digest_request
->response
);
871 if (value
.size() != 0)
872 digest_request
->response
= xstrndup(value
.rawBuf(), value
.size() + 1);
873 debugs(29, 9, "Found response '" << digest_request
->response
<< "'");
877 debugs(29, 3, "Unknown attribute '" << item
<< "' in '" << temp
<< "'");
884 /* now we validate the data given to us */
887 * TODO: on invalid parameters we should return 400, not 407.
888 * Find some clean way of doing this. perhaps return a valid
889 * struct, and set the direction to clientwards combined with
890 * a change to the clientwards handling code (ie let the
891 * clientwards call set the error type (but limited to known
892 * correct values - 400/401/407
895 /* 2069 requirements */
898 Auth::UserRequest::Pointer rv
;
899 /* do we have a username ? */
900 if (!username
|| username
[0] == '\0') {
901 debugs(29, 2, "Empty or not present username");
902 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
907 /* Sanity check of the username.
908 * " can not be allowed in usernames until * the digest helper protocol
911 if (strchr(username
, '"')) {
912 debugs(29, 2, "Unacceptable username '" << username
<< "'");
913 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
918 /* do we have a realm ? */
919 if (!digest_request
->realm
|| digest_request
->realm
[0] == '\0') {
920 debugs(29, 2, "Empty or not present realm");
921 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
927 if (!digest_request
->nonceb64
|| digest_request
->nonceb64
[0] == '\0') {
928 debugs(29, 2, "Empty or not present nonce");
929 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
934 /* we can't check the URI just yet. We'll check it in the
935 * authenticate phase, but needs to be given */
936 if (!digest_request
->uri
|| digest_request
->uri
[0] == '\0') {
937 debugs(29, 2, "Missing URI field");
938 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
943 /* is the response the correct length? */
944 if (!digest_request
->response
|| strlen(digest_request
->response
) != 32) {
945 debugs(29, 2, "Response length invalid");
946 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
951 /* check the algorithm is present and supported */
952 if (!digest_request
->algorithm
)
953 digest_request
->algorithm
= xstrndup("MD5", 4);
954 else if (strcmp(digest_request
->algorithm
, "MD5")
955 && strcmp(digest_request
->algorithm
, "MD5-sess")) {
956 debugs(29, 2, "invalid algorithm specified!");
957 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
962 /* 2617 requirements, indicated by qop */
963 if (digest_request
->qop
) {
965 /* check the qop is what we expected. */
966 if (strcmp(digest_request
->qop
, QOP_AUTH
) != 0) {
967 /* we received a qop option we didn't send */
968 debugs(29, 2, "Invalid qop option received");
969 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
975 if (!digest_request
->cnonce
|| digest_request
->cnonce
[0] == '\0') {
976 debugs(29, 2, "Missing cnonce field");
977 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
983 if (strlen(digest_request
->nc
) != 8 || strspn(digest_request
->nc
, "0123456789abcdefABCDEF") != 8) {
984 debugs(29, 2, "invalid nonce count");
985 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
990 /* cnonce and nc both require qop */
991 if (digest_request
->cnonce
|| digest_request
->nc
[0] != '\0') {
992 debugs(29, 2, "missing qop!");
993 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
999 /** below nonce state dependent **/
1002 nonce
= authenticateDigestNonceFindNonce(digest_request
->nonceb64
);
1003 /* check that we're not being hacked / the username hasn't changed */
1004 if (nonce
&& nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
1005 debugs(29, 2, "Username for the nonce does not equal the username for the request");
1008 /* check for stale nonce */
1009 if (authDigestNonceIsStale(nonce
)) {
1010 debugs(29, 3, "The received nonce is stale from " << username
);
1011 digest_request
->setDenyMessage("Stale nonce");
1015 /* we couldn't find a matching nonce! */
1016 debugs(29, 2, "Unexpected or invalid nonce received from " << username
);
1017 Auth::UserRequest::Pointer auth_request
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1018 auth_request
->user()->credentials(Auth::Handshake
);
1019 safe_free(username
);
1020 return auth_request
;
1023 digest_request
->nonce
= nonce
;
1024 authDigestNonceLink(nonce
);
1026 /* check that we're not being hacked / the username hasn't changed */
1027 if (nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
1028 debugs(29, 2, "Username for the nonce does not equal the username for the request");
1029 rv
= authDigestLogUsername(username
, digest_request
, aRequestRealm
);
1030 safe_free(username
);
1034 /* the method we'll check at the authenticate step as well */
1036 /* we don't send or parse opaques. Ok so we're flexable ... */
1039 Auth::Digest::User
*digest_user
;
1041 Auth::User::Pointer auth_user
;
1043 SBuf key
= Auth::User::BuildUserKey(username
, aRequestRealm
);
1044 if (key
.isEmpty() || (auth_user
= findUserInCache(key
.c_str(), Auth::AUTH_DIGEST
)) == NULL
) {
1045 /* the user doesn't exist in the username cache yet */
1046 debugs(29, 9, "Creating new digest user '" << username
<< "'");
1047 digest_user
= new Auth::Digest::User(this, aRequestRealm
);
1048 /* auth_user is a parent */
1049 auth_user
= digest_user
;
1050 /* save the username */
1051 digest_user
->username(username
);
1052 /* set the user type */
1053 digest_user
->auth_type
= Auth::AUTH_DIGEST
;
1054 /* this auth_user struct is the one to get added to the
1056 /* store user in hash's */
1057 digest_user
->addToNameCache();
1060 * Add the digest to the user so we can tell if a hacking
1061 * or spoofing attack is taking place. We do this by assuming
1062 * the user agent won't change user name without warning.
1064 authDigestUserLinkNonce(digest_user
, nonce
);
1066 debugs(29, 9, "Found user '" << username
<< "' in the user cache as '" << auth_user
<< "'");
1067 digest_user
= static_cast<Auth::Digest::User
*>(auth_user
.getRaw());
1068 digest_user
->credentials(Auth::Unchecked
);
1072 /*link the request and the user */
1073 assert(digest_request
!= NULL
);
1075 digest_request
->user(digest_user
);
1076 debugs(29, 9, "username = '" << digest_user
->username() << "'\nrealm = '" <<
1077 digest_request
->realm
<< "'\nqop = '" << digest_request
->qop
<<
1078 "'\nalgorithm = '" << digest_request
->algorithm
<< "'\nuri = '" <<
1079 digest_request
->uri
<< "'\nnonce = '" << digest_request
->nonceb64
<<
1080 "'\nnc = '" << digest_request
->nc
<< "'\ncnonce = '" <<
1081 digest_request
->cnonce
<< "'\nresponse = '" <<
1082 digest_request
->response
<< "'\ndigestnonce = '" << nonce
<< "'");
1084 return digest_request
;