]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/digest/UserRequest.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / auth / digest / UserRequest.cc
1 /*
2 * Copyright (C) 1996-2020 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 "format/Format.h"
16 #include "helper.h"
17 #include "helper/Reply.h"
18 #include "HttpHeaderTools.h"
19 #include "HttpReply.h"
20 #include "HttpRequest.h"
21 #include "MemBuf.h"
22 #include "SquidTime.h"
23
24 Auth::Digest::UserRequest::UserRequest() :
25 noncehex(NULL),
26 cnonce(NULL),
27 realm(NULL),
28 pszPass(NULL),
29 algorithm(NULL),
30 pszMethod(NULL),
31 qop(NULL),
32 uri(NULL),
33 response(NULL),
34 nonce(NULL)
35 {
36 memset(nc, 0, sizeof(nc));
37 memset(&flags, 0, sizeof(flags));
38 }
39
40 /**
41 * Delete the digest request structure.
42 * Does NOT delete related AuthUser structures
43 */
44 Auth::Digest::UserRequest::~UserRequest()
45 {
46 assert(LockCount()==0);
47
48 safe_free(noncehex);
49 safe_free(cnonce);
50 safe_free(realm);
51 safe_free(pszPass);
52 safe_free(algorithm);
53 safe_free(pszMethod);
54 safe_free(qop);
55 safe_free(uri);
56 safe_free(response);
57
58 if (nonce)
59 authDigestNonceUnlink(nonce);
60 }
61
62 int
63 Auth::Digest::UserRequest::authenticated() const
64 {
65 if (user() != NULL && user()->credentials() == Auth::Ok)
66 return 1;
67
68 return 0;
69 }
70
71 const char *
72 Auth::Digest::UserRequest::credentialsStr()
73 {
74 return realm;
75 }
76
77 /** log a digest user in
78 */
79 void
80 Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData *, Http::HdrType)
81 {
82 HASHHEX SESSIONKEY;
83 HASHHEX HA2 = "";
84 HASHHEX Response;
85
86 /* if the check has corrupted the user, just return */
87 if (user() == NULL || user()->credentials() == Auth::Failed) {
88 return;
89 }
90
91 Auth::User::Pointer auth_user = user();
92
93 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User*>(auth_user.getRaw());
94 assert(digest_user != NULL);
95
96 Auth::Digest::UserRequest *digest_request = this;
97
98 /* do we have the HA1 */
99 if (!digest_user->HA1created) {
100 auth_user->credentials(Auth::Pending);
101 return;
102 }
103
104 if (digest_request->nonce == NULL) {
105 /* this isn't a nonce we issued */
106 auth_user->credentials(Auth::Failed);
107 return;
108 }
109
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);
118
119 debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'");
120
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);
126 return;
127 }
128
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
134 * used.
135 */
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);
140
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");
145 return;
146 } else {
147 const char *useragent = request->header.getStr(Http::HdrType::USER_AGENT);
148
149 static Ip::Address last_broken_addr;
150 static int seen_broken_client = 0;
151
152 if (!seen_broken_client) {
153 last_broken_addr.setNoAddr();
154 seen_broken_client = 1;
155 }
156
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.");
162
163 last_broken_addr = request->client_addr;
164 }
165 }
166 } else {
167 auth_user->credentials(Auth::Failed);
168 digest_request->flags.invalid_password = true;
169 digest_request->setDenyMessage("Incorrect password");
170 return;
171 }
172 }
173
174 /* check for stale nonce */
175 /* check Auth::Pending to avoid loop */
176
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);
183 return;
184 }
185
186 auth_user->credentials(Auth::Ok);
187
188 /* password was checked and did match */
189 debugs(29, 4, "user '" << auth_user->username() << "' validated OK");
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::HdrType 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 ? Http::HdrType::AUTHENTICATION_INFO : Http::HdrType::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::SchemeConfig::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) {
236 flags.authinfo_sent = true;
237 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(user().getRaw());
238 if (!digest_user)
239 return;
240
241 digest_nonce_h *nextnonce = digest_user->currentNonce();
242 if (!nextnonce || authDigestNonceLastRequest(nonce)) {
243 nextnonce = authenticateDigestNonceNew();
244 authDigestUserLinkNonce(digest_user, nextnonce);
245 }
246 debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nextnonce) << "\"");
247 httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nextnonce));
248 }
249 }
250
251 #if WAITING_FOR_TE
252 void
253 Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply * rep, int accel)
254 {
255 int type;
256
257 if (!auth_user_request)
258 return;
259
260 /* has the header already been send? */
261 if (flags.authinfo_sent)
262 return;
263
264 /* don't add to authentication error pages */
265 if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
266 || (accel && rep->sline.status() == Http::scUnauthorized))
267 return;
268
269 type = accel ? Http::HdrType::AUTHENTICATION_INFO : Http::HdrType::PROXY_AUTHENTICATION_INFO;
270
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();
274 if (!nonce) {
275 nonce = authenticateDigestNonceNew();
276 authDigestUserLinkNonce(digest_user, nonce);
277 }
278 debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nonce) << "\"");
279 httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nonce));
280 }
281 }
282 #endif
283
284 /* send the initial data to a digest authenticator module */
285 void
286 Auth::Digest::UserRequest::startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
287 {
288 char buf[8192];
289
290 assert(user() != NULL && user()->auth_type == Auth::AUTH_DIGEST);
291 debugs(29, 9, HERE << "'\"" << user()->username() << "\":\"" << realm << "\"'");
292
293 if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram == NULL) {
294 debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured.");
295 handler(data);
296 return;
297 }
298
299 const char *keyExtras = helperRequestKeyExtras(request, al);
300 if (keyExtras)
301 snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras);
302 else
303 snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm);
304
305 helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply,
306 new Auth::StateData(this, handler, data));
307 }
308
309 void
310 Auth::Digest::UserRequest::HandleReply(void *data, const Helper::Reply &reply)
311 {
312 Auth::StateData *replyData = static_cast<Auth::StateData *>(data);
313 debugs(29, 9, HERE << "reply=" << reply);
314
315 assert(replyData->auth_user_request != NULL);
316 Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request;
317
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");
324
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;
333 }
334
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);
338
339 CvtBin(reply.other().content(), digest_user->HA1);
340 digest_user->HA1created = 1;
341 }
342 break;
343
344 case Helper::Okay: {
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);
348
349 if (const char *ha1Note = reply.notes.findFirst("ha1")) {
350 CvtBin(ha1Note, digest_user->HA1);
351 digest_user->HA1created = 1;
352 } else {
353 debugs(29, DBG_IMPORTANT, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply);
354 }
355 }
356 break;
357
358 case Helper::TT:
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.
361
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);
370
371 digest_request->user()->credentials(Auth::Failed);
372 digest_request->flags.invalid_password = true;
373
374 SBuf msgNote;
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;
383 }
384 }
385 }
386 break;
387 }
388
389 void *cbdata = NULL;
390 if (cbdataReferenceValidDone(replyData->data, &cbdata))
391 replyData->handler(cbdata);
392
393 delete replyData;
394 }
395