2 #include "auth/digest/auth_digest.h"
3 #include "auth/digest/User.h"
4 #include "auth/digest/UserRequest.h"
5 #include "auth/State.h"
7 #include "HttpHeaderTools.h"
9 #include "HttpRequest.h"
10 #include "SquidTime.h"
12 Auth::Digest::UserRequest::UserRequest() :
26 * Delete the digest request structure.
27 * Does NOT delete related AuthUser structures
29 Auth::Digest::UserRequest::~UserRequest()
31 assert(LockCount()==0);
44 authDigestNonceUnlink(nonce
);
48 Auth::Digest::UserRequest::authenticated() const
50 if (user() != NULL
&& user()->credentials() == Auth::Ok
)
56 /** log a digest user in
59 Auth::Digest::UserRequest::authenticate(HttpRequest
* request
, ConnStateData
* conn
, http_hdr_type type
)
65 /* if the check has corrupted the user, just return */
66 if (user() == NULL
|| user()->credentials() == Auth::Failed
) {
70 Auth::User::Pointer auth_user
= user();
72 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user
.getRaw());
73 assert(digest_user
!= NULL
);
75 Auth::Digest::UserRequest
*digest_request
= this;
77 /* do we have the HA1 */
78 if (!digest_user
->HA1created
) {
79 auth_user
->credentials(Auth::Pending
);
83 if (digest_request
->nonce
== NULL
) {
84 /* this isn't a nonce we issued */
85 auth_user
->credentials(Auth::Failed
);
89 DigestCalcHA1(digest_request
->algorithm
, NULL
, NULL
, NULL
,
90 authenticateDigestNonceNonceb64(digest_request
->nonce
),
91 digest_request
->cnonce
,
92 digest_user
->HA1
, SESSIONKEY
);
93 DigestCalcResponse(SESSIONKEY
, authenticateDigestNonceNonceb64(digest_request
->nonce
),
94 digest_request
->nc
, digest_request
->cnonce
, digest_request
->qop
,
95 RequestMethodStr(request
->method
), digest_request
->uri
, HA2
, Response
);
97 debugs(29, 9, "\nResponse = '" << digest_request
->response
<< "'\nsquid is = '" << Response
<< "'");
99 if (strcasecmp(digest_request
->response
, Response
) != 0) {
100 if (!digest_request
->flags
.helper_queried
) {
101 /* Query the helper in case the password has changed */
102 digest_request
->flags
.helper_queried
= 1;
103 auth_user
->credentials(Auth::Pending
);
107 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->PostWorkaround
&& request
->method
!= Http::METHOD_GET
) {
108 /* Ugly workaround for certain very broken browsers using the
109 * wrong method to calculate the request-digest on POST request.
110 * This should be deleted once Digest authentication becomes more
111 * widespread and such broken browsers no longer are commonly
114 DigestCalcResponse(SESSIONKEY
, authenticateDigestNonceNonceb64(digest_request
->nonce
),
115 digest_request
->nc
, digest_request
->cnonce
, digest_request
->qop
,
116 RequestMethodStr(Http::METHOD_GET
), digest_request
->uri
, HA2
, Response
);
118 if (strcasecmp(digest_request
->response
, Response
)) {
119 auth_user
->credentials(Auth::Failed
);
120 digest_request
->flags
.invalid_password
= 1;
121 digest_request
->setDenyMessage("Incorrect password");
124 const char *useragent
= request
->header
.getStr(HDR_USER_AGENT
);
126 static Ip::Address last_broken_addr
;
127 static int seen_broken_client
= 0;
129 if (!seen_broken_client
) {
130 last_broken_addr
.SetNoAddr();
131 seen_broken_client
= 1;
134 if (last_broken_addr
!= request
->client_addr
) {
135 debugs(29, DBG_IMPORTANT
, "Digest POST bug detected from " <<
136 request
->client_addr
<< " using '" <<
137 (useragent
? useragent
: "-") <<
138 "'. Please upgrade browser. See Bug #630 for details.");
140 last_broken_addr
= request
->client_addr
;
144 auth_user
->credentials(Auth::Failed
);
145 digest_request
->flags
.invalid_password
= 1;
146 digest_request
->setDenyMessage("Incorrect password");
150 /* check for stale nonce */
151 if (!authDigestNonceIsValid(digest_request
->nonce
, digest_request
->nc
)) {
152 debugs(29, 3, HERE
<< "user '" << auth_user
->username() << "' validated OK but nonce stale");
153 auth_user
->credentials(Auth::Failed
);
154 digest_request
->setDenyMessage("Stale nonce");
159 auth_user
->credentials(Auth::Ok
);
161 /* password was checked and did match */
162 debugs(29, 4, HERE
<< "user '" << auth_user
->username() << "' validated OK");
164 /* auth_user is now linked, we reset these values
165 * after external auth occurs anyway */
166 auth_user
->expiretime
= current_time
.tv_sec
;
171 Auth::Digest::UserRequest::module_direction()
173 if (user()->auth_type
!= Auth::AUTH_DIGEST
)
174 return Auth::CRED_ERROR
;
176 switch (user()->credentials()) {
179 return Auth::CRED_VALID
;
182 /* send new challenge */
183 return Auth::CRED_CHALLENGE
;
185 case Auth::Unchecked
:
187 return Auth::CRED_LOOKUP
;
190 return Auth::CRED_ERROR
;
195 Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply
* rep
, int accel
)
199 /* don't add to authentication error pages */
201 if ((!accel
&& rep
->sline
.status
== HTTP_PROXY_AUTHENTICATION_REQUIRED
)
202 || (accel
&& rep
->sline
.status
== HTTP_UNAUTHORIZED
))
205 type
= accel
? HDR_AUTHENTICATION_INFO
: HDR_PROXY_AUTHENTICATION_INFO
;
208 /* test for http/1.1 transfer chunked encoding */
213 if ((static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->authenticateProgram
) && authDigestNonceLastRequest(nonce
)) {
214 flags
.authinfo_sent
= 1;
215 debugs(29, 9, HERE
<< "Sending type:" << type
<< " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce
) << "\"");
216 httpHeaderPutStrf(&rep
->header
, type
, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce
));
222 Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply
* rep
, int accel
)
226 if (!auth_user_request
)
229 /* has the header already been send? */
230 if (flags
.authinfo_sent
)
233 /* don't add to authentication error pages */
234 if ((!accel
&& rep
->sline
.status
== HTTP_PROXY_AUTHENTICATION_REQUIRED
)
235 || (accel
&& rep
->sline
.status
== HTTP_UNAUTHORIZED
))
238 type
= accel
? HDR_AUTHENTICATION_INFO
: HDR_PROXY_AUTHENTICATION_INFO
;
240 if ((static_cast<Auth::Digest::Config
*>(digestScheme::GetInstance()->getConfig())->authenticate
) && authDigestNonceLastRequest(nonce
)) {
241 debugs(29, 9, HERE
<< "Sending type:" << type
<< " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce
) << "\"");
242 httpTrailerPutStrf(&rep
->header
, type
, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce
));
247 /* send the initial data to a digest authenticator module */
249 Auth::Digest::UserRequest::module_start(AUTHCB
* handler
, void *data
)
253 assert(user() != NULL
&& user()->auth_type
== Auth::AUTH_DIGEST
);
254 debugs(29, 9, HERE
<< "'\"" << user()->username() << "\":\"" << realm
<< "\"'");
256 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->authenticateProgram
== NULL
) {
257 debugs(29, DBG_CRITICAL
, "ERROR: No Digest authentication program configured.");
262 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->utf8
) {
264 latin1_to_utf8(userstr
, sizeof(userstr
), user()->username());
265 snprintf(buf
, 8192, "\"%s\":\"%s\"\n", userstr
, realm
);
267 snprintf(buf
, 8192, "\"%s\":\"%s\"\n", user()->username(), realm
);
270 helperSubmit(digestauthenticators
, buf
, Auth::Digest::UserRequest::HandleReply
,
271 new Auth::StateData(this, handler
, data
));
275 Auth::Digest::UserRequest::HandleReply(void *data
, const HelperReply
&reply
)
277 Auth::StateData
*replyData
= static_cast<Auth::StateData
*>(data
);
278 debugs(29, 9, HERE
<< "reply=" << reply
);
280 assert(replyData
->auth_user_request
!= NULL
);
281 Auth::UserRequest::Pointer auth_user_request
= replyData
->auth_user_request
;
283 static bool oldHelperWarningDone
= false;
284 switch (reply
.result
) {
285 case HelperReply::Unknown
: {
286 // Squid 3.3 and older the digest helper only returns a HA1 hash (no "OK")
287 // the HA1 will be found in content() for these responses.
288 if (!oldHelperWarningDone
) {
289 debugs(29, DBG_IMPORTANT
, "WARNING: Digest auth helper returned old format HA1 response. It needs to be upgraded.");
290 oldHelperWarningDone
=true;
293 /* allow this because the digest_request pointer is purely local */
294 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
295 assert(digest_user
!= NULL
);
297 CvtBin(reply
.other().content(), digest_user
->HA1
);
298 digest_user
->HA1created
= 1;
302 case HelperReply::Okay
: {
303 /* allow this because the digest_request pointer is purely local */
304 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
305 assert(digest_user
!= NULL
);
307 Note::Pointer ha1Note
= reply
.responseKeys
.findByName("ha1");
308 if (ha1Note
!= NULL
) {
309 CvtBin(ha1Note
->values
[0]->value
.termedBuf(), digest_user
->HA1
);
310 digest_user
->HA1created
= 1;
312 debugs(29, DBG_IMPORTANT
, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply
);
317 case HelperReply::TT
:
318 debugs(29, DBG_IMPORTANT
, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply
);
319 // fall through to next case. Handle this as an ERR response.
321 case HelperReply::BrokenHelper
:
322 // TODO retry the broken lookup on another helper?
323 // fall through to next case for now. Handle this as an ERR response silently.
325 case HelperReply::Error
: {
326 /* allow this because the digest_request pointer is purely local */
327 Auth::Digest::UserRequest
*digest_request
= dynamic_cast<Auth::Digest::UserRequest
*>(auth_user_request
.getRaw());
328 assert(digest_request
);
330 digest_request
->user()->credentials(Auth::Failed
);
331 digest_request
->flags
.invalid_password
= 1;
333 Note::Pointer msgNote
= reply
.responseKeys
.findByName("message");
334 if (msgNote
!= NULL
) {
335 digest_request
->setDenyMessage(msgNote
->values
[0]->value
.termedBuf());
336 } else if (reply
.other().hasContent()) {
337 // old helpers did send ERR result but a bare message string instead of message= key name.
338 digest_request
->setDenyMessage(reply
.other().content());
339 if (!oldHelperWarningDone
) {
340 debugs(29, DBG_IMPORTANT
, "WARNING: Digest auth helper returned old format ERR response. It needs to be upgraded.");
341 oldHelperWarningDone
=true;
349 if (cbdataReferenceValidDone(replyData
->data
, &cbdata
))
350 replyData
->handler(cbdata
);