2 #include "AccessLogEntry.h"
3 #include "auth/digest/auth_digest.h"
4 #include "auth/digest/User.h"
5 #include "auth/digest/UserRequest.h"
6 #include "auth/State.h"
8 #include "format/Format.h"
9 #include "HttpHeaderTools.h"
10 #include "HttpReply.h"
11 #include "HttpRequest.h"
13 #include "SquidTime.h"
15 Auth::Digest::UserRequest::UserRequest() :
27 memset(nc
, 0, sizeof(nc
));
28 memset(&flags
, 0, sizeof(flags
));
32 * Delete the digest request structure.
33 * Does NOT delete related AuthUser structures
35 Auth::Digest::UserRequest::~UserRequest()
37 assert(LockCount()==0);
50 authDigestNonceUnlink(nonce
);
54 Auth::Digest::UserRequest::authenticated() const
56 if (user() != NULL
&& user()->credentials() == Auth::Ok
)
63 Auth::Digest::UserRequest::credentialsStr()
68 /** log a digest user in
71 Auth::Digest::UserRequest::authenticate(HttpRequest
* request
, ConnStateData
* conn
, http_hdr_type type
)
77 /* if the check has corrupted the user, just return */
78 if (user() == NULL
|| user()->credentials() == Auth::Failed
) {
82 Auth::User::Pointer auth_user
= user();
84 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user
.getRaw());
85 assert(digest_user
!= NULL
);
87 Auth::Digest::UserRequest
*digest_request
= this;
89 /* do we have the HA1 */
90 if (!digest_user
->HA1created
) {
91 auth_user
->credentials(Auth::Pending
);
95 if (digest_request
->nonce
== NULL
) {
96 /* this isn't a nonce we issued */
97 auth_user
->credentials(Auth::Failed
);
101 DigestCalcHA1(digest_request
->algorithm
, NULL
, NULL
, NULL
,
102 authenticateDigestNonceNonceb64(digest_request
->nonce
),
103 digest_request
->cnonce
,
104 digest_user
->HA1
, SESSIONKEY
);
105 SBuf sTmp
= request
->method
.image();
106 DigestCalcResponse(SESSIONKEY
, authenticateDigestNonceNonceb64(digest_request
->nonce
),
107 digest_request
->nc
, digest_request
->cnonce
, digest_request
->qop
,
108 sTmp
.c_str(), digest_request
->uri
, HA2
, Response
);
110 debugs(29, 9, "\nResponse = '" << digest_request
->response
<< "'\nsquid is = '" << Response
<< "'");
112 if (strcasecmp(digest_request
->response
, Response
) != 0) {
113 if (!digest_request
->flags
.helper_queried
) {
114 /* Query the helper in case the password has changed */
115 digest_request
->flags
.helper_queried
= true;
116 auth_user
->credentials(Auth::Pending
);
120 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->PostWorkaround
&& request
->method
!= Http::METHOD_GET
) {
121 /* Ugly workaround for certain very broken browsers using the
122 * wrong method to calculate the request-digest on POST request.
123 * This should be deleted once Digest authentication becomes more
124 * widespread and such broken browsers no longer are commonly
127 sTmp
= HttpRequestMethod(Http::METHOD_GET
).image();
128 DigestCalcResponse(SESSIONKEY
, authenticateDigestNonceNonceb64(digest_request
->nonce
),
129 digest_request
->nc
, digest_request
->cnonce
, digest_request
->qop
,
130 sTmp
.c_str(), digest_request
->uri
, HA2
, Response
);
132 if (strcasecmp(digest_request
->response
, Response
)) {
133 auth_user
->credentials(Auth::Failed
);
134 digest_request
->flags
.invalid_password
= true;
135 digest_request
->setDenyMessage("Incorrect password");
138 const char *useragent
= request
->header
.getStr(HDR_USER_AGENT
);
140 static Ip::Address last_broken_addr
;
141 static int seen_broken_client
= 0;
143 if (!seen_broken_client
) {
144 last_broken_addr
.setNoAddr();
145 seen_broken_client
= 1;
148 if (last_broken_addr
!= request
->client_addr
) {
149 debugs(29, DBG_IMPORTANT
, "Digest POST bug detected from " <<
150 request
->client_addr
<< " using '" <<
151 (useragent
? useragent
: "-") <<
152 "'. Please upgrade browser. See Bug #630 for details.");
154 last_broken_addr
= request
->client_addr
;
158 auth_user
->credentials(Auth::Failed
);
159 digest_request
->flags
.invalid_password
= true;
160 digest_request
->setDenyMessage("Incorrect password");
165 /* check for stale nonce */
166 if (!authDigestNonceIsValid(digest_request
->nonce
, digest_request
->nc
)) {
167 debugs(29, 3, "user '" << auth_user
->username() << "' validated OK but nonce stale");
168 auth_user
->credentials(Auth::Handshake
);
169 digest_request
->setDenyMessage("Stale nonce");
173 auth_user
->credentials(Auth::Ok
);
175 /* password was checked and did match */
176 debugs(29, 4, HERE
<< "user '" << auth_user
->username() << "' validated OK");
178 /* auth_user is now linked, we reset these values
179 * after external auth occurs anyway */
180 auth_user
->expiretime
= current_time
.tv_sec
;
185 Auth::Digest::UserRequest::module_direction()
187 if (user()->auth_type
!= Auth::AUTH_DIGEST
)
188 return Auth::CRED_ERROR
;
190 switch (user()->credentials()) {
193 return Auth::CRED_VALID
;
195 case Auth::Handshake
:
197 /* send new challenge */
198 return Auth::CRED_CHALLENGE
;
200 case Auth::Unchecked
:
202 return Auth::CRED_LOOKUP
;
205 return Auth::CRED_ERROR
;
210 Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply
* rep
, int accel
)
214 /* don't add to authentication error pages */
215 if ((!accel
&& rep
->sline
.status() == Http::scProxyAuthenticationRequired
)
216 || (accel
&& rep
->sline
.status() == Http::scUnauthorized
))
219 type
= accel
? HDR_AUTHENTICATION_INFO
: HDR_PROXY_AUTHENTICATION_INFO
;
222 /* test for http/1.1 transfer chunked encoding */
227 if ((static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->authenticateProgram
) && authDigestNonceLastRequest(nonce
)) {
228 flags
.authinfo_sent
= true;
229 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(user().getRaw());
230 digest_nonce_h
*nextnonce
= digest_user
->currentNonce();
231 if (!nextnonce
|| authDigestNonceLastRequest(nonce
)) {
232 nextnonce
= authenticateDigestNonceNew();
233 authDigestUserLinkNonce(digest_user
, nextnonce
);
235 debugs(29, 9, "Sending type:" << type
<< " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nextnonce
) << "\"");
236 httpHeaderPutStrf(&rep
->header
, type
, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nextnonce
));
242 Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply
* rep
, int accel
)
246 if (!auth_user_request
)
249 /* has the header already been send? */
250 if (flags
.authinfo_sent
)
253 /* don't add to authentication error pages */
254 if ((!accel
&& rep
->sline
.status() == Http::scProxyAuthenticationRequired
)
255 || (accel
&& rep
->sline
.status() == Http::scUnauthorized
))
258 type
= accel
? HDR_AUTHENTICATION_INFO
: HDR_PROXY_AUTHENTICATION_INFO
;
260 if ((static_cast<Auth::Digest::Config
*>(digestScheme::GetInstance()->getConfig())->authenticate
) && authDigestNonceLastRequest(nonce
)) {
261 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
262 nonce
= digest_user
->currentNonce();
264 nonce
= authenticateDigestNonceNew();
265 authDigestUserLinkNonce(digest_user
, nonce
);
267 debugs(29, 9, "Sending type:" << type
<< " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce
) << "\"");
268 httpTrailerPutStrf(&rep
->header
, type
, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce
));
273 /* send the initial data to a digest authenticator module */
275 Auth::Digest::UserRequest::module_start(HttpRequest
*request
, AccessLogEntry::Pointer
&al
, AUTHCB
* handler
, void *data
)
279 assert(user() != NULL
&& user()->auth_type
== Auth::AUTH_DIGEST
);
280 debugs(29, 9, HERE
<< "'\"" << user()->username() << "\":\"" << realm
<< "\"'");
282 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->authenticateProgram
== NULL
) {
283 debugs(29, DBG_CRITICAL
, "ERROR: No Digest authentication program configured.");
288 const char *keyExtras
= helperRequestKeyExtras(request
, al
);
289 if (static_cast<Auth::Digest::Config
*>(Auth::Config::Find("digest"))->utf8
) {
291 latin1_to_utf8(userstr
, sizeof(userstr
), user()->username());
293 snprintf(buf
, 8192, "\"%s\":\"%s\" %s\n", userstr
, realm
, keyExtras
);
295 snprintf(buf
, 8192, "\"%s\":\"%s\"\n", userstr
, realm
);
298 snprintf(buf
, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm
, keyExtras
);
300 snprintf(buf
, 8192, "\"%s\":\"%s\"\n", user()->username(), realm
);
303 helperSubmit(digestauthenticators
, buf
, Auth::Digest::UserRequest::HandleReply
,
304 new Auth::StateData(this, handler
, data
));
308 Auth::Digest::UserRequest::HandleReply(void *data
, const HelperReply
&reply
)
310 Auth::StateData
*replyData
= static_cast<Auth::StateData
*>(data
);
311 debugs(29, 9, HERE
<< "reply=" << reply
);
313 assert(replyData
->auth_user_request
!= NULL
);
314 Auth::UserRequest::Pointer auth_user_request
= replyData
->auth_user_request
;
316 // add new helper kv-pair notes to the credentials object
317 // so that any transaction using those credentials can access them
318 auth_user_request
->user()->notes
.appendNewOnly(&reply
.notes
);
320 static bool oldHelperWarningDone
= false;
321 switch (reply
.result
) {
322 case HelperReply::Unknown
: {
323 // Squid 3.3 and older the digest helper only returns a HA1 hash (no "OK")
324 // the HA1 will be found in content() for these responses.
325 if (!oldHelperWarningDone
) {
326 debugs(29, DBG_IMPORTANT
, "WARNING: Digest auth helper returned old format HA1 response. It needs to be upgraded.");
327 oldHelperWarningDone
=true;
330 /* allow this because the digest_request pointer is purely local */
331 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
332 assert(digest_user
!= NULL
);
334 CvtBin(reply
.other().content(), digest_user
->HA1
);
335 digest_user
->HA1created
= 1;
339 case HelperReply::Okay
: {
340 /* allow this because the digest_request pointer is purely local */
341 Auth::Digest::User
*digest_user
= dynamic_cast<Auth::Digest::User
*>(auth_user_request
->user().getRaw());
342 assert(digest_user
!= NULL
);
344 const char *ha1Note
= reply
.notes
.findFirst("ha1");
345 if (ha1Note
!= NULL
) {
346 CvtBin(ha1Note
, digest_user
->HA1
);
347 digest_user
->HA1created
= 1;
349 debugs(29, DBG_IMPORTANT
, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply
);
354 case HelperReply::TT
:
355 debugs(29, DBG_IMPORTANT
, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply
);
356 // fall through to next case. Handle this as an ERR response.
358 case HelperReply::BrokenHelper
:
359 // TODO retry the broken lookup on another helper?
360 // fall through to next case for now. Handle this as an ERR response silently.
362 case HelperReply::Error
: {
363 /* allow this because the digest_request pointer is purely local */
364 Auth::Digest::UserRequest
*digest_request
= dynamic_cast<Auth::Digest::UserRequest
*>(auth_user_request
.getRaw());
365 assert(digest_request
);
367 digest_request
->user()->credentials(Auth::Failed
);
368 digest_request
->flags
.invalid_password
= true;
370 const char *msgNote
= reply
.notes
.find("message");
371 if (msgNote
!= NULL
) {
372 digest_request
->setDenyMessage(msgNote
);
373 } else if (reply
.other().hasContent()) {
374 // old helpers did send ERR result but a bare message string instead of message= key name.
375 digest_request
->setDenyMessage(reply
.other().content());
376 if (!oldHelperWarningDone
) {
377 debugs(29, DBG_IMPORTANT
, "WARNING: Digest auth helper returned old format ERR response. It needs to be upgraded.");
378 oldHelperWarningDone
=true;
386 if (cbdataReferenceValidDone(replyData
->data
, &cbdata
))
387 replyData
->handler(cbdata
);