2 * DEBUG: section 29 Authenticator
3 * AUTHOR: Robert Collins
5 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from the
9 * Internet community. Development is led by Duane Wessels of the
10 * National Laboratory for Applied Network Research and funded by the
11 * National Science Foundation. Squid is Copyrighted (C) 1998 by
12 * the Regents of the University of California. Please see the
13 * COPYRIGHT file for full details. Squid incorporates software
14 * developed and/or copyrighted by other sources. Please see the
15 * CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 /* The functions in this file handle authentication.
34 * They DO NOT perform access control or auditing.
35 * See acl.c for access control and client_side.c for auditing */
38 #include "auth/digest/auth_digest.h"
39 #include "auth/digest/Scheme.h"
40 #include "auth/digest/User.h"
41 #include "auth/digest/UserRequest.h"
42 #include "auth/Gadgets.h"
43 #include "auth/State.h"
44 #include "base/StringArea.h"
48 #include "HttpHeaderTools.h"
49 #include "HttpReply.h"
50 #include "HttpRequest.h"
51 #include "mgr/Registration.h"
53 #include "SquidTime.h"
60 static AUTHSSTATS authenticateDigestStats
;
62 helper
*digestauthenticators
= NULL
;
64 static hash_table
*digest_nonce_cache
;
66 static int authdigest_initialised
= 0;
67 static MemAllocator
*digest_nonce_pool
= NULL
;
69 enum http_digest_attr_type
{
82 static const HttpHeaderFieldAttrs DigestAttrs
[DIGEST_ENUM_END
] = {
83 {"username", (http_hdr_type
)DIGEST_USERNAME
},
84 {"realm", (http_hdr_type
)DIGEST_REALM
},
85 {"qop", (http_hdr_type
)DIGEST_QOP
},
86 {"algorithm", (http_hdr_type
)DIGEST_ALGORITHM
},
87 {"uri", (http_hdr_type
)DIGEST_URI
},
88 {"nonce", (http_hdr_type
)DIGEST_NONCE
},
89 {"nc", (http_hdr_type
)DIGEST_NC
},
90 {"cnonce", (http_hdr_type
)DIGEST_CNONCE
},
91 {"response", (http_hdr_type
)DIGEST_RESPONSE
},
94 class HttpHeaderFieldInfo
;
95 static HttpHeaderFieldInfo
*DigestFieldsInfo
= NULL
;
103 static void authenticateDigestNonceCacheCleanup(void *data
);
104 static digest_nonce_h
*authenticateDigestNonceFindNonce(const char *nonceb64
);
105 static digest_nonce_h
*authenticateDigestNonceNew(void);
106 static void authenticateDigestNonceDelete(digest_nonce_h
* nonce
);
107 static void authenticateDigestNonceSetup(void);
108 static int authDigestNonceIsStale(digest_nonce_h
* nonce
);
109 static void authDigestNonceEncode(digest_nonce_h
* nonce
);
110 static void authDigestNonceLink(digest_nonce_h
* nonce
);
112 static int authDigestNonceLinks(digest_nonce_h
* nonce
);
114 static void authDigestNonceUserUnlink(digest_nonce_h
* nonce
);
117 authDigestNonceEncode(digest_nonce_h
* nonce
)
125 nonce
->key
= xstrdup(base64_encode_bin((char *) &(nonce
->noncedata
), sizeof(digest_nonce_data
)));
128 static digest_nonce_h
*
129 authenticateDigestNonceNew(void)
131 digest_nonce_h
*newnonce
= static_cast < digest_nonce_h
* >(digest_nonce_pool
->alloc());
133 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
134 * === EXCERPT FROM RFC 2617 ===
135 * The contents of the nonce are implementation dependent. The quality
136 * of the implementation depends on a good choice. A nonce might, for
137 * example, be constructed as the base 64 encoding of
139 * time-stamp H(time-stamp ":" ETag ":" private-key)
141 * where time-stamp is a server-generated time or other non-repeating
142 * value, ETag is the value of the HTTP ETag header associated with
143 * the requested entity, and private-key is data known only to the
144 * server. With a nonce of this form a server would recalculate the
145 * hash portion after receiving the client authentication header and
146 * reject the request if it did not match the nonce from that header
147 * or if the time-stamp value is not recent enough. In this way the
148 * server can limit the time of the nonce's validity. The inclusion of
149 * the ETag prevents a replay request for an updated version of the
150 * resource. (Note: including the IP address of the client in the
151 * nonce would appear to offer the server the ability to limit the
152 * reuse of the nonce to the same client that originally got it.
153 * However, that would break proxy farms, where requests from a single
154 * user often go through different proxies in the farm. Also, IP
155 * address spoofing is not that hard.)
158 * Now for my reasoning:
159 * We will not accept a unrecognised nonce->we have all recognisable
160 * nonces stored. If we send out unique base64 encodings we guarantee
161 * that a given nonce applies to only one user (barring attacks or
162 * really bad timing with expiry and creation). Using a random
163 * component in the nonce allows us to loop to find a unique nonce.
164 * We use H(nonce_data) so the nonce is meaningless to the reciever.
165 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
166 * And even if our randomness is not very random (probably due to
167 * bad coding on my part) we don't really care - the timestamp and
168 * memory pointer also guarantee local uniqueness in the input to the hash
172 /* create a new nonce */
174 newnonce
->flags
.valid
= true;
175 newnonce
->noncedata
.self
= newnonce
;
176 newnonce
->noncedata
.creationtime
= current_time
.tv_sec
;
177 newnonce
->noncedata
.randomdata
= squid_random();
179 authDigestNonceEncode(newnonce
);
181 * loop until we get a unique nonce. The nonce creation must
182 * have a random factor
185 while (authenticateDigestNonceFindNonce((char const *) (newnonce
->key
))) {
186 /* create a new nonce */
187 newnonce
->noncedata
.randomdata
= squid_random();
188 /* Bug 3526 high performance fix: add 1 second to creationtime to avoid duplication */
189 ++newnonce
->noncedata
.creationtime
;
190 authDigestNonceEncode(newnonce
);
193 hash_join(digest_nonce_cache
, newnonce
);
194 /* the cache's link */
195 authDigestNonceLink(newnonce
);
196 newnonce
->flags
.incache
= true;
197 debugs(29, 5, "authenticateDigestNonceNew: created nonce " << newnonce
<< " at " << newnonce
->noncedata
.creationtime
);
202 authenticateDigestNonceDelete(digest_nonce_h
* nonce
)
205 assert(nonce
->references
== 0);
208 if (nonce
->flags
.incache
)
209 hash_remove_link(digest_nonce_cache
, nonce
);
213 assert(!nonce
->flags
.incache
);
215 safe_free(nonce
->key
);
217 digest_nonce_pool
->freeOne(nonce
);
222 authenticateDigestNonceSetup(void)
224 if (!digest_nonce_pool
)
225 digest_nonce_pool
= memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h
));
227 if (!digest_nonce_cache
) {
228 digest_nonce_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
229 assert(digest_nonce_cache
);
230 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->nonceGCInterval
, 1);
235 authenticateDigestNonceShutdown(void)
238 * We empty the cache of any nonces left in there.
240 digest_nonce_h
*nonce
;
242 if (digest_nonce_cache
) {
243 debugs(29, 2, "authenticateDigestNonceShutdown: Shutting down nonce cache ");
244 hash_first(digest_nonce_cache
);
246 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
247 assert(nonce
->flags
.incache
);
248 authDigestNoncePurge(nonce
);
253 if (digest_nonce_pool
) {
254 delete digest_nonce_pool
;
255 digest_nonce_pool
= NULL
;
259 debugs(29, 2, "authenticateDigestNonceShutdown: Nonce cache shutdown");
263 authenticateDigestNonceCacheCleanup(void *data
)
266 * We walk the hash by nonceb64 as that is the unique key we
267 * use. For big hash tables we could consider stepping through
268 * the cache, 100/200 entries at a time. Lets see how it flies
271 digest_nonce_h
*nonce
;
272 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now");
273 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Current time: " << current_time
.tv_sec
);
274 hash_first(digest_nonce_cache
);
276 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
277 debugs(29, 3, "authenticateDigestNonceCacheCleanup: nonce entry : " << nonce
<< " '" << (char *) nonce
->key
<< "'");
278 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Creation time: " << nonce
->noncedata
.creationtime
);
280 if (authDigestNonceIsStale(nonce
)) {
281 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Removing nonce " << (char *) nonce
->key
<< " from cache due to timeout.");
282 assert(nonce
->flags
.incache
);
283 /* invalidate nonce so future requests fail */
284 nonce
->flags
.valid
= false;
285 /* if it is tied to a auth_user, remove the tie */
286 authDigestNonceUserUnlink(nonce
);
287 authDigestNoncePurge(nonce
);
291 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.");
293 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->active())
294 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->nonceGCInterval
, 1);
298 authDigestNonceLink(digest_nonce_h
* nonce
)
300 assert(nonce
!= NULL
);
302 debugs(29, 9, "authDigestNonceLink: nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
307 authDigestNonceLinks(digest_nonce_h
* nonce
)
312 return nonce
->references
;
318 authDigestNonceUnlink(digest_nonce_h
* nonce
)
320 assert(nonce
!= NULL
);
322 if (nonce
->references
> 0) {
323 -- nonce
->references
;
325 debugs(29, DBG_IMPORTANT
, "authDigestNonceUnlink; Attempt to lower nonce " << nonce
<< " refcount below 0!");
328 debugs(29, 9, "authDigestNonceUnlink: nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
330 if (nonce
->references
== 0)
331 authenticateDigestNonceDelete(nonce
);
335 authenticateDigestNonceNonceb64(const digest_nonce_h
* nonce
)
340 return (char const *) nonce
->key
;
343 static digest_nonce_h
*
344 authenticateDigestNonceFindNonce(const char *nonceb64
)
346 digest_nonce_h
*nonce
= NULL
;
348 if (nonceb64
== NULL
)
351 debugs(29, 9, "authDigestNonceFindNonce:looking for nonceb64 '" << nonceb64
<< "' in the nonce cache.");
353 nonce
= static_cast < digest_nonce_h
* >(hash_lookup(digest_nonce_cache
, nonceb64
));
355 if ((nonce
== NULL
) || (strcmp(authenticateDigestNonceNonceb64(nonce
), nonceb64
)))
358 debugs(29, 9, "authDigestNonceFindNonce: Found nonce '" << nonce
<< "'");
364 authDigestNonceIsValid(digest_nonce_h
* nonce
, char nc
[9])
367 /* do we have a nonce ? */
372 intnc
= strtol(nc
, NULL
, 16);
374 /* has it already been invalidated ? */
375 if (!nonce
->flags
.valid
) {
376 debugs(29, 4, "authDigestNonceIsValid: Nonce already invalidated");
380 /* is the nonce-count ok ? */
381 if (!static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->CheckNonceCount
) {
383 return -1; /* forced OK by configuration */
386 if ((static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->NonceStrictness
&& intnc
!= nonce
->nc
+ 1) ||
387 intnc
< nonce
->nc
+ 1) {
388 debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match");
389 nonce
->flags
.valid
= false;
394 /* increment the nonce count - we've already checked that intnc is a
395 * valid representation for us, so we don't need the test here.
403 authDigestNonceIsStale(digest_nonce_h
* nonce
)
405 /* do we have a nonce ? */
410 /* has it's max duration expired? */
411 if (nonce
->noncedata
.creationtime
+ static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxduration
< current_time
.tv_sec
) {
412 debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " <<
413 nonce
->noncedata
.creationtime
<< " " <<
414 static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxduration
<< " " <<
415 current_time
.tv_sec
);
417 nonce
->flags
.valid
= false;
421 if (nonce
->nc
> 99999998) {
422 debugs(29, 4, "authDigestNonceIsStale: Nonce count overflow");
423 nonce
->flags
.valid
= false;
427 if (nonce
->nc
> static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxuses
) {
428 debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit");
429 nonce
->flags
.valid
= false;
438 * \retval 0 the digest is not stale yet
439 * \retval -1 the digest will be stale on the next request
442 authDigestNonceLastRequest(digest_nonce_h
* nonce
)
447 if (nonce
->nc
== 99999997) {
448 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to overflow");
452 if (nonce
->nc
>= static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->noncemaxuses
- 1) {
453 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit");
457 /* and other tests are possible. */
462 authDigestNoncePurge(digest_nonce_h
* nonce
)
467 if (!nonce
->flags
.incache
)
470 hash_remove_link(digest_nonce_cache
, nonce
);
472 nonce
->flags
.incache
= false;
474 /* the cache's link */
475 authDigestNonceUnlink(nonce
);
478 /* USER related functions */
479 static Auth::User::Pointer
480 authDigestUserFindUsername(const char *username
)
482 AuthUserHashPointer
*usernamehash
;
483 debugs(29, 9, HERE
<< "Looking for user '" << username
<< "'");
485 if (username
&& (usernamehash
= static_cast < AuthUserHashPointer
* >(hash_lookup(proxy_auth_username_cache
, username
)))) {
486 while ((usernamehash
->user()->auth_type
!= Auth::AUTH_DIGEST
) && (usernamehash
->next
))
487 usernamehash
= static_cast<AuthUserHashPointer
*>(usernamehash
->next
);
489 if (usernamehash
->user()->auth_type
== Auth::AUTH_DIGEST
) {
490 return usernamehash
->user();
498 Auth::Digest::Config::rotateHelpers()
500 /* schedule closure of existing helpers */
501 if (digestauthenticators
) {
502 helperShutdown(digestauthenticators
);
505 /* NP: dynamic helper restart will ensure they start up again as needed. */
509 Auth::Digest::Config::dump(StoreEntry
* entry
, const char *name
, Auth::Config
* scheme
)
511 wordlist
*list
= authenticateProgram
;
512 debugs(29, 9, "authDigestCfgDump: Dumping configuration");
513 storeAppendPrintf(entry
, "%s %s", name
, "digest");
515 while (list
!= NULL
) {
516 storeAppendPrintf(entry
, " %s", list
->key
);
520 storeAppendPrintf(entry
, "\n%s %s realm %s\n%s %s children %d startup=%d idle=%d concurrency=%d\n%s %s nonce_max_count %d\n%s %s nonce_max_duration %d seconds\n%s %s nonce_garbage_interval %d seconds\n",
521 name
, "digest", digestAuthRealm
,
522 name
, "digest", authenticateChildren
.n_max
, authenticateChildren
.n_startup
, authenticateChildren
.n_idle
, authenticateChildren
.concurrency
,
523 name
, "digest", noncemaxuses
,
524 name
, "digest", (int) noncemaxduration
,
525 name
, "digest", (int) nonceGCInterval
);
529 Auth::Digest::Config::active() const
531 return authdigest_initialised
== 1;
535 Auth::Digest::Config::configured() const
537 if ((authenticateProgram
!= NULL
) &&
538 (authenticateChildren
.n_max
!= 0) &&
539 (digestAuthRealm
!= NULL
) && (noncemaxduration
> -1))
545 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
547 Auth::Digest::Config::fixHeader(Auth::UserRequest::Pointer auth_user_request
, HttpReply
*rep
, http_hdr_type hdrType
, HttpRequest
* request
)
549 if (!authenticateProgram
)
554 if (auth_user_request
!= NULL
) {
555 Auth::Digest::UserRequest
*digest_request
= dynamic_cast<Auth::Digest::UserRequest
*>(auth_user_request
.getRaw());
556 assert (digest_request
!= NULL
);
558 stale
= !digest_request
->flags
.invalid_password
;
561 /* on a 407 or 401 we always use a new nonce */
562 digest_nonce_h
*nonce
= authenticateDigestNonceNew();
564 debugs(29, 9, HERE
<< "Sending type:" << hdrType
<<
565 " header: 'Digest realm=\"" << digestAuthRealm
<< "\", nonce=\"" <<
566 authenticateDigestNonceNonceb64(nonce
) << "\", qop=\"" << QOP_AUTH
<<
567 "\", stale=" << (stale
? "true" : "false"));
569 /* in the future, for WWW auth we may want to support the domain entry */
570 httpHeaderPutStrf(&rep
->header
, hdrType
, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestAuthRealm
, authenticateDigestNonceNonceb64(nonce
), QOP_AUTH
, stale
? "true" : "false");
573 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
576 Auth::Digest::Config::init(Auth::Config
* scheme
)
578 if (authenticateProgram
) {
579 DigestFieldsInfo
= httpHeaderBuildFieldsInfo(DigestAttrs
, DIGEST_ENUM_END
);
580 authenticateDigestNonceSetup();
581 authdigest_initialised
= 1;
583 if (digestauthenticators
== NULL
)
584 digestauthenticators
= new helper("digestauthenticator");
586 digestauthenticators
->cmdline
= authenticateProgram
;
588 digestauthenticators
->childs
.updateLimits(authenticateChildren
);
590 digestauthenticators
->ipc_type
= IPC_STREAM
;
592 helperOpenServers(digestauthenticators
);
597 Auth::Digest::Config::registerWithCacheManager(void)
599 Mgr::RegisterAction("digestauthenticator",
600 "Digest User Authenticator Stats",
601 authenticateDigestStats
, 0, 1);
604 /* free any allocated configuration details */
606 Auth::Digest::Config::done()
608 authdigest_initialised
= 0;
610 if (digestauthenticators
)
611 helperShutdown(digestauthenticators
);
613 if (DigestFieldsInfo
) {
614 httpHeaderDestroyFieldsInfo(DigestFieldsInfo
, DIGEST_ENUM_END
);
615 DigestFieldsInfo
= NULL
;
621 delete digestauthenticators
;
622 digestauthenticators
= NULL
;
624 if (authenticateProgram
)
625 wordlistDestroy(&authenticateProgram
);
627 safe_free(digestAuthRealm
);
630 Auth::Digest::Config::Config() :
631 digestAuthRealm(NULL
),
632 nonceGCInterval(5*60),
633 noncemaxduration(30*60),
642 Auth::Digest::Config::parse(Auth::Config
* scheme
, int n_configured
, char *param_str
)
644 if (strcmp(param_str
, "program") == 0) {
645 if (authenticateProgram
)
646 wordlistDestroy(&authenticateProgram
);
648 parse_wordlist(&authenticateProgram
);
650 requirePathnameExists("auth_param digest program", authenticateProgram
->key
);
651 } else if (strcmp(param_str
, "children") == 0) {
652 authenticateChildren
.parseConfig();
653 } else if (strcmp(param_str
, "realm") == 0) {
654 parse_eol(&digestAuthRealm
);
655 } else if (strcmp(param_str
, "nonce_garbage_interval") == 0) {
656 parse_time_t(&nonceGCInterval
);
657 } else if (strcmp(param_str
, "nonce_max_duration") == 0) {
658 parse_time_t(&noncemaxduration
);
659 } else if (strcmp(param_str
, "nonce_max_count") == 0) {
660 parse_int((int *) &noncemaxuses
);
661 } else if (strcmp(param_str
, "nonce_strictness") == 0) {
662 parse_onoff(&NonceStrictness
);
663 } else if (strcmp(param_str
, "check_nonce_count") == 0) {
664 parse_onoff(&CheckNonceCount
);
665 } else if (strcmp(param_str
, "post_workaround") == 0) {
666 parse_onoff(&PostWorkaround
);
667 } else if (strcmp(param_str
, "utf8") == 0) {
670 debugs(29, DBG_CRITICAL
, "unrecognised digest auth scheme parameter '" << param_str
<< "'");
675 Auth::Digest::Config::type() const
677 return Auth::Digest::Scheme::GetInstance()->type();
681 authenticateDigestStats(StoreEntry
* sentry
)
683 helperStats(sentry
, digestauthenticators
, "Digest Authenticator Statistics");
686 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
689 authDigestNonceUserUnlink(digest_nonce_h
* nonce
)
691 Auth::Digest::User
*digest_user
;
692 dlink_node
*link
, *tmplink
;
700 digest_user
= nonce
->user
;
702 /* unlink from the user list. Yes we're crossing structures but this is the only
703 * time this code is needed
705 link
= digest_user
->nonces
.head
;
711 if (tmplink
->data
== nonce
) {
712 dlinkDelete(tmplink
, &digest_user
->nonces
);
713 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
714 dlinkNodeDelete(tmplink
);
719 /* this reference to user was not locked because freeeing the user frees
725 /* authDigestUserLinkNonce: add a nonce to a given user's struct */
727 authDigestUserLinkNonce(Auth::Digest::User
* user
, digest_nonce_h
* nonce
)
734 Auth::Digest::User
*digest_user
= user
;
736 node
= digest_user
->nonces
.head
;
738 while (node
&& (node
->data
!= nonce
))
744 node
= dlinkNodeNew();
746 dlinkAddTail(nonce
, node
, &digest_user
->nonces
);
748 authDigestNonceLink(nonce
);
750 /* ping this nonce to this auth user */
751 assert((nonce
->user
== NULL
) || (nonce
->user
== user
));
753 /* we don't lock this reference because removing the user removes the
754 * hash too. Of course if that changes we're stuffed so read the code huh?
759 /* setup the necessary info to log the username */
760 static Auth::UserRequest::Pointer
761 authDigestLogUsername(char *username
, Auth::UserRequest::Pointer auth_user_request
)
763 assert(auth_user_request
!= NULL
);
765 /* log the username */
766 debugs(29, 9, "Creating new user for logging '" << (username
?username
:"[no username]") << "'");
767 Auth::User::Pointer digest_user
= new Auth::Digest::User(static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest")));
768 /* save the credentials */
769 digest_user
->username(username
);
770 /* set the auth_user type */
771 digest_user
->auth_type
= Auth::AUTH_BROKEN
;
772 /* link the request to the user */
773 auth_user_request
->user(digest_user
);
774 return auth_user_request
;
778 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
779 * Auth_user structure.
781 Auth::UserRequest::Pointer
782 Auth::Digest::Config::decode(char const *proxy_auth
)
786 const char *pos
= NULL
;
787 char *username
= NULL
;
788 digest_nonce_h
*nonce
;
791 debugs(29, 9, "authenticateDigestDecodeAuth: beginning");
793 Auth::Digest::UserRequest
*digest_request
= new Auth::Digest::UserRequest();
795 /* trim DIGEST from string */
797 while (xisgraph(*proxy_auth
))
800 /* Trim leading whitespace before decoding */
801 while (xisspace(*proxy_auth
))
804 String
temp(proxy_auth
);
806 while (strListGetItem(&temp
, ',', &item
, &ilen
, &pos
)) {
807 /* isolate directive name & value */
810 if ((p
= (const char *)memchr(item
, '=', ilen
)) && (p
- item
< ilen
)) {
813 vlen
= ilen
- (p
- item
);
819 StringArea
keyName(item
, nlen
);
823 // see RFC 2617 section 3.2.1 and 3.2.2 for details on the BNF
825 if (keyName
== StringArea("domain",6) || keyName
== StringArea("uri",3)) {
826 // domain is Special. Not a quoted-string, must not be de-quoted. But is wrapped in '"'
827 // BUG 3077: uri= can also be sent to us in a mangled (invalid!) form like domain
828 if (*p
== '"' && *(p
+ vlen
-1) == '"') {
829 value
.limitInit(p
+1, vlen
-2);
831 } else if (keyName
== StringArea("qop",3)) {
832 // qop is more special.
833 // On request this must not be quoted-string de-quoted. But is several values wrapped in '"'
834 // On response this is a single un-quoted token.
835 if (*p
== '"' && *(p
+ vlen
-1) == '"') {
836 value
.limitInit(p
+1, vlen
-2);
838 value
.limitInit(p
, vlen
);
840 } else if (*p
== '"') {
841 if (!httpHeaderParseQuotedString(p
, vlen
, &value
)) {
842 debugs(29, 9, HERE
<< "Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
846 value
.limitInit(p
, vlen
);
849 debugs(29, 9, HERE
<< "Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
854 http_digest_attr_type t
= (http_digest_attr_type
)httpHeaderIdByName(item
, nlen
, DigestFieldsInfo
, DIGEST_ENUM_END
);
857 case DIGEST_USERNAME
:
859 username
= xstrndup(value
.rawBuf(), value
.size() + 1);
860 debugs(29, 9, HERE
<< "Found Username '" << username
<< "'");
864 safe_free(digest_request
->realm
);
865 digest_request
->realm
= xstrndup(value
.rawBuf(), value
.size() + 1);
866 debugs(29, 9, HERE
<< "Found realm '" << digest_request
->realm
<< "'");
870 safe_free(digest_request
->qop
);
871 digest_request
->qop
= xstrndup(value
.rawBuf(), value
.size() + 1);
872 debugs(29, 9, HERE
<< "Found qop '" << digest_request
->qop
<< "'");
875 case DIGEST_ALGORITHM
:
876 safe_free(digest_request
->algorithm
);
877 digest_request
->algorithm
= xstrndup(value
.rawBuf(), value
.size() + 1);
878 debugs(29, 9, HERE
<< "Found algorithm '" << digest_request
->algorithm
<< "'");
882 safe_free(digest_request
->uri
);
883 digest_request
->uri
= xstrndup(value
.rawBuf(), value
.size() + 1);
884 debugs(29, 9, HERE
<< "Found uri '" << digest_request
->uri
<< "'");
888 safe_free(digest_request
->nonceb64
);
889 digest_request
->nonceb64
= xstrndup(value
.rawBuf(), value
.size() + 1);
890 debugs(29, 9, HERE
<< "Found nonce '" << digest_request
->nonceb64
<< "'");
894 if (value
.size() != 8) {
895 debugs(29, 9, HERE
<< "Invalid nc '" << value
<< "' in '" << temp
<< "'");
897 xstrncpy(digest_request
->nc
, value
.rawBuf(), value
.size() + 1);
898 debugs(29, 9, HERE
<< "Found noncecount '" << digest_request
->nc
<< "'");
902 safe_free(digest_request
->cnonce
);
903 digest_request
->cnonce
= xstrndup(value
.rawBuf(), value
.size() + 1);
904 debugs(29, 9, HERE
<< "Found cnonce '" << digest_request
->cnonce
<< "'");
907 case DIGEST_RESPONSE
:
908 safe_free(digest_request
->response
);
909 digest_request
->response
= xstrndup(value
.rawBuf(), value
.size() + 1);
910 debugs(29, 9, HERE
<< "Found response '" << digest_request
->response
<< "'");
914 debugs(29, 3, HERE
<< "Unknown attribute '" << item
<< "' in '" << temp
<< "'");
921 /* now we validate the data given to us */
924 * TODO: on invalid parameters we should return 400, not 407.
925 * Find some clean way of doing this. perhaps return a valid
926 * struct, and set the direction to clientwards combined with
927 * a change to the clientwards handling code (ie let the
928 * clientwards call set the error type (but limited to known
929 * correct values - 400/401/407
932 /* 2069 requirements */
935 Auth::UserRequest::Pointer rv
;
936 /* do we have a username ? */
937 if (!username
|| username
[0] == '\0') {
938 debugs(29, 2, "Empty or not present username");
939 rv
= authDigestLogUsername(username
, digest_request
);
944 /* Sanity check of the username.
945 * " can not be allowed in usernames until * the digest helper protocol
948 if (strchr(username
, '"')) {
949 debugs(29, 2, "Unacceptable username '" << username
<< "'");
950 rv
= authDigestLogUsername(username
, digest_request
);
955 /* do we have a realm ? */
956 if (!digest_request
->realm
|| digest_request
->realm
[0] == '\0') {
957 debugs(29, 2, "Empty or not present realm");
958 rv
= authDigestLogUsername(username
, digest_request
);
964 if (!digest_request
->nonceb64
|| digest_request
->nonceb64
[0] == '\0') {
965 debugs(29, 2, "Empty or not present nonce");
966 rv
= authDigestLogUsername(username
, digest_request
);
971 /* we can't check the URI just yet. We'll check it in the
972 * authenticate phase, but needs to be given */
973 if (!digest_request
->uri
|| digest_request
->uri
[0] == '\0') {
974 debugs(29, 2, "Missing URI field");
975 rv
= authDigestLogUsername(username
, digest_request
);
980 /* is the response the correct length? */
981 if (!digest_request
->response
|| strlen(digest_request
->response
) != 32) {
982 debugs(29, 2, "Response length invalid");
983 rv
= authDigestLogUsername(username
, digest_request
);
988 /* check the algorithm is present and supported */
989 if (!digest_request
->algorithm
)
990 digest_request
->algorithm
= xstrndup("MD5", 4);
991 else if (strcmp(digest_request
->algorithm
, "MD5")
992 && strcmp(digest_request
->algorithm
, "MD5-sess")) {
993 debugs(29, 2, "invalid algorithm specified!");
994 rv
= authDigestLogUsername(username
, digest_request
);
999 /* 2617 requirements, indicated by qop */
1000 if (digest_request
->qop
) {
1002 /* check the qop is what we expected. */
1003 if (strcmp(digest_request
->qop
, QOP_AUTH
) != 0) {
1004 /* we received a qop option we didn't send */
1005 debugs(29, 2, "Invalid qop option received");
1006 rv
= authDigestLogUsername(username
, digest_request
);
1007 safe_free(username
);
1012 if (!digest_request
->cnonce
|| digest_request
->cnonce
[0] == '\0') {
1013 debugs(29, 2, "Missing cnonce field");
1014 rv
= authDigestLogUsername(username
, digest_request
);
1015 safe_free(username
);
1020 if (strlen(digest_request
->nc
) != 8 || strspn(digest_request
->nc
, "0123456789abcdefABCDEF") != 8) {
1021 debugs(29, 2, "invalid nonce count");
1022 rv
= authDigestLogUsername(username
, digest_request
);
1023 safe_free(username
);
1027 /* cnonce and nc both require qop */
1028 if (digest_request
->cnonce
|| digest_request
->nc
[0] != '\0') {
1029 debugs(29, 2, "missing qop!");
1030 rv
= authDigestLogUsername(username
, digest_request
);
1031 safe_free(username
);
1036 /** below nonce state dependent **/
1039 nonce
= authenticateDigestNonceFindNonce(digest_request
->nonceb64
);
1041 /* we couldn't find a matching nonce! */
1042 debugs(29, 2, "Unexpected or invalid nonce received");
1043 if (digest_request
->user() != NULL
)
1044 digest_request
->user()->credentials(Auth::Failed
);
1045 rv
= authDigestLogUsername(username
, digest_request
);
1046 safe_free(username
);
1050 digest_request
->nonce
= nonce
;
1051 authDigestNonceLink(nonce
);
1053 /* check that we're not being hacked / the username hasn't changed */
1054 if (nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
1055 debugs(29, 2, "Username for the nonce does not equal the username for the request");
1056 rv
= authDigestLogUsername(username
, digest_request
);
1057 safe_free(username
);
1061 /* the method we'll check at the authenticate step as well */
1063 /* we don't send or parse opaques. Ok so we're flexable ... */
1066 Auth::Digest::User
*digest_user
;
1068 Auth::User::Pointer auth_user
;
1070 if ((auth_user
= authDigestUserFindUsername(username
)) == NULL
) {
1071 /* the user doesn't exist in the username cache yet */
1072 debugs(29, 9, HERE
<< "Creating new digest user '" << username
<< "'");
1073 digest_user
= new Auth::Digest::User(this);
1074 /* auth_user is a parent */
1075 auth_user
= digest_user
;
1076 /* save the username */
1077 digest_user
->username(username
);
1078 /* set the user type */
1079 digest_user
->auth_type
= Auth::AUTH_DIGEST
;
1080 /* this auth_user struct is the one to get added to the
1082 /* store user in hash's */
1083 digest_user
->addToNameCache();
1086 * Add the digest to the user so we can tell if a hacking
1087 * or spoofing attack is taking place. We do this by assuming
1088 * the user agent won't change user name without warning.
1090 authDigestUserLinkNonce(digest_user
, nonce
);
1092 debugs(29, 9, HERE
<< "Found user '" << username
<< "' in the user cache as '" << auth_user
<< "'");
1093 digest_user
= static_cast<Auth::Digest::User
*>(auth_user
.getRaw());
1094 digest_user
->credentials(Auth::Unchecked
);
1098 /*link the request and the user */
1099 assert(digest_request
!= NULL
);
1101 digest_request
->user(digest_user
);
1102 debugs(29, 9, HERE
<< "username = '" << digest_user
->username() << "'\nrealm = '" <<
1103 digest_request
->realm
<< "'\nqop = '" << digest_request
->qop
<<
1104 "'\nalgorithm = '" << digest_request
->algorithm
<< "'\nuri = '" <<
1105 digest_request
->uri
<< "'\nnonce = '" << digest_request
->nonceb64
<<
1106 "'\nnc = '" << digest_request
->nc
<< "'\ncnonce = '" <<
1107 digest_request
->cnonce
<< "'\nresponse = '" <<
1108 digest_request
->response
<< "'\ndigestnonce = '" << nonce
<< "'");
1110 return digest_request
;