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