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"
46 #include "mgr/Registration.h"
48 #include "HttpRequest.h"
49 #include "HttpReply.h"
51 #include "SquidTime.h"
52 /* TODO don't include this */
53 #include "auth/digest/digestScheme.h"
54 #include "auth/digest/digestUserRequest.h"
58 static AUTHSSTATS authenticateDigestStats
;
60 helper
*digestauthenticators
= NULL
;
62 static hash_table
*digest_nonce_cache
;
64 static int authdigest_initialised
= 0;
65 static MemAllocator
*digest_nonce_pool
= NULL
;
67 // CBDATA_TYPE(DigestAuthenticateStateData);
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 static HttpHeaderFieldInfo
*DigestFieldsInfo
= NULL
;
102 static void authenticateDigestNonceCacheCleanup(void *data
);
103 static digest_nonce_h
*authenticateDigestNonceFindNonce(const char *nonceb64
);
104 static digest_nonce_h
*authenticateDigestNonceNew(void);
105 static void authenticateDigestNonceDelete(digest_nonce_h
* nonce
);
106 static void authenticateDigestNonceSetup(void);
107 static void authenticateDigestNonceShutdown(void);
108 static void authenticateDigestNonceReconfigure(void);
109 static int authDigestNonceIsStale(digest_nonce_h
* nonce
);
110 static void authDigestNonceEncode(digest_nonce_h
* nonce
);
111 static void authDigestNonceLink(digest_nonce_h
* nonce
);
113 static int authDigestNonceLinks(digest_nonce_h
* nonce
);
115 static void authDigestNonceUserUnlink(digest_nonce_h
* nonce
);
116 static void authDigestNoncePurge(digest_nonce_h
* nonce
);
119 authDigestNonceEncode(digest_nonce_h
* nonce
)
127 nonce
->key
= xstrdup(base64_encode_bin((char *) &(nonce
->noncedata
), sizeof(digest_nonce_data
)));
130 static digest_nonce_h
*
131 authenticateDigestNonceNew(void)
133 digest_nonce_h
*newnonce
= static_cast < digest_nonce_h
* >(digest_nonce_pool
->alloc());
134 digest_nonce_h
*temp
;
136 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
137 * === EXCERPT FROM RFC 2617 ===
138 * The contents of the nonce are implementation dependent. The quality
139 * of the implementation depends on a good choice. A nonce might, for
140 * example, be constructed as the base 64 encoding of
142 * time-stamp H(time-stamp ":" ETag ":" private-key)
144 * where time-stamp is a server-generated time or other non-repeating
145 * value, ETag is the value of the HTTP ETag header associated with
146 * the requested entity, and private-key is data known only to the
147 * server. With a nonce of this form a server would recalculate the
148 * hash portion after receiving the client authentication header and
149 * reject the request if it did not match the nonce from that header
150 * or if the time-stamp value is not recent enough. In this way the
151 * server can limit the time of the nonce's validity. The inclusion of
152 * the ETag prevents a replay request for an updated version of the
153 * resource. (Note: including the IP address of the client in the
154 * nonce would appear to offer the server the ability to limit the
155 * reuse of the nonce to the same client that originally got it.
156 * However, that would break proxy farms, where requests from a single
157 * user often go through different proxies in the farm. Also, IP
158 * address spoofing is not that hard.)
161 * Now for my reasoning:
162 * We will not accept a unrecognised nonce->we have all recognisable
163 * nonces stored. If we send out unique base64 encodings we guarantee
164 * that a given nonce applies to only one user (barring attacks or
165 * really bad timing with expiry and creation). Using a random
166 * component in the nonce allows us to loop to find a unique nonce.
167 * We use H(nonce_data) so the nonce is meaningless to the reciever.
168 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
169 * And even if our randomness is not very random (probably due to
170 * bad coding on my part) we don't really care - the timestamp and
171 * memory pointer also guarantee local uniqueness in the input to the hash
175 /* create a new nonce */
177 newnonce
->flags
.valid
= 1;
178 newnonce
->noncedata
.self
= newnonce
;
179 newnonce
->noncedata
.creationtime
= current_time
.tv_sec
;
180 newnonce
->noncedata
.randomdata
= squid_random();
182 authDigestNonceEncode(newnonce
);
184 * loop until we get a unique nonce. The nonce creation must
185 * have a random factor
188 while ((temp
= authenticateDigestNonceFindNonce((char const *) (newnonce
->key
)))) {
189 /* create a new nonce */
190 newnonce
->noncedata
.randomdata
= squid_random();
191 authDigestNonceEncode(newnonce
);
194 hash_join(digest_nonce_cache
, newnonce
);
195 /* the cache's link */
196 authDigestNonceLink(newnonce
);
197 newnonce
->flags
.incache
= 1;
198 debugs(29, 5, "authenticateDigestNonceNew: created nonce " << newnonce
<< " at " << newnonce
->noncedata
.creationtime
);
203 authenticateDigestNonceDelete(digest_nonce_h
* nonce
)
206 assert(nonce
->references
== 0);
209 if (nonce
->flags
.incache
)
210 hash_remove_link(digest_nonce_cache
, nonce
);
214 assert(nonce
->flags
.incache
== 0);
216 safe_free(nonce
->key
);
218 digest_nonce_pool
->freeOne(nonce
);
223 authenticateDigestNonceSetup(void)
225 if (!digest_nonce_pool
)
226 digest_nonce_pool
= memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h
));
228 if (!digest_nonce_cache
) {
229 digest_nonce_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
230 assert(digest_nonce_cache
);
231 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->nonceGCInterval
, 1);
236 authenticateDigestNonceShutdown(void)
239 * We empty the cache of any nonces left in there.
241 digest_nonce_h
*nonce
;
243 if (digest_nonce_cache
) {
244 debugs(29, 2, "authenticateDigestNonceShutdown: Shutting down nonce cache ");
245 hash_first(digest_nonce_cache
);
247 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
248 assert(nonce
->flags
.incache
);
249 authDigestNoncePurge(nonce
);
254 if (digest_nonce_pool
) {
255 delete digest_nonce_pool
;
256 digest_nonce_pool
= NULL
;
260 debugs(29, 2, "authenticateDigestNonceShutdown: Nonce cache shutdown");
264 authenticateDigestNonceReconfigure(void)
268 authenticateDigestNonceCacheCleanup(void *data
)
271 * We walk the hash by nonceb64 as that is the unique key we
272 * use. For big hash tables we could consider stepping through
273 * the cache, 100/200 entries at a time. Lets see how it flies
276 digest_nonce_h
*nonce
;
277 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now");
278 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Current time: " << current_time
.tv_sec
);
279 hash_first(digest_nonce_cache
);
281 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
282 debugs(29, 3, "authenticateDigestNonceCacheCleanup: nonce entry : " << nonce
<< " '" << (char *) nonce
->key
<< "'");
283 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Creation time: " << nonce
->noncedata
.creationtime
);
285 if (authDigestNonceIsStale(nonce
)) {
286 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Removing nonce " << (char *) nonce
->key
<< " from cache due to timeout.");
287 assert(nonce
->flags
.incache
);
288 /* invalidate nonce so future requests fail */
289 nonce
->flags
.valid
= 0;
290 /* if it is tied to a auth_user, remove the tie */
291 authDigestNonceUserUnlink(nonce
);
292 authDigestNoncePurge(nonce
);
296 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.");
298 if (static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->active())
299 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->nonceGCInterval
, 1);
303 authDigestNonceLink(digest_nonce_h
* nonce
)
305 assert(nonce
!= NULL
);
307 debugs(29, 9, "authDigestNonceLink: nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
312 authDigestNonceLinks(digest_nonce_h
* nonce
)
317 return nonce
->references
;
323 authDigestNonceUnlink(digest_nonce_h
* nonce
)
325 assert(nonce
!= NULL
);
327 if (nonce
->references
> 0) {
330 debugs(29, 1, "authDigestNonceUnlink; Attempt to lower nonce " << nonce
<< " refcount below 0!");
333 debugs(29, 9, "authDigestNonceUnlink: nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
335 if (nonce
->references
== 0)
336 authenticateDigestNonceDelete(nonce
);
340 authenticateDigestNonceNonceb64(const digest_nonce_h
* nonce
)
345 return (char const *) nonce
->key
;
348 static digest_nonce_h
*
349 authenticateDigestNonceFindNonce(const char *nonceb64
)
351 digest_nonce_h
*nonce
= NULL
;
353 if (nonceb64
== NULL
)
356 debugs(29, 9, "authDigestNonceFindNonce:looking for nonceb64 '" << nonceb64
<< "' in the nonce cache.");
358 nonce
= static_cast < digest_nonce_h
* >(hash_lookup(digest_nonce_cache
, nonceb64
));
360 if ((nonce
== NULL
) || (strcmp(authenticateDigestNonceNonceb64(nonce
), nonceb64
)))
363 debugs(29, 9, "authDigestNonceFindNonce: Found nonce '" << nonce
<< "'");
369 authDigestNonceIsValid(digest_nonce_h
* nonce
, char nc
[9])
372 /* do we have a nonce ? */
377 intnc
= strtol(nc
, NULL
, 16);
379 /* has it already been invalidated ? */
380 if (!nonce
->flags
.valid
) {
381 debugs(29, 4, "authDigestNonceIsValid: Nonce already invalidated");
385 /* is the nonce-count ok ? */
386 if (!static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->CheckNonceCount
) {
388 return -1; /* forced OK by configuration */
391 if ((static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->NonceStrictness
&& intnc
!= nonce
->nc
+ 1) ||
392 intnc
< nonce
->nc
+ 1) {
393 debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match");
394 nonce
->flags
.valid
= 0;
399 /* increment the nonce count - we've already checked that intnc is a
400 * valid representation for us, so we don't need the test here.
408 authDigestNonceIsStale(digest_nonce_h
* nonce
)
410 /* do we have a nonce ? */
415 /* has it's max duration expired? */
416 if (nonce
->noncedata
.creationtime
+ static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxduration
< current_time
.tv_sec
) {
417 debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " <<
418 nonce
->noncedata
.creationtime
<< " " <<
419 static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxduration
<< " " <<
420 current_time
.tv_sec
);
422 nonce
->flags
.valid
= 0;
426 if (nonce
->nc
> 99999998) {
427 debugs(29, 4, "authDigestNonceIsStale: Nonce count overflow");
428 nonce
->flags
.valid
= 0;
432 if (nonce
->nc
> static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxuses
) {
433 debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit");
434 nonce
->flags
.valid
= 0;
443 * \retval 0 the digest is not stale yet
444 * \retval -1 the digest will be stale on the next request
447 authDigestNonceLastRequest(digest_nonce_h
* nonce
)
452 if (nonce
->nc
== 99999997) {
453 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to overflow");
457 if (nonce
->nc
>= static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxuses
- 1) {
458 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit");
462 /* and other tests are possible. */
467 authDigestNoncePurge(digest_nonce_h
* nonce
)
472 if (!nonce
->flags
.incache
)
475 hash_remove_link(digest_nonce_cache
, nonce
);
477 nonce
->flags
.incache
= 0;
479 /* the cache's link */
480 authDigestNonceUnlink(nonce
);
483 /* USER related functions */
484 static AuthUser::Pointer
485 authDigestUserFindUsername(const char *username
)
487 AuthUserHashPointer
*usernamehash
;
488 debugs(29, 9, HERE
<< "Looking for user '" << username
<< "'");
490 if (username
&& (usernamehash
= static_cast < auth_user_hash_pointer
* >(hash_lookup(proxy_auth_username_cache
, username
)))) {
491 while ((usernamehash
->user()->auth_type
!= AUTH_DIGEST
) && (usernamehash
->next
))
492 usernamehash
= static_cast<AuthUserHashPointer
*>(usernamehash
->next
);
494 if (usernamehash
->user()->auth_type
== AUTH_DIGEST
) {
495 return usernamehash
->user();
503 AuthDigestConfig::rotateHelpers()
505 /* schedule closure of existing helpers */
506 if (digestauthenticators
) {
507 helperShutdown(digestauthenticators
);
510 /* NP: dynamic helper restart will ensure they start up again as needed. */
513 /** delete the digest request structure. Does NOT delete related structures */
517 /** \todo this should be a Config call. */
519 if (digestauthenticators
)
520 helperShutdown(digestauthenticators
);
522 if (DigestFieldsInfo
) {
523 httpHeaderDestroyFieldsInfo(DigestFieldsInfo
, DIGEST_ENUM_END
);
524 DigestFieldsInfo
= NULL
;
527 authdigest_initialised
= 0;
529 if (!shutting_down
) {
530 authenticateDigestNonceReconfigure();
534 delete digestauthenticators
;
535 digestauthenticators
= NULL
;
537 PurgeCredentialsCache();
538 authenticateDigestNonceShutdown();
539 debugs(29, 2, "authenticateDigestDone: Digest authentication shut down.");
541 /* clear the global handle to this scheme. */
546 AuthDigestConfig::dump(StoreEntry
* entry
, const char *name
, AuthConfig
* scheme
)
548 wordlist
*list
= authenticate
;
549 debugs(29, 9, "authDigestCfgDump: Dumping configuration");
550 storeAppendPrintf(entry
, "%s %s", name
, "digest");
552 while (list
!= NULL
) {
553 storeAppendPrintf(entry
, " %s", list
->key
);
557 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",
558 name
, "digest", digestAuthRealm
,
559 name
, "digest", authenticateChildren
.n_max
, authenticateChildren
.n_startup
, authenticateChildren
.n_idle
, authenticateChildren
.concurrency
,
560 name
, "digest", noncemaxuses
,
561 name
, "digest", (int) noncemaxduration
,
562 name
, "digest", (int) nonceGCInterval
);
566 AuthDigestConfig::active() const
568 return authdigest_initialised
== 1;
572 AuthDigestConfig::configured() const
574 if ((authenticate
!= NULL
) &&
575 (authenticateChildren
.n_max
!= 0) &&
576 (digestAuthRealm
!= NULL
) && (noncemaxduration
> -1))
582 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
584 AuthDigestConfig::fixHeader(AuthUserRequest::Pointer auth_user_request
, HttpReply
*rep
, http_hdr_type hdrType
, HttpRequest
* request
)
591 if (auth_user_request
!= NULL
) {
592 AuthDigestUserRequest
*digest_request
;
593 digest_request
= dynamic_cast<AuthDigestUserRequest
*>(auth_user_request
.getRaw());
594 assert (digest_request
!= NULL
);
596 stale
= !digest_request
->flags
.invalid_password
;
599 /* on a 407 or 401 we always use a new nonce */
600 digest_nonce_h
*nonce
= authenticateDigestNonceNew();
602 debugs(29, 9, "authenticateFixHeader: Sending type:" << hdrType
<<
603 " header: 'Digest realm=\"" << digestAuthRealm
<< "\", nonce=\"" <<
604 authenticateDigestNonceNonceb64(nonce
) << "\", qop=\"" << QOP_AUTH
<<
605 "\", stale=" << (stale
? "true" : "false"));
607 /* in the future, for WWW auth we may want to support the domain entry */
608 httpHeaderPutStrf(&rep
->header
, hdrType
, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestAuthRealm
, authenticateDigestNonceNonceb64(nonce
), QOP_AUTH
, stale
? "true" : "false");
611 DigestUser::~DigestUser()
613 dlink_node
*link
, *tmplink
;
619 dlinkDelete(tmplink
, &nonces
);
620 authDigestNoncePurge(static_cast < digest_nonce_h
* >(tmplink
->data
));
621 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
622 dlinkNodeDelete(tmplink
);
627 DigestUser::ttl() const
629 int32_t global_ttl
= static_cast<int32_t>(expiretime
- squid_curtime
+ Config
.authenticateTTL
);
631 /* find the longest lasting nonce. */
632 int32_t latest_nonce
= -1;
633 dlink_node
*link
= nonces
.head
;
635 digest_nonce_h
*nonce
= static_cast<digest_nonce_h
*>(link
->data
);
636 if (nonce
->flags
.valid
&& nonce
->noncedata
.creationtime
> latest_nonce
)
637 latest_nonce
= nonce
->noncedata
.creationtime
;
641 if (latest_nonce
== -1)
642 return min(-1, global_ttl
);
644 int32_t nonce_ttl
= latest_nonce
- current_time
.tv_sec
+ static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest"))->noncemaxduration
;
646 return min(nonce_ttl
, global_ttl
);
649 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
652 AuthDigestConfig::init(AuthConfig
* scheme
)
655 DigestFieldsInfo
= httpHeaderBuildFieldsInfo(DigestAttrs
, DIGEST_ENUM_END
);
656 authenticateDigestNonceSetup();
657 authdigest_initialised
= 1;
659 if (digestauthenticators
== NULL
)
660 digestauthenticators
= new helper("digestauthenticator");
662 digestauthenticators
->cmdline
= authenticate
;
664 digestauthenticators
->childs
= authenticateChildren
;
666 digestauthenticators
->ipc_type
= IPC_STREAM
;
668 helperOpenServers(digestauthenticators
);
670 CBDATA_INIT_TYPE(authenticateStateData
);
675 AuthDigestConfig::registerWithCacheManager(void)
677 Mgr::RegisterAction("digestauthenticator",
678 "Digest User Authenticator Stats",
679 authenticateDigestStats
, 0, 1);
682 /* free any allocated configuration details */
684 AuthDigestConfig::done()
687 wordlistDestroy(&authenticate
);
689 safe_free(digestAuthRealm
);
692 AuthDigestConfig::AuthDigestConfig()
694 /* TODO: move into initialisation list */
696 nonceGCInterval
= 5 * 60;
698 noncemaxduration
= 30 * 60;
701 /* Not strict nonce count behaviour */
703 /* Verify nonce count */
708 AuthDigestConfig::parse(AuthConfig
* scheme
, int n_configured
, char *param_str
)
710 if (strcasecmp(param_str
, "program") == 0) {
712 wordlistDestroy(&authenticate
);
714 parse_wordlist(&authenticate
);
716 requirePathnameExists("auth_param digest program", authenticate
->key
);
717 } else if (strcasecmp(param_str
, "children") == 0) {
718 authenticateChildren
.parseConfig();
719 } else if (strcasecmp(param_str
, "realm") == 0) {
720 parse_eol(&digestAuthRealm
);
721 } else if (strcasecmp(param_str
, "nonce_garbage_interval") == 0) {
722 parse_time_t(&nonceGCInterval
);
723 } else if (strcasecmp(param_str
, "nonce_max_duration") == 0) {
724 parse_time_t(&noncemaxduration
);
725 } else if (strcasecmp(param_str
, "nonce_max_count") == 0) {
726 parse_int((int *) &noncemaxuses
);
727 } else if (strcasecmp(param_str
, "nonce_strictness") == 0) {
728 parse_onoff(&NonceStrictness
);
729 } else if (strcasecmp(param_str
, "check_nonce_count") == 0) {
730 parse_onoff(&CheckNonceCount
);
731 } else if (strcasecmp(param_str
, "post_workaround") == 0) {
732 parse_onoff(&PostWorkaround
);
733 } else if (strcasecmp(param_str
, "utf8") == 0) {
736 debugs(29, 0, "unrecognised digest auth scheme parameter '" << param_str
<< "'");
741 AuthDigestConfig::type() const
743 return digestScheme::GetInstance()->type();
748 authenticateDigestStats(StoreEntry
* sentry
)
750 helperStats(sentry
, digestauthenticators
, "Digest Authenticator Statistics");
753 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
756 authDigestNonceUserUnlink(digest_nonce_h
* nonce
)
758 DigestUser
*digest_user
;
759 dlink_node
*link
, *tmplink
;
767 digest_user
= nonce
->user
;
769 /* unlink from the user list. Yes we're crossing structures but this is the only
770 * time this code is needed
772 link
= digest_user
->nonces
.head
;
778 if (tmplink
->data
== nonce
) {
779 dlinkDelete(tmplink
, &digest_user
->nonces
);
780 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
781 dlinkNodeDelete(tmplink
);
786 /* this reference to user was not locked because freeeing the user frees
792 /* authDigestUserLinkNonce: add a nonce to a given user's struct */
795 authDigestUserLinkNonce(DigestUser
* user
, digest_nonce_h
* nonce
)
798 DigestUser
*digest_user
;
805 node
= digest_user
->nonces
.head
;
807 while (node
&& (node
->data
!= nonce
))
813 node
= dlinkNodeNew();
815 dlinkAddTail(nonce
, node
, &digest_user
->nonces
);
817 authDigestNonceLink(nonce
);
819 /* ping this nonce to this auth user */
820 assert((nonce
->user
== NULL
) || (nonce
->user
== user
));
822 /* we don't lock this reference because removing the user removes the
823 * hash too. Of course if that changes we're stuffed so read the code huh?
828 /* setup the necessary info to log the username */
829 static AuthUserRequest::Pointer
830 authDigestLogUsername(char *username
, AuthUserRequest::Pointer auth_user_request
)
832 assert(auth_user_request
!= NULL
);
834 /* log the username */
835 debugs(29, 9, "authDigestLogUsername: Creating new user for logging '" << username
<< "'");
836 AuthUser::Pointer digest_user
= new DigestUser(static_cast<AuthDigestConfig
*>(AuthConfig::Find("digest")));
837 /* save the credentials */
838 digest_user
->username(username
);
839 /* set the auth_user type */
840 digest_user
->auth_type
= AUTH_BROKEN
;
841 /* link the request to the user */
842 auth_user_request
->user(digest_user
);
843 return auth_user_request
;
847 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
848 * Auth_user structure.
850 AuthUserRequest::Pointer
851 AuthDigestConfig::decode(char const *proxy_auth
)
855 const char *pos
= NULL
;
856 char *username
= NULL
;
857 digest_nonce_h
*nonce
;
860 debugs(29, 9, "authenticateDigestDecodeAuth: beginning");
862 AuthDigestUserRequest
*digest_request
= new AuthDigestUserRequest();
864 /* trim DIGEST from string */
866 while (xisgraph(*proxy_auth
))
869 /* Trim leading whitespace before decoding */
870 while (xisspace(*proxy_auth
))
873 String
temp(proxy_auth
);
875 while (strListGetItem(&temp
, ',', &item
, &ilen
, &pos
)) {
876 /* isolate directive name & value */
879 if ((p
= (const char *)memchr(item
, '=', ilen
)) && (p
- item
< ilen
)) {
881 vlen
= ilen
- (p
- item
);
887 /* parse value. auth-param = token "=" ( token | quoted-string ) */
891 if (!httpHeaderParseQuotedString(p
, vlen
, &value
)) {
892 debugs(29, 9, "authDigestDecodeAuth: Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
896 value
.limitInit(p
, vlen
);
899 debugs(29, 9, "authDigestDecodeAuth: Failed to parse attribute '" << item
<< "' in '" << temp
<< "'");
904 http_digest_attr_type type
= (http_digest_attr_type
)httpHeaderIdByName(item
, nlen
, DigestFieldsInfo
, DIGEST_ENUM_END
);
907 case DIGEST_USERNAME
:
909 username
= xstrndup(value
.rawBuf(), value
.size() + 1);
910 debugs(29, 9, "authDigestDecodeAuth: Found Username '" << username
<< "'");
914 safe_free(digest_request
->realm
);
915 digest_request
->realm
= xstrndup(value
.rawBuf(), value
.size() + 1);
916 debugs(29, 9, "authDigestDecodeAuth: Found realm '" << digest_request
->realm
<< "'");
920 safe_free(digest_request
->qop
);
921 digest_request
->qop
= xstrndup(value
.rawBuf(), value
.size() + 1);
922 debugs(29, 9, "authDigestDecodeAuth: Found qop '" << digest_request
->qop
<< "'");
925 case DIGEST_ALGORITHM
:
926 safe_free(digest_request
->algorithm
);
927 digest_request
->algorithm
= xstrndup(value
.rawBuf(), value
.size() + 1);
928 debugs(29, 9, "authDigestDecodeAuth: Found algorithm '" << digest_request
->algorithm
<< "'");
932 safe_free(digest_request
->uri
);
933 digest_request
->uri
= xstrndup(value
.rawBuf(), value
.size() + 1);
934 debugs(29, 9, "authDigestDecodeAuth: Found uri '" << digest_request
->uri
<< "'");
938 safe_free(digest_request
->nonceb64
);
939 digest_request
->nonceb64
= xstrndup(value
.rawBuf(), value
.size() + 1);
940 debugs(29, 9, "authDigestDecodeAuth: Found nonce '" << digest_request
->nonceb64
<< "'");
944 if (value
.size() != 8) {
945 debugs(29, 9, "authDigestDecodeAuth: Invalid nc '" << value
<< "' in '" << temp
<< "'");
947 xstrncpy(digest_request
->nc
, value
.rawBuf(), value
.size() + 1);
948 debugs(29, 9, "authDigestDecodeAuth: Found noncecount '" << digest_request
->nc
<< "'");
952 safe_free(digest_request
->cnonce
);
953 digest_request
->cnonce
= xstrndup(value
.rawBuf(), value
.size() + 1);
954 debugs(29, 9, "authDigestDecodeAuth: Found cnonce '" << digest_request
->cnonce
<< "'");
957 case DIGEST_RESPONSE
:
958 safe_free(digest_request
->response
);
959 digest_request
->response
= xstrndup(value
.rawBuf(), value
.size() + 1);
960 debugs(29, 9, "authDigestDecodeAuth: Found response '" << digest_request
->response
<< "'");
964 debugs(29, 3, "authDigestDecodeAuth: Unknown attribute '" << item
<< "' in '" << temp
<< "'");
972 /* now we validate the data given to us */
975 * TODO: on invalid parameters we should return 400, not 407.
976 * Find some clean way of doing this. perhaps return a valid
977 * struct, and set the direction to clientwards combined with
978 * a change to the clientwards handling code (ie let the
979 * clientwards call set the error type (but limited to known
980 * correct values - 400/401/407
983 /* 2069 requirements */
985 /* do we have a username ? */
986 if (!username
|| username
[0] == '\0') {
987 debugs(29, 2, "authenticateDigestDecode: Empty or not present username");
988 return authDigestLogUsername(username
, digest_request
);
991 /* Sanity check of the username.
992 * " can not be allowed in usernames until * the digest helper protocol
995 if (strchr(username
, '"')) {
996 debugs(29, 2, "authenticateDigestDecode: Unacceptable username '" << username
<< "'");
997 return authDigestLogUsername(username
, digest_request
);
1000 /* do we have a realm ? */
1001 if (!digest_request
->realm
|| digest_request
->realm
[0] == '\0') {
1002 debugs(29, 2, "authenticateDigestDecode: Empty or not present realm");
1003 return authDigestLogUsername(username
, digest_request
);
1007 if (!digest_request
->nonceb64
|| digest_request
->nonceb64
[0] == '\0') {
1008 debugs(29, 2, "authenticateDigestDecode: Empty or not present nonce");
1009 return authDigestLogUsername(username
, digest_request
);
1012 /* we can't check the URI just yet. We'll check it in the
1013 * authenticate phase, but needs to be given */
1014 if (!digest_request
->uri
|| digest_request
->uri
[0] == '\0') {
1015 debugs(29, 2, "authenticateDigestDecode: Missing URI field");
1016 return authDigestLogUsername(username
, digest_request
);
1019 /* is the response the correct length? */
1020 if (!digest_request
->response
|| strlen(digest_request
->response
) != 32) {
1021 debugs(29, 2, "authenticateDigestDecode: Response length invalid");
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, 2, "authenticateDigestDecode: invalid algorithm specified!");
1031 return authDigestLogUsername(username
, digest_request
);
1034 /* 2617 requirements, indicated by qop */
1035 if (digest_request
->qop
) {
1037 /* check the qop is what we expected. */
1038 if (strcmp(digest_request
->qop
, QOP_AUTH
) != 0) {
1039 /* we received a qop option we didn't send */
1040 debugs(29, 2, "authenticateDigestDecode: Invalid qop option received");
1041 return authDigestLogUsername(username
, digest_request
);
1045 if (!digest_request
->cnonce
|| digest_request
->cnonce
[0] == '\0') {
1046 debugs(29, 2, "authenticateDigestDecode: Missing cnonce field");
1047 return authDigestLogUsername(username
, digest_request
);
1051 if (strlen(digest_request
->nc
) != 8 || strspn(digest_request
->nc
, "0123456789abcdefABCDEF") != 8) {
1052 debugs(29, 2, "authenticateDigestDecode: invalid nonce count");
1053 return authDigestLogUsername(username
, digest_request
);
1056 /* cnonce and nc both require qop */
1057 if (digest_request
->cnonce
|| digest_request
->nc
) {
1058 debugs(29, 2, "authenticateDigestDecode: missing qop!");
1059 return authDigestLogUsername(username
, digest_request
);
1063 /** below nonce state dependent **/
1066 nonce
= authenticateDigestNonceFindNonce(digest_request
->nonceb64
);
1068 /* we couldn't find a matching nonce! */
1069 debugs(29, 2, "authenticateDigestDecode: Unexpected or invalid nonce received");
1070 if (digest_request
->user() != NULL
)
1071 digest_request
->user()->credentials(AuthUser::Failed
);
1072 return authDigestLogUsername(username
, digest_request
);
1075 digest_request
->nonce
= nonce
;
1076 authDigestNonceLink(nonce
);
1078 /* check that we're not being hacked / the username hasn't changed */
1079 if (nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
1080 debugs(29, 2, "authenticateDigestDecode: Username for the nonce does not equal the username for the request");
1081 return authDigestLogUsername(username
, digest_request
);
1084 /* the method we'll check at the authenticate step as well */
1087 /* we don't send or parse opaques. Ok so we're flexable ... */
1090 DigestUser
*digest_user
;
1092 AuthUser::Pointer auth_user
;
1094 if ((auth_user
= authDigestUserFindUsername(username
)) == NULL
) {
1095 /* the user doesn't exist in the username cache yet */
1096 debugs(29, 9, "authDigestDecodeAuth: Creating new digest user '" << username
<< "'");
1097 digest_user
= new DigestUser(this);
1098 /* auth_user is a parent */
1099 auth_user
= digest_user
;
1100 /* save the username */
1101 digest_user
->username(username
);
1102 /* set the user type */
1103 digest_user
->auth_type
= AUTH_DIGEST
;
1104 /* this auth_user struct is the one to get added to the
1106 /* store user in hash's */
1107 digest_user
->addToNameCache();
1110 * Add the digest to the user so we can tell if a hacking
1111 * or spoofing attack is taking place. We do this by assuming
1112 * the user agent won't change user name without warning.
1114 authDigestUserLinkNonce(digest_user
, nonce
);
1116 debugs(29, 9, "authDigestDecodeAuth: Found user '" << username
<< "' in the user cache as '" << auth_user
<< "'");
1117 digest_user
= static_cast<DigestUser
*>(auth_user
.getRaw());
1121 /*link the request and the user */
1122 assert(digest_request
!= NULL
);
1124 digest_request
->user(digest_user
);
1125 debugs(29, 9, "username = '" << digest_user
->username() << "'\nrealm = '" <<
1126 digest_request
->realm
<< "'\nqop = '" << digest_request
->qop
<<
1127 "'\nalgorithm = '" << digest_request
->algorithm
<< "'\nuri = '" <<
1128 digest_request
->uri
<< "'\nnonce = '" << digest_request
->nonceb64
<<
1129 "'\nnc = '" << digest_request
->nc
<< "'\ncnonce = '" <<
1130 digest_request
->cnonce
<< "'\nresponse = '" <<
1131 digest_request
->response
<< "'\ndigestnonce = '" << nonce
<< "'");
1133 return digest_request
;
1136 DigestUser::DigestUser(AuthConfig
*aConfig
) : AuthUser(aConfig
), HA1created (0)