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.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 "digestScheme.h"
56 static HLPCB authenticateDigestHandleReply
;
57 static AUTHSSTATS authenticateDigestStats
;
59 static helper
*digestauthenticators
= NULL
;
61 static hash_table
*digest_nonce_cache
;
63 static AuthDigestConfig digestConfig
;
65 static int authdigest_initialised
= 0;
66 static MemAllocator
*digest_nonce_pool
= NULL
;
68 CBDATA_TYPE(DigestAuthenticateStateData
);
83 static const HttpHeaderFieldAttrs DigestAttrs
[DIGEST_ENUM_END
] = {
84 {"username", (http_hdr_type
)DIGEST_USERNAME
},
85 {"realm", (http_hdr_type
)DIGEST_REALM
},
86 {"qop", (http_hdr_type
)DIGEST_QOP
},
87 {"algorithm", (http_hdr_type
)DIGEST_ALGORITHM
},
88 {"uri", (http_hdr_type
)DIGEST_URI
},
89 {"nonce", (http_hdr_type
)DIGEST_NONCE
},
90 {"nc", (http_hdr_type
)DIGEST_NC
},
91 {"cnonce", (http_hdr_type
)DIGEST_CNONCE
},
92 {"response", (http_hdr_type
)DIGEST_RESPONSE
},
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 void authenticateDigestNonceShutdown(void);
109 static void authenticateDigestNonceReconfigure(void);
110 static const char *authenticateDigestNonceNonceb64(digest_nonce_h
* nonce
);
111 static int authDigestNonceIsValid(digest_nonce_h
* nonce
, char nc
[9]);
112 static int authDigestNonceIsStale(digest_nonce_h
* nonce
);
113 static void authDigestNonceEncode(digest_nonce_h
* nonce
);
114 static int authDigestNonceLastRequest(digest_nonce_h
* nonce
);
115 static void authDigestNonceLink(digest_nonce_h
* nonce
);
116 static void authDigestNonceUnlink(digest_nonce_h
* nonce
);
118 static int authDigestNonceLinks(digest_nonce_h
* nonce
);
120 static void authDigestNonceUserUnlink(digest_nonce_h
* nonce
);
121 static void authDigestNoncePurge(digest_nonce_h
* nonce
);
124 authDigestNonceEncode(digest_nonce_h
* nonce
)
132 nonce
->key
= xstrdup(base64_encode_bin((char *) &(nonce
->noncedata
), sizeof(digest_nonce_data
)));
135 static digest_nonce_h
*
136 authenticateDigestNonceNew(void)
138 digest_nonce_h
*newnonce
= static_cast < digest_nonce_h
* >(digest_nonce_pool
->alloc());
139 digest_nonce_h
*temp
;
141 /* NONCE CREATION - NOTES AND REASONING. RBC 20010108
142 * === EXCERPT FROM RFC 2617 ===
143 * The contents of the nonce are implementation dependent. The quality
144 * of the implementation depends on a good choice. A nonce might, for
145 * example, be constructed as the base 64 encoding of
147 * time-stamp H(time-stamp ":" ETag ":" private-key)
149 * where time-stamp is a server-generated time or other non-repeating
150 * value, ETag is the value of the HTTP ETag header associated with
151 * the requested entity, and private-key is data known only to the
152 * server. With a nonce of this form a server would recalculate the
153 * hash portion after receiving the client authentication header and
154 * reject the request if it did not match the nonce from that header
155 * or if the time-stamp value is not recent enough. In this way the
156 * server can limit the time of the nonce's validity. The inclusion of
157 * the ETag prevents a replay request for an updated version of the
158 * resource. (Note: including the IP address of the client in the
159 * nonce would appear to offer the server the ability to limit the
160 * reuse of the nonce to the same client that originally got it.
161 * However, that would break proxy farms, where requests from a single
162 * user often go through different proxies in the farm. Also, IP
163 * address spoofing is not that hard.)
166 * Now for my reasoning:
167 * We will not accept a unrecognised nonce->we have all recognisable
168 * nonces stored. If we send out unique base64 encodings we guarantee
169 * that a given nonce applies to only one user (barring attacks or
170 * really bad timing with expiry and creation). Using a random
171 * component in the nonce allows us to loop to find a unique nonce.
172 * We use H(nonce_data) so the nonce is meaningless to the reciever.
173 * So our nonce looks like base64(H(timestamp,pointertohash,randomdata))
174 * And even if our randomness is not very random (probably due to
175 * bad coding on my part) we don't really care - the timestamp and
176 * memory pointer also guarantee local uniqueness in the input to the hash
180 /* create a new nonce */
182 newnonce
->flags
.valid
= 1;
183 newnonce
->noncedata
.self
= newnonce
;
184 newnonce
->noncedata
.creationtime
= current_time
.tv_sec
;
185 newnonce
->noncedata
.randomdata
= squid_random();
187 authDigestNonceEncode(newnonce
);
189 * loop until we get a unique nonce. The nonce creation must
190 * have a random factor
193 while ((temp
= authenticateDigestNonceFindNonce((char const *) (newnonce
->key
)))) {
194 /* create a new nonce */
195 newnonce
->noncedata
.randomdata
= squid_random();
196 authDigestNonceEncode(newnonce
);
199 hash_join(digest_nonce_cache
, newnonce
);
200 /* the cache's link */
201 authDigestNonceLink(newnonce
);
202 newnonce
->flags
.incache
= 1;
203 debugs(29, 5, "authenticateDigestNonceNew: created nonce " << newnonce
<< " at " << newnonce
->noncedata
.creationtime
);
208 authenticateDigestNonceDelete(digest_nonce_h
* nonce
)
211 assert(nonce
->references
== 0);
214 if (nonce
->flags
.incache
)
215 hash_remove_link(digest_nonce_cache
, nonce
);
219 assert(nonce
->flags
.incache
== 0);
221 safe_free(nonce
->key
);
223 digest_nonce_pool
->free(nonce
);
228 authenticateDigestNonceSetup(void)
230 if (!digest_nonce_pool
)
231 digest_nonce_pool
= memPoolCreate("Digest Scheme nonce's", sizeof(digest_nonce_h
));
233 if (!digest_nonce_cache
) {
234 digest_nonce_cache
= hash_create((HASHCMP
*) strcmp
, 7921, hash_string
);
235 assert(digest_nonce_cache
);
236 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, digestConfig
.nonceGCInterval
, 1);
241 authenticateDigestNonceShutdown(void)
244 * We empty the cache of any nonces left in there.
246 digest_nonce_h
*nonce
;
248 if (digest_nonce_cache
) {
249 debugs(29, 2, "authenticateDigestNonceShutdown: Shutting down nonce cache ");
250 hash_first(digest_nonce_cache
);
252 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
253 assert(nonce
->flags
.incache
);
254 authDigestNoncePurge(nonce
);
259 if (digest_nonce_pool
) {
260 delete digest_nonce_pool
;
261 digest_nonce_pool
= NULL
;
265 debugs(29, 2, "authenticateDigestNonceShutdown: Nonce cache shutdown");
269 authenticateDigestNonceReconfigure(void)
273 authenticateDigestNonceCacheCleanup(void *data
)
276 * We walk the hash by nonceb64 as that is the unique key we
277 * use. For big hash tables we could consider stepping through
278 * the cache, 100/200 entries at a time. Lets see how it flies
281 digest_nonce_h
*nonce
;
282 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Cleaning the nonce cache now");
283 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Current time: " << current_time
.tv_sec
);
284 hash_first(digest_nonce_cache
);
286 while ((nonce
= ((digest_nonce_h
*) hash_next(digest_nonce_cache
)))) {
287 debugs(29, 3, "authenticateDigestNonceCacheCleanup: nonce entry : " << nonce
<< " '" << (char *) nonce
->key
<< "'");
288 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Creation time: " << nonce
->noncedata
.creationtime
);
290 if (authDigestNonceIsStale(nonce
)) {
291 debugs(29, 4, "authenticateDigestNonceCacheCleanup: Removing nonce " << (char *) nonce
->key
<< " from cache due to timeout.");
292 assert(nonce
->flags
.incache
);
293 /* invalidate nonce so future requests fail */
294 nonce
->flags
.valid
= 0;
295 /* if it is tied to a auth_user, remove the tie */
296 authDigestNonceUserUnlink(nonce
);
297 authDigestNoncePurge(nonce
);
301 debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.");
303 if (digestConfig
.active())
304 eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup
, NULL
, digestConfig
.nonceGCInterval
, 1);
308 authDigestNonceLink(digest_nonce_h
* nonce
)
310 assert(nonce
!= NULL
);
312 debugs(29, 9, "authDigestNonceLink: nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
317 authDigestNonceLinks(digest_nonce_h
* nonce
)
322 return nonce
->references
;
328 authDigestNonceUnlink(digest_nonce_h
* nonce
)
330 assert(nonce
!= NULL
);
332 if (nonce
->references
> 0) {
335 debugs(29, 1, "authDigestNonceUnlink; Attempt to lower nonce " << nonce
<< " refcount below 0!");
338 debugs(29, 9, "authDigestNonceUnlink: nonce '" << nonce
<< "' now at '" << nonce
->references
<< "'.");
340 if (nonce
->references
== 0)
341 authenticateDigestNonceDelete(nonce
);
345 authenticateDigestNonceNonceb64(digest_nonce_h
* nonce
)
350 return (char const *) nonce
->key
;
353 static digest_nonce_h
*
354 authenticateDigestNonceFindNonce(const char *nonceb64
)
356 digest_nonce_h
*nonce
= NULL
;
358 if (nonceb64
== NULL
)
361 debugs(29, 9, "authDigestNonceFindNonce:looking for nonceb64 '" << nonceb64
<< "' in the nonce cache.");
363 nonce
= static_cast < digest_nonce_h
* >(hash_lookup(digest_nonce_cache
, nonceb64
));
365 if ((nonce
== NULL
) || (strcmp(authenticateDigestNonceNonceb64(nonce
), nonceb64
)))
368 debugs(29, 9, "authDigestNonceFindNonce: Found nonce '" << nonce
<< "'");
374 authDigestNonceIsValid(digest_nonce_h
* nonce
, char nc
[9])
377 /* do we have a nonce ? */
382 intnc
= strtol(nc
, NULL
, 16);
384 /* has it already been invalidated ? */
385 if (!nonce
->flags
.valid
) {
386 debugs(29, 4, "authDigestNonceIsValid: Nonce already invalidated");
390 /* is the nonce-count ok ? */
391 if (!digestConfig
.CheckNonceCount
) {
393 return -1; /* forced OK by configuration */
396 if ((digestConfig
.NonceStrictness
&& intnc
!= nonce
->nc
+ 1) ||
397 intnc
< nonce
->nc
+ 1) {
398 debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match");
399 nonce
->flags
.valid
= 0;
404 /* increment the nonce count - we've already checked that intnc is a
405 * valid representation for us, so we don't need the test here.
413 authDigestNonceIsStale(digest_nonce_h
* nonce
)
415 /* do we have a nonce ? */
420 /* has it's max duration expired? */
421 if (nonce
->noncedata
.creationtime
+ digestConfig
.noncemaxduration
< current_time
.tv_sec
) {
422 debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " <<
423 nonce
->noncedata
.creationtime
<< " " <<
424 digestConfig
.noncemaxduration
<< " " <<
425 current_time
.tv_sec
);
427 nonce
->flags
.valid
= 0;
431 if (nonce
->nc
> 99999998) {
432 debugs(29, 4, "authDigestNonceIsStale: Nonce count overflow");
433 nonce
->flags
.valid
= 0;
437 if (nonce
->nc
> digestConfig
.noncemaxuses
) {
438 debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit");
439 nonce
->flags
.valid
= 0;
447 /* return -1 if the digest will be stale on the next request */
449 authDigestNonceLastRequest(digest_nonce_h
* nonce
)
454 if (nonce
->nc
== 99999997) {
455 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to overflow");
459 if (nonce
->nc
>= digestConfig
.noncemaxuses
- 1) {
460 debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit");
464 /* and other tests are possible. */
469 authDigestNoncePurge(digest_nonce_h
* nonce
)
474 if (!nonce
->flags
.incache
)
477 hash_remove_link(digest_nonce_cache
, nonce
);
479 nonce
->flags
.incache
= 0;
481 /* the cache's link */
482 authDigestNonceUnlink(nonce
);
485 /* USER related functions */
487 authDigestUserFindUsername(const char *username
)
489 AuthUserHashPointer
*usernamehash
;
491 debugs(29, 9, HERE
<< "Looking for user '" << username
<< "'");
493 if (username
&& (usernamehash
= static_cast < auth_user_hash_pointer
* >(hash_lookup(proxy_auth_username_cache
, username
)))) {
494 while ((usernamehash
->user()->auth_type
!= AUTH_DIGEST
) &&
495 (usernamehash
->next
))
496 usernamehash
= static_cast < auth_user_hash_pointer
* >(usernamehash
->next
);
500 if (usernamehash
->user()->auth_type
== AUTH_DIGEST
) {
501 auth_user
= usernamehash
->user();
511 authDigestUserShutdown(void)
513 /** \todo Future work: the auth framework could flush it's cache */
514 AuthUserHashPointer
*usernamehash
;
516 hash_first(proxy_auth_username_cache
);
518 while ((usernamehash
= ((auth_user_hash_pointer
*) hash_next(proxy_auth_username_cache
)))) {
519 auth_user
= usernamehash
->user();
521 if (strcmp(auth_user
->config
->type(), "digest") == 0)
526 /** delete the digest request structure. Does NOT delete related structures */
530 /** \todo this should be a Config call. */
532 if (digestauthenticators
)
533 helperShutdown(digestauthenticators
);
535 httpHeaderDestroyFieldsInfo(DigestFieldsInfo
, DIGEST_ENUM_END
);
536 DigestFieldsInfo
= NULL
;
538 authdigest_initialised
= 0;
540 if (!shutting_down
) {
541 authenticateDigestNonceReconfigure();
545 delete digestauthenticators
;
546 digestauthenticators
= NULL
;
548 authDigestUserShutdown();
549 authenticateDigestNonceShutdown();
550 debugs(29, 2, "authenticateDigestDone: Digest authentication shut down.");
554 AuthDigestConfig::dump(StoreEntry
* entry
, const char *name
, AuthConfig
* scheme
)
556 wordlist
*list
= authenticate
;
557 debugs(29, 9, "authDigestCfgDump: Dumping configuration");
558 storeAppendPrintf(entry
, "%s %s", name
, "digest");
560 while (list
!= NULL
) {
561 storeAppendPrintf(entry
, " %s", list
->key
);
565 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",
566 name
, "digest", digestAuthRealm
,
567 name
, "digest", authenticateChildren
.n_max
, authenticateChildren
.n_startup
, authenticateChildren
.n_idle
, authenticateChildren
.concurrency
,
568 name
, "digest", noncemaxuses
,
569 name
, "digest", (int) noncemaxduration
,
570 name
, "digest", (int) nonceGCInterval
);
574 AuthDigestConfig::active() const
576 return authdigest_initialised
== 1;
580 AuthDigestConfig::configured() const
582 if ((authenticate
!= NULL
) &&
583 (authenticateChildren
.n_max
!= 0) &&
584 (digestAuthRealm
!= NULL
) && (noncemaxduration
> -1))
591 AuthDigestUserRequest::authenticated() const
593 if (credentials() == Ok
)
599 /** log a digest user in
602 AuthDigestUserRequest::authenticate(HttpRequest
* request
, ConnStateData
* conn
, http_hdr_type type
)
605 AuthDigestUserRequest
*digest_request
;
606 digest_user_h
*digest_user
;
612 assert(authUser() != NULL
);
613 auth_user
= authUser();
615 digest_user
= dynamic_cast < digest_user_h
* >(auth_user
);
617 assert(digest_user
!= NULL
);
619 /* if the check has corrupted the user, just return */
621 if (credentials() == Failed
) {
625 digest_request
= this;
627 /* do we have the HA1 */
629 if (!digest_user
->HA1created
) {
630 credentials(Pending
);
634 if (digest_request
->nonce
== NULL
) {
635 /* this isn't a nonce we issued */
640 DigestCalcHA1(digest_request
->algorithm
, NULL
, NULL
, NULL
,
641 authenticateDigestNonceNonceb64(digest_request
->nonce
),
642 digest_request
->cnonce
,
643 digest_user
->HA1
, SESSIONKEY
);
644 DigestCalcResponse(SESSIONKEY
, authenticateDigestNonceNonceb64(digest_request
->nonce
),
645 digest_request
->nc
, digest_request
->cnonce
, digest_request
->qop
,
646 RequestMethodStr(request
->method
), digest_request
->uri
, HA2
, Response
);
648 debugs(29, 9, "\nResponse = '" << digest_request
->response
<< "'\nsquid is = '" << Response
<< "'");
650 if (strcasecmp(digest_request
->response
, Response
) != 0) {
651 if (!digest_request
->flags
.helper_queried
) {
652 /* Query the helper in case the password has changed */
653 digest_request
->flags
.helper_queried
= 1;
654 digest_request
->credentials_ok
= Pending
;
658 if (digestConfig
.PostWorkaround
&& request
->method
!= METHOD_GET
) {
659 /* Ugly workaround for certain very broken browsers using the
660 * wrong method to calculate the request-digest on POST request.
661 * This should be deleted once Digest authentication becomes more
662 * widespread and such broken browsers no longer are commonly
665 DigestCalcResponse(SESSIONKEY
, authenticateDigestNonceNonceb64(digest_request
->nonce
),
666 digest_request
->nc
, digest_request
->cnonce
, digest_request
->qop
,
667 RequestMethodStr(METHOD_GET
), digest_request
->uri
, HA2
, Response
);
669 if (strcasecmp(digest_request
->response
, Response
)) {
671 digest_request
->setDenyMessage("Incorrect password");
674 const char *useragent
= request
->header
.getStr(HDR_USER_AGENT
);
676 static IpAddress last_broken_addr
;
677 static int seen_broken_client
= 0;
679 if (!seen_broken_client
) {
680 last_broken_addr
.SetNoAddr();
681 seen_broken_client
= 1;
684 if (last_broken_addr
!= request
->client_addr
) {
685 debugs(29, 1, "\nDigest POST bug detected from " <<
686 request
->client_addr
<< " using '" <<
687 (useragent
? useragent
: "-") <<
688 "'. Please upgrade browser. See Bug #630 for details.");
690 last_broken_addr
= request
->client_addr
;
695 digest_request
->flags
.invalid_password
= 1;
696 digest_request
->setDenyMessage("Incorrect password");
700 /* check for stale nonce */
701 if (!authDigestNonceIsValid(digest_request
->nonce
, digest_request
->nc
)) {
702 debugs(29, 3, "authenticateDigestAuthenticateuser: user '" << digest_user
->username() << "' validated OK but nonce stale");
704 digest_request
->setDenyMessage("Stale nonce");
711 /* password was checked and did match */
712 debugs(29, 4, "authenticateDigestAuthenticateuser: user '" << digest_user
->username() << "' validated OK");
714 /* auth_user is now linked, we reset these values
715 * after external auth occurs anyway */
716 auth_user
->expiretime
= current_time
.tv_sec
;
721 AuthDigestUserRequest::module_direction()
723 switch (credentials()) {
737 /* send new challenge */
744 /* add the [proxy]authorisation header */
746 AuthDigestUserRequest::addHeader(HttpReply
* rep
, int accel
)
750 /* don't add to authentication error pages */
752 if ((!accel
&& rep
->sline
.status
== HTTP_PROXY_AUTHENTICATION_REQUIRED
)
753 || (accel
&& rep
->sline
.status
== HTTP_UNAUTHORIZED
))
756 type
= accel
? HDR_AUTHENTICATION_INFO
: HDR_PROXY_AUTHENTICATION_INFO
;
759 /* test for http/1.1 transfer chunked encoding */
765 if ((digestConfig
.authenticate
) && authDigestNonceLastRequest(nonce
)) {
766 flags
.authinfo_sent
= 1;
767 debugs(29, 9, "authDigestAddHead: Sending type:" << type
<< " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce
) << "\"");
768 httpHeaderPutStrf(&rep
->header
, type
, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce
));
773 /* add the [proxy]authorisation header */
775 AuthDigestUserRequest::addTrailer(HttpReply
* rep
, int accel
)
779 if (!auth_user_request
)
783 /* has the header already been send? */
784 if (flags
.authinfo_sent
)
787 /* don't add to authentication error pages */
788 if ((!accel
&& rep
->sline
.status
== HTTP_PROXY_AUTHENTICATION_REQUIRED
)
789 || (accel
&& rep
->sline
.status
== HTTP_UNAUTHORIZED
))
792 type
= accel
? HDR_AUTHENTICATION_INFO
: HDR_PROXY_AUTHENTICATION_INFO
;
794 if ((digestConfig
.authenticate
) && authDigestNonceLastRequest(nonce
)) {
795 debugs(29, 9, "authDigestAddTrailer: Sending type:" << type
<< " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce
) << "\"");
796 httpTrailerPutStrf(&rep
->header
, type
, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce
));
802 /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
804 AuthDigestConfig::fixHeader(AuthUserRequest
*auth_user_request
, HttpReply
*rep
, http_hdr_type hdrType
, HttpRequest
* request
)
811 if (auth_user_request
) {
812 AuthDigestUserRequest
*digest_request
;
813 digest_request
= dynamic_cast < AuthDigestUserRequest
* >(auth_user_request
);
814 assert (digest_request
!= NULL
);
816 stale
= !digest_request
->flags
.invalid_password
;
819 /* on a 407 or 401 we always use a new nonce */
820 digest_nonce_h
*nonce
= authenticateDigestNonceNew();
822 debugs(29, 9, "authenticateFixHeader: Sending type:" << hdrType
<<
823 " header: 'Digest realm=\"" << digestAuthRealm
<< "\", nonce=\"" <<
824 authenticateDigestNonceNonceb64(nonce
) << "\", qop=\"" << QOP_AUTH
<<
825 "\", stale=" << (stale
? "true" : "false"));
827 /* in the future, for WWW auth we may want to support the domain entry */
828 httpHeaderPutStrf(&rep
->header
, hdrType
, "Digest realm=\"%s\", nonce=\"%s\", qop=\"%s\", stale=%s", digestAuthRealm
, authenticateDigestNonceNonceb64(nonce
), QOP_AUTH
, stale
? "true" : "false");
831 DigestUser::~DigestUser()
834 dlink_node
*link
, *tmplink
;
840 dlinkDelete(tmplink
, &nonces
);
841 authDigestNoncePurge(static_cast < digest_nonce_h
* >(tmplink
->data
));
842 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
843 dlinkNodeDelete(tmplink
);
848 authenticateDigestHandleReply(void *data
, char *reply
)
850 DigestAuthenticateStateData
*replyData
= static_cast < DigestAuthenticateStateData
* >(data
);
851 AuthUserRequest
*auth_user_request
;
852 AuthDigestUserRequest
*digest_request
;
853 digest_user_h
*digest_user
;
856 debugs(29, 9, "authenticateDigestHandleReply: {" << (reply
? reply
: "<NULL>") << "}");
859 if ((t
= strchr(reply
, ' ')))
862 if (*reply
== '\0' || *reply
== '\n')
866 assert(replyData
->auth_user_request
!= NULL
);
867 auth_user_request
= replyData
->auth_user_request
;
868 digest_request
= dynamic_cast < AuthDigestUserRequest
* >(auth_user_request
);
869 assert(digest_request
);
871 digest_user
= dynamic_cast < digest_user_h
* >(auth_user_request
->user());
872 assert(digest_user
!= NULL
);
874 if (reply
&& (strncasecmp(reply
, "ERR", 3) == 0)) {
875 digest_request
->credentials(AuthDigestUserRequest::Failed
);
876 digest_request
->flags
.invalid_password
= 1;
879 digest_request
->setDenyMessage(t
);
881 CvtBin(reply
, digest_user
->HA1
);
882 digest_user
->HA1created
= 1;
885 if (cbdataReferenceValidDone(replyData
->data
, &cbdata
))
886 replyData
->handler(cbdata
, NULL
);
888 //we know replyData->auth_user_request != NULL, or we'd have asserted
889 AUTHUSERREQUESTUNLOCK(replyData
->auth_user_request
, "replyData");
891 cbdataFree(replyData
);
894 /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
897 AuthDigestConfig::init(AuthConfig
* scheme
)
900 DigestFieldsInfo
= httpHeaderBuildFieldsInfo(DigestAttrs
, DIGEST_ENUM_END
);
901 authenticateDigestNonceSetup();
902 authdigest_initialised
= 1;
904 if (digestauthenticators
== NULL
)
905 digestauthenticators
= new helper("digestauthenticator");
907 digestauthenticators
->cmdline
= authenticate
;
909 digestauthenticators
->childs
= authenticateChildren
;
911 digestauthenticators
->ipc_type
= IPC_STREAM
;
913 helperOpenServers(digestauthenticators
);
915 CBDATA_INIT_TYPE(DigestAuthenticateStateData
);
920 AuthDigestConfig::registerWithCacheManager(void)
922 CacheManager::GetInstance()->
923 registerAction("digestauthenticator",
924 "Digest User Authenticator Stats",
925 authenticateDigestStats
, 0, 1);
928 /* free any allocated configuration details */
930 AuthDigestConfig::done()
933 wordlistDestroy(&authenticate
);
935 safe_free(digestAuthRealm
);
938 AuthDigestConfig::AuthDigestConfig() : authenticateChildren(20)
940 /* TODO: move into initialisation list */
942 nonceGCInterval
= 5 * 60;
944 noncemaxduration
= 30 * 60;
947 /* Not strict nonce count behaviour */
949 /* Verify nonce count */
954 AuthDigestConfig::parse(AuthConfig
* scheme
, int n_configured
, char *param_str
)
956 if (strcasecmp(param_str
, "program") == 0) {
958 wordlistDestroy(&authenticate
);
960 parse_wordlist(&authenticate
);
962 requirePathnameExists("auth_param digest program", authenticate
->key
);
963 } else if (strcasecmp(param_str
, "children") == 0) {
964 authenticateChildren
.parseConfig();
965 } else if (strcasecmp(param_str
, "realm") == 0) {
966 parse_eol(&digestAuthRealm
);
967 } else if (strcasecmp(param_str
, "nonce_garbage_interval") == 0) {
968 parse_time_t(&nonceGCInterval
);
969 } else if (strcasecmp(param_str
, "nonce_max_duration") == 0) {
970 parse_time_t(&noncemaxduration
);
971 } else if (strcasecmp(param_str
, "nonce_max_count") == 0) {
972 parse_int((int *) &noncemaxuses
);
973 } else if (strcasecmp(param_str
, "nonce_strictness") == 0) {
974 parse_onoff(&NonceStrictness
);
975 } else if (strcasecmp(param_str
, "check_nonce_count") == 0) {
976 parse_onoff(&CheckNonceCount
);
977 } else if (strcasecmp(param_str
, "post_workaround") == 0) {
978 parse_onoff(&PostWorkaround
);
979 } else if (strcasecmp(param_str
, "utf8") == 0) {
982 debugs(29, 0, "unrecognised digest auth scheme parameter '" << param_str
<< "'");
987 AuthDigestConfig::type() const
989 return digestScheme::GetInstance().type();
994 authenticateDigestStats(StoreEntry
* sentry
)
996 helperStats(sentry
, digestauthenticators
, "Digest Authenticator Statistics");
999 /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */
1002 authDigestNonceUserUnlink(digest_nonce_h
* nonce
)
1004 digest_user_h
*digest_user
;
1005 dlink_node
*link
, *tmplink
;
1013 digest_user
= nonce
->user
;
1015 /* unlink from the user list. Yes we're crossing structures but this is the only
1016 * time this code is needed
1018 link
= digest_user
->nonces
.head
;
1024 if (tmplink
->data
== nonce
) {
1025 dlinkDelete(tmplink
, &digest_user
->nonces
);
1026 authDigestNonceUnlink(static_cast < digest_nonce_h
* >(tmplink
->data
));
1027 dlinkNodeDelete(tmplink
);
1032 /* this reference to user was not locked because freeeing the user frees
1038 /* authDigestUserLinkNonce: add a nonce to a given user's struct */
1041 authDigestUserLinkNonce(DigestUser
* user
, digest_nonce_h
* nonce
)
1044 digest_user_h
*digest_user
;
1046 if (!user
|| !nonce
)
1051 node
= digest_user
->nonces
.head
;
1053 while (node
&& (node
->data
!= nonce
))
1059 node
= dlinkNodeNew();
1061 dlinkAddTail(nonce
, node
, &digest_user
->nonces
);
1063 authDigestNonceLink(nonce
);
1065 /* ping this nonce to this auth user */
1066 assert((nonce
->user
== NULL
) || (nonce
->user
== user
));
1068 /* we don't lock this reference because removing the user removes the
1069 * hash too. Of course if that changes we're stuffed so read the code huh?
1074 /* setup the necessary info to log the username */
1075 static AuthUserRequest
*
1076 authDigestLogUsername(char *username
, AuthDigestUserRequest
*auth_user_request
)
1078 assert(auth_user_request
!= NULL
);
1080 /* log the username */
1081 debugs(29, 9, "authDigestLogUsername: Creating new user for logging '" << username
<< "'");
1082 digest_user_h
*digest_user
= new DigestUser(&digestConfig
);
1083 /* save the credentials */
1084 digest_user
->username(username
);
1085 /* set the auth_user type */
1086 digest_user
->auth_type
= AUTH_BROKEN
;
1087 /* link the request to the user */
1088 auth_user_request
->authUser(digest_user
);
1089 auth_user_request
->user(digest_user
);
1090 digest_user
->addRequest (auth_user_request
);
1091 return auth_user_request
;
1095 * Decode a Digest [Proxy-]Auth string, placing the results in the passed
1096 * Auth_user structure.
1099 AuthDigestConfig::decode(char const *proxy_auth
)
1103 const char *pos
= NULL
;
1104 char *username
= NULL
;
1105 digest_nonce_h
*nonce
;
1108 debugs(29, 9, "authenticateDigestDecodeAuth: beginning");
1110 AuthDigestUserRequest
*digest_request
= new AuthDigestUserRequest();
1112 /* trim DIGEST from string */
1114 while (xisgraph(*proxy_auth
))
1117 /* Trim leading whitespace before decoding */
1118 while (xisspace(*proxy_auth
))
1121 String
temp(proxy_auth
);
1123 while (strListGetItem(&temp
, ',', &item
, &ilen
, &pos
)) {
1124 if ((p
= strchr(item
, '=')) && (p
- item
< ilen
))
1127 if (!strncmp(item
, "username", ilen
)) {
1130 while (xisspace(*p
))
1136 safe_free(username
);
1137 username
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
1139 debugs(29, 9, "authDigestDecodeAuth: Found Username '" << username
<< "'");
1140 } else if (!strncmp(item
, "realm", ilen
)) {
1143 while (xisspace(*p
))
1149 safe_free(digest_request
->realm
);
1150 digest_request
->realm
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
1152 debugs(29, 9, "authDigestDecodeAuth: Found realm '" << digest_request
->realm
<< "'");
1153 } else if (!strncmp(item
, "qop", ilen
)) {
1156 while (xisspace(*p
))
1163 safe_free(digest_request
->qop
);
1164 digest_request
->qop
= xstrndup(p
, strcspn(p
, "\" \t\r\n()<>@,;:\\/[]?={}") + 1);
1166 debugs(29, 9, "authDigestDecodeAuth: Found qop '" << digest_request
->qop
<< "'");
1167 } else if (!strncmp(item
, "algorithm", ilen
)) {
1170 while (xisspace(*p
))
1177 safe_free(digest_request
->algorithm
);
1178 digest_request
->algorithm
= xstrndup(p
, strcspn(p
, "\" \t\r\n()<>@,;:\\/[]?={}") + 1);
1180 debugs(29, 9, "authDigestDecodeAuth: Found algorithm '" << digest_request
->algorithm
<< "'");
1181 } else if (!strncmp(item
, "uri", ilen
)) {
1184 while (xisspace(*p
))
1190 safe_free(digest_request
->uri
);
1191 digest_request
->uri
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
1193 debugs(29, 9, "authDigestDecodeAuth: Found uri '" << digest_request
->uri
<< "'");
1194 } else if (!strncmp(item
, "nonce", ilen
)) {
1197 while (xisspace(*p
))
1203 safe_free(digest_request
->nonceb64
);
1204 digest_request
->nonceb64
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
1206 debugs(29, 9, "authDigestDecodeAuth: Found nonce '" << digest_request
->nonceb64
<< "'");
1207 } else if (!strncmp(item
, "nc", ilen
)) {
1210 while (xisspace(*p
))
1213 xstrncpy(digest_request
->nc
, p
, 9);
1215 debugs(29, 9, "authDigestDecodeAuth: Found noncecount '" << digest_request
->nc
<< "'");
1216 } else if (!strncmp(item
, "cnonce", ilen
)) {
1219 while (xisspace(*p
))
1225 safe_free(digest_request
->cnonce
);
1226 digest_request
->cnonce
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
1228 debugs(29, 9, "authDigestDecodeAuth: Found cnonce '" << digest_request
->cnonce
<< "'");
1229 } else if (!strncmp(item
, "response", ilen
)) {
1232 while (xisspace(*p
))
1238 safe_free(digest_request
->response
);
1239 digest_request
->response
= xstrndup(p
, strchr(p
, '"') + 1 - p
);
1241 debugs(29, 9, "authDigestDecodeAuth: Found response '" << digest_request
->response
<< "'");
1248 /* now we validate the data given to us */
1251 * TODO: on invalid parameters we should return 400, not 407.
1252 * Find some clean way of doing this. perhaps return a valid
1253 * struct, and set the direction to clientwards combined with
1254 * a change to the clientwards handling code (ie let the
1255 * clientwards call set the error type (but limited to known
1256 * correct values - 400/401/407
1259 /* first the NONCE count */
1261 if (digest_request
->cnonce
&& strlen(digest_request
->nc
) != 8) {
1262 debugs(29, 4, "authenticateDigestDecode: nonce count length invalid");
1263 return authDigestLogUsername(username
, digest_request
);
1267 nonce
= authenticateDigestNonceFindNonce(digest_request
->nonceb64
);
1270 /* we couldn't find a matching nonce! */
1271 debugs(29, 4, "authenticateDigestDecode: Unexpected or invalid nonce received");
1272 return authDigestLogUsername(username
, digest_request
);
1275 digest_request
->nonce
= nonce
;
1276 authDigestNonceLink(nonce
);
1278 /* check the qop is what we expected. Note that for compatability with
1279 * RFC 2069 we should support a missing qop. Tough. */
1281 if (digest_request
->qop
&& strcmp(digest_request
->qop
, QOP_AUTH
) != 0) {
1282 /* we received a qop option we didn't send */
1283 debugs(29, 4, "authenticateDigestDecode: Invalid qop option received");
1284 return authDigestLogUsername(username
, digest_request
);
1287 /* we can't check the URI just yet. We'll check it in the
1288 * authenticate phase, but needs to be given */
1289 if (!digest_request
->uri
) {
1290 debugs(29, 4, "authenticateDigestDecode: Missing URI field");
1291 return authDigestLogUsername(username
, digest_request
);
1294 /* is the response the correct length? */
1296 if (!digest_request
->response
|| strlen(digest_request
->response
) != 32) {
1297 debugs(29, 4, "authenticateDigestDecode: Response length invalid");
1298 return authDigestLogUsername(username
, digest_request
);
1301 /* do we have a username ? */
1302 if (!username
|| username
[0] == '\0') {
1303 debugs(29, 4, "authenticateDigestDecode: Empty or not present username");
1304 return authDigestLogUsername(username
, digest_request
);
1307 /* check that we're not being hacked / the username hasn't changed */
1308 if (nonce
->user
&& strcmp(username
, nonce
->user
->username())) {
1309 debugs(29, 4, "authenticateDigestDecode: Username for the nonce does not equal the username for the request");
1310 return authDigestLogUsername(username
, digest_request
);
1313 /* if we got a qop, did we get a cnonce or did we get a cnonce wihtout a qop? */
1314 if ((digest_request
->qop
&& !digest_request
->cnonce
)
1315 || (!digest_request
->qop
&& digest_request
->cnonce
)) {
1316 debugs(29, 4, "authenticateDigestDecode: qop without cnonce, or vice versa!");
1317 return authDigestLogUsername(username
, digest_request
);
1320 /* check the algorithm is present and supported */
1321 if (!digest_request
->algorithm
)
1322 digest_request
->algorithm
= xstrndup("MD5", 4);
1323 else if (strcmp(digest_request
->algorithm
, "MD5")
1324 && strcmp(digest_request
->algorithm
, "MD5-sess")) {
1325 debugs(29, 4, "authenticateDigestDecode: invalid algorithm specified!");
1326 return authDigestLogUsername(username
, digest_request
);
1329 /* the method we'll check at the authenticate step as well */
1332 /* we don't send or parse opaques. Ok so we're flexable ... */
1335 digest_user_h
*digest_user
;
1337 AuthUser
*auth_user
;
1339 if ((auth_user
= authDigestUserFindUsername(username
)) == NULL
) {
1340 /* the user doesn't exist in the username cache yet */
1341 debugs(29, 9, "authDigestDecodeAuth: Creating new digest user '" << username
<< "'");
1342 digest_user
= new DigestUser (&digestConfig
);
1343 /* auth_user is a parent */
1344 auth_user
= digest_user
;
1345 /* save the username */
1346 digest_user
->username(username
);
1347 /* set the user type */
1348 digest_user
->auth_type
= AUTH_DIGEST
;
1349 /* this auth_user struct is the one to get added to the
1351 /* store user in hash's */
1352 digest_user
->addToNameCache();
1355 * Add the digest to the user so we can tell if a hacking
1356 * or spoofing attack is taking place. We do this by assuming
1357 * the user agent won't change user name without warning.
1359 authDigestUserLinkNonce(digest_user
, nonce
);
1361 debugs(29, 9, "authDigestDecodeAuth: Found user '" << username
<< "' in the user cache as '" << auth_user
<< "'");
1362 digest_user
= static_cast < digest_user_h
* >(auth_user
);
1366 /*link the request and the user */
1367 assert(digest_request
!= NULL
);
1369 digest_request
->authUser (digest_user
);
1371 digest_request
->user(digest_user
);
1373 digest_user
->addRequest (digest_request
);
1375 debugs(29, 9, "username = '" << digest_user
->username() << "'\nrealm = '" <<
1376 digest_request
->realm
<< "'\nqop = '" << digest_request
->qop
<<
1377 "'\nalgorithm = '" << digest_request
->algorithm
<< "'\nuri = '" <<
1378 digest_request
->uri
<< "'\nnonce = '" << digest_request
->nonceb64
<<
1379 "'\nnc = '" << digest_request
->nc
<< "'\ncnonce = '" <<
1380 digest_request
->cnonce
<< "'\nresponse = '" <<
1381 digest_request
->response
<< "'\ndigestnonce = '" << nonce
<< "'");
1383 return digest_request
;
1386 /* send the initial data to a digest authenticator module */
1388 AuthDigestUserRequest::module_start(RH
* handler
, void *data
)
1390 DigestAuthenticateStateData
*r
= NULL
;
1392 digest_user_h
*digest_user
;
1393 assert(user()->auth_type
== AUTH_DIGEST
);
1394 digest_user
= dynamic_cast < digest_user_h
* >(user());
1395 assert(digest_user
!= NULL
);
1396 debugs(29, 9, "authenticateStart: '\"" << digest_user
->username() << "\":\"" << realm
<< "\"'");
1398 if (digestConfig
.authenticate
== NULL
) {
1399 handler(data
, NULL
);
1403 r
= cbdataAlloc(DigestAuthenticateStateData
);
1404 r
->handler
= handler
;
1405 r
->data
= cbdataReference(data
);
1406 r
->auth_user_request
= this;
1407 AUTHUSERREQUESTLOCK(r
->auth_user_request
, "r");
1408 if (digestConfig
.utf8
) {
1410 latin1_to_utf8(userstr
, sizeof(userstr
), digest_user
->username());
1411 snprintf(buf
, 8192, "\"%s\":\"%s\"\n", userstr
, realm
);
1413 snprintf(buf
, 8192, "\"%s\":\"%s\"\n", digest_user
->username(), realm
);
1416 helperSubmit(digestauthenticators
, buf
, authenticateDigestHandleReply
, r
);
1419 DigestUser::DigestUser (AuthConfig
*aConfig
) : AuthUser (aConfig
), HA1created (0)
1423 AuthDigestUserRequest::authUser() const
1425 return const_cast<AuthUser
*>(user());
1429 AuthDigestUserRequest::authUser(AuthUser
*aUser
)
1431 assert(!authUser());
1436 AuthDigestUserRequest::CredentialsState
1437 AuthDigestUserRequest::credentials() const
1439 return credentials_ok
;
1443 AuthDigestUserRequest::credentials(CredentialsState newCreds
)
1445 credentials_ok
= newCreds
;
1448 AuthDigestUserRequest::AuthDigestUserRequest() : nonceb64(NULL
) ,cnonce(NULL
) ,realm(NULL
),
1449 pszPass(NULL
) ,algorithm(NULL
) ,pszMethod(NULL
),
1450 qop(NULL
) ,uri(NULL
) ,response(NULL
),
1451 nonce(NULL
), _theUser (NULL
) ,
1452 credentials_ok (Unchecked
)
1455 /** delete the digest request structure. Does NOT delete related structures */
1456 AuthDigestUserRequest::~AuthDigestUserRequest()
1458 safe_free (nonceb64
);
1461 safe_free (pszPass
);
1462 safe_free (algorithm
);
1463 safe_free (pszMethod
);
1466 safe_free (response
);
1469 authDigestNonceUnlink(nonce
);
1473 digestScheme::createConfig()
1475 return &digestConfig
;