4 * DEBUG: section 29 Authenticator
5 * AUTHOR: Robert Collins
7 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from the
11 * Internet community. Development is led by Duane Wessels of the
12 * National Laboratory for Applied Network Research and funded by the
13 * National Science Foundation. Squid is Copyrighted (C) 1998 by
14 * the Regents of the University of California. Please see the
15 * COPYRIGHT file for full details. Squid incorporates software
16 * developed and/or copyrighted by other sources. Please see the
17 * CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
35 /* The functions in this file handle authentication.
36 * They DO NOT perform access control or auditing.
37 * See acl.c for access control and client_side.c for auditing */
42 #include "auth/digest/auth_digest.h"
43 #include "auth/Gadgets.h"
45 #include "CacheManager.h"
47 #include "HttpRequest.h"
48 #include "HttpReply.h"
50 #include "SquidTime.h"
51 /* TODO don't include this */
52 #include "auth/digest/digestScheme.h"
53 #include "auth/digest/digestUserRequest.h"
57 static AUTHSSTATS authenticateDigestStats
;
59 helper
*digestauthenticators
= NULL
;
61 static hash_table
*digest_nonce_cache
;
63 static int authdigest_initialised
= 0;
64 static MemAllocator
*digest_nonce_pool
= NULL
;
72 static void authenticateDigestNonceCacheCleanup(void *data
);
73 static digest_nonce_h
*authenticateDigestNonceFindNonce(const char *nonceb64
);
74 static digest_nonce_h
*authenticateDigestNonceNew(void);
75 static void authenticateDigestNonceDelete(digest_nonce_h
* nonce
);
76 static void authenticateDigestNonceSetup(void);
77 static void authenticateDigestNonceShutdown(void);
78 static void authenticateDigestNonceReconfigure(void);
79 static int authDigestNonceIsStale(digest_nonce_h
* nonce
);
80 static void authDigestNonceEncode(digest_nonce_h
* nonce
);
81 static void authDigestNonceLink(digest_nonce_h
* nonce
);
83 static int authDigestNonceLinks(digest_nonce_h
* nonce
);
85 static void authDigestNonceUserUnlink(digest_nonce_h
* nonce
);
86 static void authDigestNoncePurge(digest_nonce_h
* nonce
);
89 authDigestNonceEncode(digest_nonce_h
* nonce
)
97 nonce
->key
= xstrdup(base64_encode_bin((char *) &(nonce
->noncedata
), sizeof(digest_nonce_data
)));
100 static digest_nonce_h
*
101 authenticateDigestNonceNew(void)
103 digest_nonce_h
*newnonce
= static_cast < digest_nonce_h
* >(digest_nonce_pool
->alloc());
104 digest_nonce_h
*temp
;
106 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
107 * === EXCERPT FROM RFC 2617 ===
108 * The contents of the nonce are implementation dependent. The quality
109 * of the implementation depends on a good choice. A nonce might, for
110 * example, be constructed as the base 64 encoding of
112 * time-stamp H(time-stamp ":" ETag ":" private-key)
114 * where time-stamp is a server-generated time or other non-repeating
115 * value, ETag is the value of the HTTP ETag header associated with
116 * the requested entity, and private-key is data known only to the
117 * server. With a nonce of this form a server would recalculate the
118 * hash portion after receiving the client authentication header and
119 * reject the request if it did not match the nonce from that header
120 * or if the time-stamp value is not recent enough. In this way the
121 * server can limit the time of the nonce's validity. The inclusion of
122 * the ETag prevents a replay request for an updated version of the
123 * resource. (Note: including the IP address of the client in the
124 * nonce would appear to offer the server the ability to limit the
125 * reuse of the nonce to the same client that originally got it.
126 * However, that would break proxy farms, where requests from a single
127 * user often go through different proxies in the farm. Also, IP
128 * address spoofing is not that hard.)
131 * Now for my reasoning:
132 * We will not accept a unrecognised nonce->we have all recognisable
133 * nonces stored. If we send out unique base64 encodings we guarantee
134 * that a given nonce applies to only one user (barring attacks or
135 * really bad timing with expiry and creation). Using a random
136 * component in the nonce allows us to loop to find a unique nonce.
137 * We use H(nonce_data) so the nonce is meaningless to the reciever.
138 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
139 * And even if our randomness is not very random (probably due to
140 * bad coding on my part) we don't really care - the timestamp and
141 * memory pointer also guarantee local uniqueness in the input to the hash
145 /* create a new nonce */
147 newnonce
->flags
.valid
= 1;
148 newnonce
->noncedata
.self
= newnonce
;
149 newnonce
->noncedata
.creationtime
= current_time
.tv_sec
;
150 newnonce
->noncedata
.randomdata
= squid_random();
152 authDigestNonceEncode(newnonce
);
154 * loop until we get a unique nonce. The nonce creation must
155 * have a random factor
158 while ((temp
= authenticateDigestNonceFindNonce((char const *) (newnonce
->key
)))) {
159 /* create a new nonce */
160 newnonce
->noncedata
.randomdata
= squid_random();
161 authDigestNonceEncode(newnonce
);
164 hash_join(digest_nonce_cache
, newnonce
);
165 /* the cache's link */
166 authDigestNonceLink(newnonce
);
167 newnonce
->flags
.incache
= 1;
168 debugs(29, 5, "authenticateDigestNonceNew: created nonce " << newnonce
<< " at " << newnonce
->noncedata
.creationtime
);
173 authenticateDigestNonceDelete(digest_nonce_h
* nonce
)
176 assert(nonce
->references
== 0);
179 if (nonce
->flags
.incache
)
180 hash_remove_link(digest_nonce_cache
, nonce
);
184 assert(nonce
->flags
.incache
== 0);
186 safe_free(nonce
->key
);
188 digest_nonce_pool
->free(nonce
);
193 authenticateDigestNonceSetup(void)
195 if (!digest_nonce_pool
)
196 digest_nonce_pool
= memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h
));
198 if (!digest_nonce_cache
) {
199 digest_nonce_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
200 assert(digest_nonce_cache
);
201 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->nonceGCInterval
, 1);
206 authenticateDigestNonceShutdown(void)
209 * We empty the cache of any nonces left in there.
211 digest_nonce_h
*nonce
;
213 if (digest_nonce_cache
) {
214 debugs(29, 2, "authenticateDigestNonceShutdown: Shutting down nonce cache ");
215 hash_first(digest_nonce_cache
);
217 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
218 assert(nonce
->flags
.incache
);
219 authDigestNoncePurge(nonce
);
224 if (digest_nonce_pool
) {
225 delete digest_nonce_pool
;
226 digest_nonce_pool
= NULL
;
230 debugs(29, 2, "authenticateDigestNonceShutdown: Nonce cache shutdown");
234 authenticateDigestNonceReconfigure(void)
238 authenticateDigestNonceCacheCleanup(void *data
)
241 * We walk the hash by nonceb64 as that is the unique key we
242 * use. For big hash tables we could consider stepping through
243 * the cache, 100/200 entries at a time. Lets see how it flies
246 digest_nonce_h
*nonce
;
247 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now");
248 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Current time: " << current_time
.tv_sec
);
249 hash_first(digest_nonce_cache
);
251 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
252 debugs(29, 3, "authenticateDigestNonceCacheCleanup: nonce entry : " << nonce
<< " '" << (char *) nonce
->key
<< "'");
253 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Creation time: " << nonce
->noncedata
.creationtime
);
255 if (authDigestNonceIsStale(nonce
)) {
256 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Removing nonce " << (char *) nonce
->key
<< " from cache due to timeout.");
257 assert(nonce
->flags
.incache
);
258 /* invalidate nonce so future requests fail */
259 nonce
->flags
.valid
= 0;
260 /* if it is tied to a auth_user, remove the tie */
261 authDigestNonceUserUnlink(nonce
);
262 authDigestNoncePurge(nonce
);
266 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.");
268 if (static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->active())
269 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->nonceGCInterval
, 1);
273 authDigestNonceLink(digest_nonce_h
* nonce
)
275 assert(nonce
!= NULL
);
277 debugs(29, 9, "authDigestNonceLink: nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
282 authDigestNonceLinks(digest_nonce_h
* nonce
)
287 return nonce
->references
;
293 authDigestNonceUnlink(digest_nonce_h
* nonce
)
295 assert(nonce
!= NULL
);
297 if (nonce
->references
> 0) {
300 debugs(29, 1, "authDigestNonceUnlink; Attempt to lower nonce " << nonce
<< " refcount below 0!");
303 debugs(29, 9, "authDigestNonceUnlink: nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
305 if (nonce
->references
== 0)
306 authenticateDigestNonceDelete(nonce
);
310 authenticateDigestNonceNonceb64(const digest_nonce_h
* nonce
)
315 return (char const *) nonce
->key
;
318 static digest_nonce_h
*
319 authenticateDigestNonceFindNonce(const char *nonceb64
)
321 digest_nonce_h
*nonce
= NULL
;
323 if (nonceb64
== NULL
)
326 debugs(29, 9, "authDigestNonceFindNonce:looking for nonceb64 '" << nonceb64
<< "' in the nonce cache.");
328 nonce
= static_cast < digest_nonce_h
* >(hash_lookup(digest_nonce_cache
, nonceb64
));
330 if ((nonce
== NULL
) || (strcmp(authenticateDigestNonceNonceb64(nonce
), nonceb64
)))
333 debugs(29, 9, "authDigestNonceFindNonce: Found nonce '" << nonce
<< "'");
339 authDigestNonceIsValid(digest_nonce_h
* nonce
, char nc
[9])
342 /* do we have a nonce ? */
347 intnc
= strtol(nc
, NULL
, 16);
349 /* has it already been invalidated ? */
350 if (!nonce
->flags
.valid
) {
351 debugs(29, 4, "authDigestNonceIsValid: Nonce already invalidated");
355 /* is the nonce-count ok ? */
356 if (!static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->CheckNonceCount
) {
358 return -1; /* forced OK by configuration */
361 if ((static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->NonceStrictness
&& intnc
!= nonce
->nc
+ 1) ||
362 intnc
< nonce
->nc
+ 1) {
363 debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match");
364 nonce
->flags
.valid
= 0;
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.
378 authDigestNonceIsStale(digest_nonce_h
* nonce
)
380 /* do we have a nonce ? */
385 /* has it's max duration expired? */
386 if (nonce
->noncedata
.creationtime
+ static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxduration
< current_time
.tv_sec
) {
387 debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " <<
388 nonce
->noncedata
.creationtime
<< " " <<
389 static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxduration
<< " " <<
390 current_time
.tv_sec
);
392 nonce
->flags
.valid
= 0;
396 if (nonce
->nc
> 99999998) {
397 debugs(29, 4, "authDigestNonceIsStale: Nonce count overflow");
398 nonce
->flags
.valid
= 0;
402 if (nonce
->nc
> static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxuses
) {
403 debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit");
404 nonce
->flags
.valid
= 0;
413 * \retval 0 the digest is not stale yet
414 * \retval -1 the digest will be stale on the next request
417 authDigestNonceLastRequest(digest_nonce_h
* nonce
)
422 if (nonce
->nc
== 99999997) {
423 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to overflow");
427 if (nonce
->nc
>= static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxuses
- 1) {
428 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit");
432 /* and other tests are possible. */
437 authDigestNoncePurge(digest_nonce_h
* nonce
)
442 if (!nonce
->flags
.incache
)
445 hash_remove_link(digest_nonce_cache
, nonce
);
447 nonce
->flags
.incache
= 0;
449 /* the cache's link */
450 authDigestNonceUnlink(nonce
);
453 /* USER related functions */
455 authDigestUserFindUsername(const char *username
)
457 AuthUserHashPointer
*usernamehash
;
459 debugs(29, 9, HERE
<< "Looking for user '" << username
<< "'");
461 if (username
&& (usernamehash
= static_cast < auth_user_hash_pointer
* >(hash_lookup(proxy_auth_username_cache
, username
)))) {
462 while ((usernamehash
->user()->auth_type
!= AUTH_DIGEST
) &&
463 (usernamehash
->next
))
464 usernamehash
= static_cast < auth_user_hash_pointer
* >(usernamehash
->next
);
468 if (usernamehash
->user()->auth_type
== AUTH_DIGEST
) {
469 auth_user
= usernamehash
->user();
479 authDigestUserShutdown(void)
481 /** \todo Future work: the auth framework could flush it's cache */
482 AuthUserHashPointer
*usernamehash
;
484 hash_first(proxy_auth_username_cache
);
486 while ((usernamehash
= ((auth_user_hash_pointer
*) hash_next(proxy_auth_username_cache
)))) {
487 auth_user
= usernamehash
->user();
489 if (strcmp(auth_user
->config
->type(), "digest") == 0)
494 /** delete the digest request structure. Does NOT delete related structures */
498 /** \todo this should be a Config call. */
500 if (digestauthenticators
)
501 helperShutdown(digestauthenticators
);
503 authdigest_initialised
= 0;
505 if (!shutting_down
) {
506 authenticateDigestNonceReconfigure();
510 delete digestauthenticators
;
511 digestauthenticators
= NULL
;
513 authDigestUserShutdown();
514 authenticateDigestNonceShutdown();
515 debugs(29, 2, "authenticateDigestDone: Digest authentication shut down.");
517 /* clear the global handle to this scheme. */
522 AuthDigestConfig::dump(StoreEntry
* entry
, const char *name
, AuthConfig
* scheme
)
524 wordlist
*list
= authenticate
;
525 debugs(29, 9, "authDigestCfgDump: Dumping configuration");
526 storeAppendPrintf(entry
, "%s %s", name
, "digest");
528 while (list
!= NULL
) {
529 storeAppendPrintf(entry
, " %s", list
->key
);
533 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",
534 name
, "digest", digestAuthRealm
,
535 name
, "digest", authenticateChildren
.n_max
, authenticateChildren
.n_startup
, authenticateChildren
.n_idle
, authenticateChildren
.concurrency
,
536 name
, "digest", noncemaxuses
,
537 name
, "digest", (int) noncemaxduration
,
538 name
, "digest", (int) nonceGCInterval
);
542 AuthDigestConfig::active() const
544 return authdigest_initialised
== 1;
548 AuthDigestConfig::configured() const
550 if ((authenticate
!= NULL
) &&
551 (authenticateChildren
.n_max
!= 0) &&
552 (digestAuthRealm
!= NULL
) && (noncemaxduration
> -1))
558 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
560 AuthDigestConfig::fixHeader(AuthUserRequest::Pointer auth_user_request
, HttpReply
*rep
, http_hdr_type hdrType
, HttpRequest
* request
)
567 if (auth_user_request
!= NULL
) {
568 AuthDigestUserRequest
*digest_request
;
569 digest_request
= dynamic_cast<AuthDigestUserRequest
*>(auth_user_request
.getRaw());
570 assert (digest_request
!= NULL
);
572 stale
= !digest_request
->flags
.invalid_password
;
575 /* on a 407 or 401 we always use a new nonce */
576 digest_nonce_h
*nonce
= authenticateDigestNonceNew();
578 debugs(29, 9, "authenticateFixHeader: Sending type:" << hdrType
<<
579 " header: 'Digest realm=\"" << digestAuthRealm
<< "\", nonce=\"" <<
580 authenticateDigestNonceNonceb64(nonce
) << "\", qop=\"" << QOP_AUTH
<<
581 "\", stale=" << (stale
? "true" : "false"));
583 /* in the future, for WWW auth we may want to support the domain entry */
584 httpHeaderPutStrf(&rep
->header
, hdrType
, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestAuthRealm
, authenticateDigestNonceNonceb64(nonce
), QOP_AUTH
, stale
? "true" : "false");
587 DigestUser::~DigestUser()
590 dlink_node
*link
, *tmplink
;
596 dlinkDelete(tmplink
, &nonces
);
597 authDigestNoncePurge(static_cast < digest_nonce_h
* >(tmplink
->data
));
598 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
599 dlinkNodeDelete(tmplink
);
603 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
606 AuthDigestConfig::init(AuthConfig
* scheme
)
609 authenticateDigestNonceSetup();
610 authdigest_initialised
= 1;
612 if (digestauthenticators
== NULL
)
613 digestauthenticators
= new helper("digestauthenticator");
615 digestauthenticators
->cmdline
= authenticate
;
617 digestauthenticators
->childs
= authenticateChildren
;
619 digestauthenticators
->ipc_type
= IPC_STREAM
;
621 helperOpenServers(digestauthenticators
);
623 CBDATA_INIT_TYPE(authenticateStateData
);
628 AuthDigestConfig::registerWithCacheManager(void)
630 CacheManager::GetInstance()->
631 registerAction("digestauthenticator",
632 "Digest User Authenticator Stats",
633 authenticateDigestStats
, 0, 1);
636 /* free any allocated configuration details */
638 AuthDigestConfig::done()
641 wordlistDestroy(&authenticate
);
643 safe_free(digestAuthRealm
);
646 AuthDigestConfig::AuthDigestConfig() : authenticateChildren(20,0,1,1)
648 /* TODO: move into initialisation list */
651 nonceGCInterval
= 5 * 60;
653 noncemaxduration
= 30 * 60;
656 /* Not strict nonce count behaviour */
658 /* Verify nonce count */
663 AuthDigestConfig::parse(AuthConfig
* scheme
, int n_configured
, char *param_str
)
665 if (strcasecmp(param_str
, "program") == 0) {
667 wordlistDestroy(&authenticate
);
669 parse_wordlist(&authenticate
);
671 requirePathnameExists("auth_param digest program", authenticate
->key
);
672 } else if (strcasecmp(param_str
, "children") == 0) {
673 authenticateChildren
.parseConfig();
674 } else if (strcasecmp(param_str
, "realm") == 0) {
675 parse_eol(&digestAuthRealm
);
676 } else if (strcasecmp(param_str
, "nonce_garbage_interval") == 0) {
677 parse_time_t(&nonceGCInterval
);
678 } else if (strcasecmp(param_str
, "nonce_max_duration") == 0) {
679 parse_time_t(&noncemaxduration
);
680 } else if (strcasecmp(param_str
, "nonce_max_count") == 0) {
681 parse_int((int *) &noncemaxuses
);
682 } else if (strcasecmp(param_str
, "nonce_strictness") == 0) {
683 parse_onoff(&NonceStrictness
);
684 } else if (strcasecmp(param_str
, "check_nonce_count") == 0) {
685 parse_onoff(&CheckNonceCount
);
686 } else if (strcasecmp(param_str
, "post_workaround") == 0) {
687 parse_onoff(&PostWorkaround
);
688 } else if (strcasecmp(param_str
, "utf8") == 0) {
691 debugs(29, 0, "unrecognised digest auth scheme parameter '" << param_str
<< "'");
696 AuthDigestConfig::type() const
698 return digestScheme::GetInstance()->type();
703 authenticateDigestStats(StoreEntry
* sentry
)
705 helperStats(sentry
, digestauthenticators
, "Digest Authenticator Statistics");
708 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
711 authDigestNonceUserUnlink(digest_nonce_h
* nonce
)
713 digest_user_h
*digest_user
;
714 dlink_node
*link
, *tmplink
;
722 digest_user
= nonce
->user
;
724 /* unlink from the user list. Yes we're crossing structures but this is the only
725 * time this code is needed
727 link
= digest_user
->nonces
.head
;
733 if (tmplink
->data
== nonce
) {
734 dlinkDelete(tmplink
, &digest_user
->nonces
);
735 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
736 dlinkNodeDelete(tmplink
);
741 /* this reference to user was not locked because freeeing the user frees
747 /* authDigestUserLinkNonce: add a nonce to a given user's struct */
750 authDigestUserLinkNonce(DigestUser
* user
, digest_nonce_h
* nonce
)
753 digest_user_h
*digest_user
;
760 node
= digest_user
->nonces
.head
;
762 while (node
&& (node
->data
!= nonce
))
768 node
= dlinkNodeNew();
770 dlinkAddTail(nonce
, node
, &digest_user
->nonces
);
772 authDigestNonceLink(nonce
);
774 /* ping this nonce to this auth user */
775 assert((nonce
->user
== NULL
) || (nonce
->user
== user
));
777 /* we don't lock this reference because removing the user removes the
778 * hash too. Of course if that changes we're stuffed so read the code huh?
783 /* setup the necessary info to log the username */
784 static AuthUserRequest::Pointer
785 authDigestLogUsername(char *username
, AuthUserRequest::Pointer auth_user_request
)
787 assert(auth_user_request
!= NULL
);
789 /* log the username */
790 debugs(29, 9, "authDigestLogUsername: Creating new user for logging '" << username
<< "'");
791 digest_user_h
*digest_user
= new DigestUser(static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest")));
792 /* save the credentials */
793 digest_user
->username(username
);
794 /* set the auth_user type */
795 digest_user
->auth_type
= AUTH_BROKEN
;
796 /* link the request to the user */
797 auth_user_request
->user(digest_user
);
799 digest_user
->addRequest(auth_user_request
);
800 return auth_user_request
;
804 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
805 * Auth_user structure.
807 AuthUserRequest::Pointer
808 AuthDigestConfig::decode(char const *proxy_auth
)
812 const char *pos
= NULL
;
813 char *username
= NULL
;
814 digest_nonce_h
*nonce
;
817 debugs(29, 9, "authenticateDigestDecodeAuth: beginning");
819 AuthDigestUserRequest
*digest_request
= new AuthDigestUserRequest();
821 /* trim DIGEST from string */
823 while (xisgraph(*proxy_auth
))
826 /* Trim leading whitespace before decoding */
827 while (xisspace(*proxy_auth
))
830 String
temp(proxy_auth
);
832 while (strListGetItem(&temp
, ',', &item
, &ilen
, &pos
)) {
833 if ((p
= strchr(item
, '=')) && (p
- item
< ilen
))
836 if (!strncmp(item
, "username", ilen
)) {
846 username
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
848 debugs(29, 9, "authDigestDecodeAuth: Found Username '" << username
<< "'");
849 } else if (!strncmp(item
, "realm", ilen
)) {
858 safe_free(digest_request
->realm
);
859 digest_request
->realm
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
861 debugs(29, 9, "authDigestDecodeAuth: Found realm '" << digest_request
->realm
<< "'");
862 } else if (!strncmp(item
, "qop", ilen
)) {
872 safe_free(digest_request
->qop
);
873 digest_request
->qop
= xstrndup(p
, strcspn(p
, "\" \t\r\n()<>@,;:\\/[]?={}") + 1);
875 debugs(29, 9, "authDigestDecodeAuth: Found qop '" << digest_request
->qop
<< "'");
876 } else if (!strncmp(item
, "algorithm", ilen
)) {
886 safe_free(digest_request
->algorithm
);
887 digest_request
->algorithm
= xstrndup(p
, strcspn(p
, "\" \t\r\n()<>@,;:\\/[]?={}") + 1);
889 debugs(29, 9, "authDigestDecodeAuth: Found algorithm '" << digest_request
->algorithm
<< "'");
890 } else if (!strncmp(item
, "uri", ilen
)) {
899 safe_free(digest_request
->uri
);
900 digest_request
->uri
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
902 debugs(29, 9, "authDigestDecodeAuth: Found uri '" << digest_request
->uri
<< "'");
903 } else if (!strncmp(item
, "nonce", ilen
)) {
912 safe_free(digest_request
->nonceb64
);
913 digest_request
->nonceb64
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
915 debugs(29, 9, "authDigestDecodeAuth: Found nonce '" << digest_request
->nonceb64
<< "'");
916 } else if (!strncmp(item
, "nc", ilen
)) {
922 xstrncpy(digest_request
->nc
, p
, 9);
924 debugs(29, 9, "authDigestDecodeAuth: Found noncecount '" << digest_request
->nc
<< "'");
925 } else if (!strncmp(item
, "cnonce", ilen
)) {
934 safe_free(digest_request
->cnonce
);
935 digest_request
->cnonce
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
937 debugs(29, 9, "authDigestDecodeAuth: Found cnonce '" << digest_request
->cnonce
<< "'");
938 } else if (!strncmp(item
, "response", ilen
)) {
947 safe_free(digest_request
->response
);
948 digest_request
->response
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
950 debugs(29, 9, "authDigestDecodeAuth: Found response '" << digest_request
->response
<< "'");
957 /* now we validate the data given to us */
960 * TODO: on invalid parameters we should return 400, not 407.
961 * Find some clean way of doing this. perhaps return a valid
962 * struct, and set the direction to clientwards combined with
963 * a change to the clientwards handling code (ie let the
964 * clientwards call set the error type (but limited to known
965 * correct values - 400/401/407
968 /* first the NONCE count */
970 if (digest_request
->cnonce
&& strlen(digest_request
->nc
) != 8) {
971 debugs(29, 4, "authenticateDigestDecode: nonce count length invalid");
972 return authDigestLogUsername(username
, digest_request
);
976 nonce
= authenticateDigestNonceFindNonce(digest_request
->nonceb64
);
979 /* we couldn't find a matching nonce! */
980 debugs(29, 4, "authenticateDigestDecode: Unexpected or invalid nonce received");
981 return authDigestLogUsername(username
, digest_request
);
984 digest_request
->nonce
= nonce
;
985 authDigestNonceLink(nonce
);
987 /* check the qop is what we expected. Note that for compatability with
988 * RFC 2069 we should support a missing qop. Tough. */
990 if (digest_request
->qop
&& strcmp(digest_request
->qop
, QOP_AUTH
) != 0) {
991 /* we received a qop option we didn't send */
992 debugs(29, 4, "authenticateDigestDecode: Invalid qop option received");
993 return authDigestLogUsername(username
, digest_request
);
996 /* we can't check the URI just yet. We'll check it in the
997 * authenticate phase */
999 /* is the response the correct length? */
1001 if (!digest_request
->response
|| strlen(digest_request
->response
) != 32) {
1002 debugs(29, 4, "authenticateDigestDecode: Response length invalid");
1003 return authDigestLogUsername(username
, digest_request
);
1006 /* do we have a username ? */
1007 if (!username
|| username
[0] == '\0') {
1008 debugs(29, 4, "authenticateDigestDecode: Empty or not present username");
1009 return authDigestLogUsername(username
, digest_request
);
1012 /* check that we're not being hacked / the username hasn't changed */
1013 if (nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
1014 debugs(29, 4, "authenticateDigestDecode: Username for the nonce does not equal the username for the request");
1015 return authDigestLogUsername(username
, digest_request
);
1018 /* if we got a qop, did we get a cnonce or did we get a cnonce wihtout a qop? */
1019 if ((digest_request
->qop
&& !digest_request
->cnonce
)
1020 || (!digest_request
->qop
&& digest_request
->cnonce
)) {
1021 debugs(29, 4, "authenticateDigestDecode: qop without cnonce, or vice versa!");
1022 return authDigestLogUsername(username
, digest_request
);
1025 /* check the algorithm is present and supported */
1026 if (!digest_request
->algorithm
)
1027 digest_request
->algorithm
= xstrndup("MD5", 4);
1028 else if (strcmp(digest_request
->algorithm
, "MD5")
1029 && strcmp(digest_request
->algorithm
, "MD5-sess")) {
1030 debugs(29, 4, "authenticateDigestDecode: invalid algorithm specified!");
1031 return authDigestLogUsername(username
, digest_request
);
1034 /* the method we'll check at the authenticate step as well */
1037 /* we don't send or parse opaques. Ok so we're flexable ... */
1040 digest_user_h
*digest_user
;
1042 AuthUser
*auth_user
;
1044 if ((auth_user
= authDigestUserFindUsername(username
)) == NULL
) {
1045 /* the user doesn't exist in the username cache yet */
1046 debugs(29, 9, "authDigestDecodeAuth: Creating new digest user '" << username
<< "'");
1047 digest_user
= new DigestUser(this);
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_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, "authDigestDecodeAuth: Found user '" << username
<< "' in the user cache as '" << auth_user
<< "'");
1067 digest_user
= static_cast < digest_user_h
* >(auth_user
);
1071 /*link the request and the user */
1072 assert(digest_request
!= NULL
);
1074 digest_user
->lock();
1075 digest_request
->user(digest_user
);
1077 digest_user
->addRequest(digest_request
);
1079 debugs(29, 9, "username = '" << digest_user
->username() << "'\nrealm = '" <<
1080 digest_request
->realm
<< "'\nqop = '" << digest_request
->qop
<<
1081 "'\nalgorithm = '" << digest_request
->algorithm
<< "'\nuri = '" <<
1082 digest_request
->uri
<< "'\nnonce = '" << digest_request
->nonceb64
<<
1083 "'\nnc = '" << digest_request
->nc
<< "'\ncnonce = '" <<
1084 digest_request
->cnonce
<< "'\nresponse = '" <<
1085 digest_request
->response
<< "'\ndigestnonce = '" << nonce
<< "'");
1087 return digest_request
;
1090 DigestUser::DigestUser (AuthConfig
*aConfig
) : AuthUser (aConfig
), HA1created (0)