]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/digest/UserRequest.cc
Merge from trunk rev.13584
[thirdparty/squid.git] / src / auth / digest / UserRequest.cc
1 /*
2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9 #include "squid.h"
10 #include "AccessLogEntry.h"
11 #include "auth/digest/auth_digest.h"
12 #include "auth/digest/User.h"
13 #include "auth/digest/UserRequest.h"
14 #include "auth/State.h"
15 #include "charset.h"
16 #include "format/Format.h"
17 #include "HttpHeaderTools.h"
18 #include "HttpReply.h"
19 #include "HttpRequest.h"
20 #include "MemBuf.h"
21 #include "SquidTime.h"
22
23 Auth::Digest::UserRequest::UserRequest() :
24 nonceb64(NULL),
25 cnonce(NULL),
26 realm(NULL),
27 pszPass(NULL),
28 algorithm(NULL),
29 pszMethod(NULL),
30 qop(NULL),
31 uri(NULL),
32 response(NULL),
33 nonce(NULL)
34 {
35 memset(nc, 0, sizeof(nc));
36 memset(&flags, 0, sizeof(flags));
37 }
38
39 /**
40 * Delete the digest request structure.
41 * Does NOT delete related AuthUser structures
42 */
43 Auth::Digest::UserRequest::~UserRequest()
44 {
45 assert(LockCount()==0);
46
47 safe_free(nonceb64);
48 safe_free(cnonce);
49 safe_free(realm);
50 safe_free(pszPass);
51 safe_free(algorithm);
52 safe_free(pszMethod);
53 safe_free(qop);
54 safe_free(uri);
55 safe_free(response);
56
57 if (nonce)
58 authDigestNonceUnlink(nonce);
59 }
60
61 int
62 Auth::Digest::UserRequest::authenticated() const
63 {
64 if (user() != NULL && user()->credentials() == Auth::Ok)
65 return 1;
66
67 return 0;
68 }
69
70 const char *
71 Auth::Digest::UserRequest::credentialsStr()
72 {
73 return realm;
74 }
75
76 /** log a digest user in
77 */
78 void
79 Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type)
80 {
81 HASHHEX SESSIONKEY;
82 HASHHEX HA2 = "";
83 HASHHEX Response;
84
85 /* if the check has corrupted the user, just return */
86 if (user() == NULL || user()->credentials() == Auth::Failed) {
87 return;
88 }
89
90 Auth::User::Pointer auth_user = user();
91
92 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User*>(auth_user.getRaw());
93 assert(digest_user != NULL);
94
95 Auth::Digest::UserRequest *digest_request = this;
96
97 /* do we have the HA1 */
98 if (!digest_user->HA1created) {
99 auth_user->credentials(Auth::Pending);
100 return;
101 }
102
103 if (digest_request->nonce == NULL) {
104 /* this isn't a nonce we issued */
105 auth_user->credentials(Auth::Failed);
106 return;
107 }
108
109 DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL,
110 authenticateDigestNonceNonceb64(digest_request->nonce),
111 digest_request->cnonce,
112 digest_user->HA1, SESSIONKEY);
113 SBuf sTmp = request->method.image();
114 DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
115 digest_request->nc, digest_request->cnonce, digest_request->qop,
116 sTmp.c_str(), digest_request->uri, HA2, Response);
117
118 debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'");
119
120 if (strcasecmp(digest_request->response, Response) != 0) {
121 if (!digest_request->flags.helper_queried) {
122 /* Query the helper in case the password has changed */
123 digest_request->flags.helper_queried = true;
124 auth_user->credentials(Auth::Pending);
125 return;
126 }
127
128 if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->PostWorkaround && request->method != Http::METHOD_GET) {
129 /* Ugly workaround for certain very broken browsers using the
130 * wrong method to calculate the request-digest on POST request.
131 * This should be deleted once Digest authentication becomes more
132 * widespread and such broken browsers no longer are commonly
133 * used.
134 */
135 sTmp = HttpRequestMethod(Http::METHOD_GET).image();
136 DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
137 digest_request->nc, digest_request->cnonce, digest_request->qop,
138 sTmp.c_str(), digest_request->uri, HA2, Response);
139
140 if (strcasecmp(digest_request->response, Response)) {
141 auth_user->credentials(Auth::Failed);
142 digest_request->flags.invalid_password = true;
143 digest_request->setDenyMessage("Incorrect password");
144 return;
145 } else {
146 const char *useragent = request->header.getStr(HDR_USER_AGENT);
147
148 static Ip::Address last_broken_addr;
149 static int seen_broken_client = 0;
150
151 if (!seen_broken_client) {
152 last_broken_addr.setNoAddr();
153 seen_broken_client = 1;
154 }
155
156 if (last_broken_addr != request->client_addr) {
157 debugs(29, DBG_IMPORTANT, "Digest POST bug detected from " <<
158 request->client_addr << " using '" <<
159 (useragent ? useragent : "-") <<
160 "'. Please upgrade browser. See Bug #630 for details.");
161
162 last_broken_addr = request->client_addr;
163 }
164 }
165 } else {
166 auth_user->credentials(Auth::Failed);
167 digest_request->flags.invalid_password = true;
168 digest_request->setDenyMessage("Incorrect password");
169 return;
170 }
171 }
172
173 /* check for stale nonce */
174 if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc)) {
175 debugs(29, 3, "user '" << auth_user->username() << "' validated OK but nonce stale");
176 auth_user->credentials(Auth::Handshake);
177 digest_request->setDenyMessage("Stale nonce");
178 return;
179 }
180
181 auth_user->credentials(Auth::Ok);
182
183 /* password was checked and did match */
184 debugs(29, 4, HERE << "user '" << auth_user->username() << "' validated OK");
185
186 /* auth_user is now linked, we reset these values
187 * after external auth occurs anyway */
188 auth_user->expiretime = current_time.tv_sec;
189 return;
190 }
191
192 Auth::Direction
193 Auth::Digest::UserRequest::module_direction()
194 {
195 if (user()->auth_type != Auth::AUTH_DIGEST)
196 return Auth::CRED_ERROR;
197
198 switch (user()->credentials()) {
199
200 case Auth::Ok:
201 return Auth::CRED_VALID;
202
203 case Auth::Handshake:
204 case Auth::Failed:
205 /* send new challenge */
206 return Auth::CRED_CHALLENGE;
207
208 case Auth::Unchecked:
209 case Auth::Pending:
210 return Auth::CRED_LOOKUP;
211
212 default:
213 return Auth::CRED_ERROR;
214 }
215 }
216
217 void
218 Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply * rep, int accel)
219 {
220 http_hdr_type type;
221
222 /* don't add to authentication error pages */
223 if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
224 || (accel && rep->sline.status() == Http::scUnauthorized))
225 return;
226
227 type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
228
229 #if WAITING_FOR_TE
230 /* test for http/1.1 transfer chunked encoding */
231 if (chunkedtest)
232 return;
233 #endif
234
235 if ((static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) {
236 flags.authinfo_sent = true;
237 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(user().getRaw());
238 digest_nonce_h *nextnonce = digest_user->currentNonce();
239 if (!nextnonce || authDigestNonceLastRequest(nonce)) {
240 nextnonce = authenticateDigestNonceNew();
241 authDigestUserLinkNonce(digest_user, nextnonce);
242 }
243 debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nextnonce) << "\"");
244 httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nextnonce));
245 }
246 }
247
248 #if WAITING_FOR_TE
249 void
250 Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply * rep, int accel)
251 {
252 int type;
253
254 if (!auth_user_request)
255 return;
256
257 /* has the header already been send? */
258 if (flags.authinfo_sent)
259 return;
260
261 /* don't add to authentication error pages */
262 if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
263 || (accel && rep->sline.status() == Http::scUnauthorized))
264 return;
265
266 type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
267
268 if ((static_cast<Auth::Digest::Config*>(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) {
269 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
270 nonce = digest_user->currentNonce();
271 if (!nonce) {
272 nonce = authenticateDigestNonceNew();
273 authDigestUserLinkNonce(digest_user, nonce);
274 }
275 debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
276 httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
277 }
278 }
279 #endif
280
281 /* send the initial data to a digest authenticator module */
282 void
283 Auth::Digest::UserRequest::startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
284 {
285 char buf[8192];
286
287 assert(user() != NULL && user()->auth_type == Auth::AUTH_DIGEST);
288 debugs(29, 9, HERE << "'\"" << user()->username() << "\":\"" << realm << "\"'");
289
290 if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->authenticateProgram == NULL) {
291 debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured.");
292 handler(data);
293 return;
294 }
295
296 const char *keyExtras = helperRequestKeyExtras(request, al);
297 if (static_cast<Auth::Digest::Config*>(Auth::Config::Find("digest"))->utf8) {
298 char userstr[1024];
299 latin1_to_utf8(userstr, sizeof(userstr), user()->username());
300 if (keyExtras)
301 snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", userstr, realm, keyExtras);
302 else
303 snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm);
304 } else {
305 if (keyExtras)
306 snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras);
307 else
308 snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm);
309 }
310
311 helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply,
312 new Auth::StateData(this, handler, data));
313 }
314
315 void
316 Auth::Digest::UserRequest::HandleReply(void *data, const HelperReply &reply)
317 {
318 Auth::StateData *replyData = static_cast<Auth::StateData *>(data);
319 debugs(29, 9, HERE << "reply=" << reply);
320
321 assert(replyData->auth_user_request != NULL);
322 Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request;
323
324 // add new helper kv-pair notes to the credentials object
325 // so that any transaction using those credentials can access them
326 auth_user_request->user()->notes.appendNewOnly(&reply.notes);
327
328 static bool oldHelperWarningDone = false;
329 switch (reply.result) {
330 case HelperReply::Unknown: {
331 // Squid 3.3 and older the digest helper only returns a HA1 hash (no "OK")
332 // the HA1 will be found in content() for these responses.
333 if (!oldHelperWarningDone) {
334 debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format HA1 response. It needs to be upgraded.");
335 oldHelperWarningDone=true;
336 }
337
338 /* allow this because the digest_request pointer is purely local */
339 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
340 assert(digest_user != NULL);
341
342 CvtBin(reply.other().content(), digest_user->HA1);
343 digest_user->HA1created = 1;
344 }
345 break;
346
347 case HelperReply::Okay: {
348 /* allow this because the digest_request pointer is purely local */
349 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
350 assert(digest_user != NULL);
351
352 const char *ha1Note = reply.notes.findFirst("ha1");
353 if (ha1Note != NULL) {
354 CvtBin(ha1Note, digest_user->HA1);
355 digest_user->HA1created = 1;
356 } else {
357 debugs(29, DBG_IMPORTANT, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply);
358 }
359 }
360 break;
361
362 case HelperReply::TT:
363 debugs(29, DBG_IMPORTANT, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply);
364 // fall through to next case. Handle this as an ERR response.
365
366 case HelperReply::BrokenHelper:
367 // TODO retry the broken lookup on another helper?
368 // fall through to next case for now. Handle this as an ERR response silently.
369
370 case HelperReply::Error: {
371 /* allow this because the digest_request pointer is purely local */
372 Auth::Digest::UserRequest *digest_request = dynamic_cast<Auth::Digest::UserRequest *>(auth_user_request.getRaw());
373 assert(digest_request);
374
375 digest_request->user()->credentials(Auth::Failed);
376 digest_request->flags.invalid_password = true;
377
378 const char *msgNote = reply.notes.find("message");
379 if (msgNote != NULL) {
380 digest_request->setDenyMessage(msgNote);
381 } else if (reply.other().hasContent()) {
382 // old helpers did send ERR result but a bare message string instead of message= key name.
383 digest_request->setDenyMessage(reply.other().content());
384 if (!oldHelperWarningDone) {
385 debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format ERR response. It needs to be upgraded.");
386 oldHelperWarningDone=true;
387 }
388 }
389 }
390 break;
391 }
392
393 void *cbdata = NULL;
394 if (cbdataReferenceValidDone(replyData->data, &cbdata))
395 replyData->handler(cbdata);
396
397 delete replyData;
398 }