2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
10 #include "AccessLogEntry.h"
11 #include "auth/digest/Config.h"
12 #include "auth/digest/User.h"
13 #include "auth/digest/UserRequest.h"
14 #include "auth/State.h"
15 #include "format/Format.h"
17 #include "helper/Reply.h"
18 #include "HttpHeaderTools.h"
19 #include "HttpReply.h"
20 #include "HttpRequest.h"
22 #include "SquidTime.h"
24 Auth::Digest::UserRequest::UserRequest() :
36 memset(nc
, 0, sizeof(nc
));
37 memset(&flags
, 0, sizeof(flags
));
41 * Delete the digest request structure.
42 * Does NOT delete related AuthUser structures
44 Auth::Digest::UserRequest::~UserRequest()
46 assert(LockCount()==0);
59 authDigestNonceUnlink(nonce
);
63 Auth::Digest::UserRequest::authenticated() const
65 if (user() != NULL
&& user()->credentials() == Auth::Ok
)
72 Auth::Digest::UserRequest::credentialsStr()
77 /** log a digest user in
80 Auth::Digest::UserRequest::authenticate(HttpRequest
* request
, ConnStateData
*, Http::HdrType
)
86 /* if the check has corrupted the user, just return */
87 if (user() == NULL
|| user()->credentials() == Auth::Failed
) {
91 Auth::User::Pointer auth_user
= user();
93 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user
.getRaw());
94 assert(digest_user
!= NULL
);
96 Auth::Digest::UserRequest
*digest_request
= this;
98 /* do we have the HA1 */
99 if (!digest_user
->HA1created
) {
100 auth_user
->credentials(Auth::Pending
);
104 if (digest_request
->nonce
== NULL
) {
105 /* this isn't a nonce we issued */
106 auth_user
->credentials(Auth::Failed
);
110 DigestCalcHA1(digest_request
->algorithm
, NULL
, NULL
, NULL
,
111 authenticateDigestNonceNonceHex(digest_request
->nonce
),
112 digest_request
->cnonce
,
113 digest_user
->HA1
, SESSIONKEY
);
114 SBuf sTmp
= request
->method
.image();
115 DigestCalcResponse(SESSIONKEY
, authenticateDigestNonceNonceHex(digest_request
->nonce
),
116 digest_request
->nc
, digest_request
->cnonce
, digest_request
->qop
,
117 sTmp
.c_str(), digest_request
->uri
, HA2
, Response
);
119 debugs(29, 9, "\nResponse = '" << digest_request
->response
<< "'\nsquid is = '" << Response
<< "'");
121 if (strcasecmp(digest_request
->response
, Response
) != 0) {
122 if (!digest_request
->flags
.helper_queried
) {
123 /* Query the helper in case the password has changed */
124 digest_request
->flags
.helper_queried
= true;
125 auth_user
->credentials(Auth::Pending
);
129 if (static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->PostWorkaround
&& request
->method
!= Http::METHOD_GET
) {
130 /* Ugly workaround for certain very broken browsers using the
131 * wrong method to calculate the request-digest on POST request.
132 * This should be deleted once Digest authentication becomes more
133 * widespread and such broken browsers no longer are commonly
136 sTmp
= HttpRequestMethod(Http::METHOD_GET
).image();
137 DigestCalcResponse(SESSIONKEY
, authenticateDigestNonceNonceHex(digest_request
->nonce
),
138 digest_request
->nc
, digest_request
->cnonce
, digest_request
->qop
,
139 sTmp
.c_str(), digest_request
->uri
, HA2
, Response
);
141 if (strcasecmp(digest_request
->response
, Response
)) {
142 auth_user
->credentials(Auth::Failed
);
143 digest_request
->flags
.invalid_password
= true;
144 digest_request
->setDenyMessage("Incorrect password");
147 const char *useragent
= request
->header
.getStr(Http::HdrType::USER_AGENT
);
149 static Ip::Address last_broken_addr
;
150 static int seen_broken_client
= 0;
152 if (!seen_broken_client
) {
153 last_broken_addr
.setNoAddr();
154 seen_broken_client
= 1;
157 if (last_broken_addr
!= request
->client_addr
) {
158 debugs(29, DBG_IMPORTANT
, "Digest POST bug detected from " <<
159 request
->client_addr
<< " using '" <<
160 (useragent
? useragent
: "-") <<
161 "'. Please upgrade browser. See Bug #630 for details.");
163 last_broken_addr
= request
->client_addr
;
167 auth_user
->credentials(Auth::Failed
);
168 digest_request
->flags
.invalid_password
= true;
169 digest_request
->setDenyMessage("Incorrect password");
174 /* check for stale nonce */
175 /* check Auth::Pending to avoid loop */
177 if (!authDigestNonceIsValid(digest_request
->nonce
, digest_request
->nc
) && user()->credentials() != Auth::Pending
) {
178 debugs(29, 3, auth_user
->username() << "' validated OK but nonce stale: " << digest_request
->noncehex
);
179 /* Pending prevent banner and makes a ldap control */
180 auth_user
->credentials(Auth::Pending
);
181 nonce
->flags
.valid
= false;
182 authDigestNoncePurge(nonce
);
186 auth_user
->credentials(Auth::Ok
);
188 /* password was checked and did match */
189 debugs(29, 4, "user '" << auth_user
->username() << "' validated OK");
193 Auth::Digest::UserRequest::module_direction()
195 if (user()->auth_type
!= Auth::AUTH_DIGEST
)
196 return Auth::CRED_ERROR
;
198 switch (user()->credentials()) {
201 return Auth::CRED_VALID
;
203 case Auth::Handshake
:
205 /* send new challenge */
206 return Auth::CRED_CHALLENGE
;
208 case Auth::Unchecked
:
210 return Auth::CRED_LOOKUP
;
213 return Auth::CRED_ERROR
;
218 Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply
* rep
, int accel
)
222 /* don't add to authentication error pages */
223 if ((!accel
&& rep
->sline
.status() == Http::scProxyAuthenticationRequired
)
224 || (accel
&& rep
->sline
.status() == Http::scUnauthorized
))
227 type
= accel
? Http::HdrType::AUTHENTICATION_INFO
: Http::HdrType::PROXY_AUTHENTICATION_INFO
;
230 /* test for http/1.1 transfer chunked encoding */
235 if ((static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram
) && authDigestNonceLastRequest(nonce
)) {
236 flags
.authinfo_sent
= true;
237 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(user().getRaw());
241 digest_nonce_h
*nextnonce
= digest_user
->currentNonce();
242 if (!nextnonce
|| authDigestNonceLastRequest(nonce
)) {
243 nextnonce
= authenticateDigestNonceNew();
244 authDigestUserLinkNonce(digest_user
, nextnonce
);
246 debugs(29, 9, "Sending type:" << type
<< " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nextnonce
) << "\"");
247 httpHeaderPutStrf(&rep
->header
, type
, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nextnonce
));
253 Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply
* rep
, int accel
)
257 if (!auth_user_request
)
260 /* has the header already been send? */
261 if (flags
.authinfo_sent
)
264 /* don't add to authentication error pages */
265 if ((!accel
&& rep
->sline
.status() == Http::scProxyAuthenticationRequired
)
266 || (accel
&& rep
->sline
.status() == Http::scUnauthorized
))
269 type
= accel
? Http::HdrType::AUTHENTICATION_INFO
: Http::HdrType::PROXY_AUTHENTICATION_INFO
;
271 if ((static_cast<Auth::Digest::Config
*>(digestScheme::GetInstance()->getConfig())->authenticate
) && authDigestNonceLastRequest(nonce
)) {
272 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
273 nonce
= digest_user
->currentNonce();
275 nonce
= authenticateDigestNonceNew();
276 authDigestUserLinkNonce(digest_user
, nonce
);
278 debugs(29, 9, "Sending type:" << type
<< " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nonce
) << "\"");
279 httpTrailerPutStrf(&rep
->header
, type
, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nonce
));
284 /* send the initial data to a digest authenticator module */
286 Auth::Digest::UserRequest::startHelperLookup(HttpRequest
*request
, AccessLogEntry::Pointer
&al
, AUTHCB
* handler
, void *data
)
290 assert(user() != NULL
&& user()->auth_type
== Auth::AUTH_DIGEST
);
291 debugs(29, 9, HERE
<< "'\"" << user()->username() << "\":\"" << realm
<< "\"'");
293 if (static_cast<Auth::Digest::Config
*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram
== NULL
) {
294 debugs(29, DBG_CRITICAL
, "ERROR: No Digest authentication program configured.");
299 const char *keyExtras
= helperRequestKeyExtras(request
, al
);
301 snprintf(buf
, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm
, keyExtras
);
303 snprintf(buf
, 8192, "\"%s\":\"%s\"\n", user()->username(), realm
);
305 helperSubmit(digestauthenticators
, buf
, Auth::Digest::UserRequest::HandleReply
,
306 new Auth::StateData(this, handler
, data
));
310 Auth::Digest::UserRequest::HandleReply(void *data
, const Helper::Reply
&reply
)
312 Auth::StateData
*replyData
= static_cast<Auth::StateData
*>(data
);
313 debugs(29, 9, HERE
<< "reply=" << reply
);
315 assert(replyData
->auth_user_request
!= NULL
);
316 Auth::UserRequest::Pointer auth_user_request
= replyData
->auth_user_request
;
318 // add new helper kv-pair notes to the credentials object
319 // so that any transaction using those credentials can access them
320 static const NotePairs::Names appendables
= { SBuf("group"), SBuf("nonce"), SBuf("tag") };
321 auth_user_request
->user()->notes
.replaceOrAddOrAppend(&reply
.notes
, appendables
);
322 // remove any private credentials detail which got added.
323 auth_user_request
->user()->notes
.remove("ha1");
325 static bool oldHelperWarningDone
= false;
326 switch (reply
.result
) {
327 case Helper::Unknown
: {
328 // Squid 3.3 and older the digest helper only returns a HA1 hash (no "OK")
329 // the HA1 will be found in content() for these responses.
330 if (!oldHelperWarningDone
) {
331 debugs(29, DBG_IMPORTANT
, "WARNING: Digest auth helper returned old format HA1 response. It needs to be upgraded.");
332 oldHelperWarningDone
=true;
335 /* allow this because the digest_request pointer is purely local */
336 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
337 assert(digest_user
!= NULL
);
339 CvtBin(reply
.other().content(), digest_user
->HA1
);
340 digest_user
->HA1created
= 1;
345 /* allow this because the digest_request pointer is purely local */
346 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
347 assert(digest_user
!= NULL
);
349 if (const char *ha1Note
= reply
.notes
.findFirst("ha1")) {
350 CvtBin(ha1Note
, digest_user
->HA1
);
351 digest_user
->HA1created
= 1;
353 debugs(29, DBG_IMPORTANT
, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply
);
359 debugs(29, DBG_IMPORTANT
, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply
);
360 // fall through to next case. Handle this as an ERR response.
362 case Helper::TimedOut
:
363 case Helper::BrokenHelper
:
364 // TODO retry the broken lookup on another helper?
365 // fall through to next case for now. Handle this as an ERR response silently.
366 case Helper::Error
: {
367 /* allow this because the digest_request pointer is purely local */
368 Auth::Digest::UserRequest
*digest_request
= dynamic_cast<Auth::Digest::UserRequest
*>(auth_user_request
.getRaw());
369 assert(digest_request
);
371 digest_request
->user()->credentials(Auth::Failed
);
372 digest_request
->flags
.invalid_password
= true;
375 if (reply
.notes
.find(msgNote
, "message")) {
376 digest_request
->setDenyMessage(msgNote
.c_str());
377 } else if (reply
.other().hasContent()) {
378 // old helpers did send ERR result but a bare message string instead of message= key name.
379 digest_request
->setDenyMessage(reply
.other().content());
380 if (!oldHelperWarningDone
) {
381 debugs(29, DBG_IMPORTANT
, "WARNING: Digest auth helper returned old format ERR response. It needs to be upgraded.");
382 oldHelperWarningDone
=true;
390 if (cbdataReferenceValidDone(replyData
->data
, &cbdata
))
391 replyData
->handler(cbdata
);