]> git.ipfire.org Git - thirdparty/squid.git/blame - src/auth/digest/UserRequest.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / auth / digest / UserRequest.cc
CommitLineData
bbc27441 1/*
77b1029d 2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
bbc27441
AJ
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
f7f3304a 9#include "squid.h"
d4806c91 10#include "AccessLogEntry.h"
12daeef6 11#include "auth/digest/Config.h"
aa110616 12#include "auth/digest/User.h"
616cfc4c 13#include "auth/digest/UserRequest.h"
928f3421 14#include "auth/State.h"
86c63190 15#include "format/Format.h"
24438ec5
AJ
16#include "helper.h"
17#include "helper/Reply.h"
a5bac1d2 18#include "HttpHeaderTools.h"
928f3421
AJ
19#include "HttpReply.h"
20#include "HttpRequest.h"
d4806c91 21#include "MemBuf.h"
928f3421
AJ
22#include "SquidTime.h"
23
c7baff40 24Auth::Digest::UserRequest::UserRequest() :
b20ce974 25 noncehex(NULL),
f53969cc
SM
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)
1032a194
AJ
35{
36 memset(nc, 0, sizeof(nc));
37 memset(&flags, 0, sizeof(flags));
38}
928f3421
AJ
39
40/**
41 * Delete the digest request structure.
42 * Does NOT delete related AuthUser structures
43 */
c7baff40 44Auth::Digest::UserRequest::~UserRequest()
928f3421 45{
8bf217bd 46 assert(LockCount()==0);
ea0695f2 47
b20ce974 48 safe_free(noncehex);
928f3421
AJ
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
928f3421 62int
c7baff40 63Auth::Digest::UserRequest::authenticated() const
928f3421 64{
d87154ee 65 if (user() != NULL && user()->credentials() == Auth::Ok)
928f3421
AJ
66 return 1;
67
68 return 0;
69}
70
d4806c91
CT
71const char *
72Auth::Digest::UserRequest::credentialsStr()
73{
74 return realm;
75}
76
928f3421
AJ
77/** log a digest user in
78 */
79void
789217a2 80Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData *, Http::HdrType)
928f3421 81{
928f3421
AJ
82 HASHHEX SESSIONKEY;
83 HASHHEX HA2 = "";
84 HASHHEX Response;
85
928f3421 86 /* if the check has corrupted the user, just return */
d87154ee 87 if (user() == NULL || user()->credentials() == Auth::Failed) {
928f3421
AJ
88 return;
89 }
90
d87154ee 91 Auth::User::Pointer auth_user = user();
928f3421 92
aa110616 93 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User*>(auth_user.getRaw());
56a49fda 94 assert(digest_user != NULL);
928f3421 95
c7baff40 96 Auth::Digest::UserRequest *digest_request = this;
56a49fda
AJ
97
98 /* do we have the HA1 */
928f3421 99 if (!digest_user->HA1created) {
d87154ee 100 auth_user->credentials(Auth::Pending);
928f3421
AJ
101 return;
102 }
103
104 if (digest_request->nonce == NULL) {
105 /* this isn't a nonce we issued */
d87154ee 106 auth_user->credentials(Auth::Failed);
928f3421
AJ
107 return;
108 }
109
110 DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL,
b20ce974 111 authenticateDigestNonceNonceHex(digest_request->nonce),
928f3421
AJ
112 digest_request->cnonce,
113 digest_user->HA1, SESSIONKEY);
7f06a3d8 114 SBuf sTmp = request->method.image();
b20ce974 115 DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(digest_request->nonce),
928f3421 116 digest_request->nc, digest_request->cnonce, digest_request->qop,
7f06a3d8 117 sTmp.c_str(), digest_request->uri, HA2, Response);
928f3421
AJ
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 */
3dd52a0b 124 digest_request->flags.helper_queried = true;
d87154ee 125 auth_user->credentials(Auth::Pending);
928f3421
AJ
126 return;
127 }
128
dc79fed8 129 if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->PostWorkaround && request->method != Http::METHOD_GET) {
928f3421
AJ
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 */
7f06a3d8 136 sTmp = HttpRequestMethod(Http::METHOD_GET).image();
b20ce974 137 DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(digest_request->nonce),
928f3421 138 digest_request->nc, digest_request->cnonce, digest_request->qop,
7f06a3d8 139 sTmp.c_str(), digest_request->uri, HA2, Response);
928f3421
AJ
140
141 if (strcasecmp(digest_request->response, Response)) {
d87154ee 142 auth_user->credentials(Auth::Failed);
3dd52a0b 143 digest_request->flags.invalid_password = true;
928f3421
AJ
144 digest_request->setDenyMessage("Incorrect password");
145 return;
146 } else {
789217a2 147 const char *useragent = request->header.getStr(Http::HdrType::USER_AGENT);
928f3421 148
08acdd08 149 static Ip::Address last_broken_addr;
928f3421
AJ
150 static int seen_broken_client = 0;
151
152 if (!seen_broken_client) {
4dd643d5 153 last_broken_addr.setNoAddr();
928f3421
AJ
154 seen_broken_client = 1;
155 }
156
157 if (last_broken_addr != request->client_addr) {
c7baff40 158 debugs(29, DBG_IMPORTANT, "Digest POST bug detected from " <<
928f3421
AJ
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 {
d87154ee 167 auth_user->credentials(Auth::Failed);
3dd52a0b 168 digest_request->flags.invalid_password = true;
928f3421
AJ
169 digest_request->setDenyMessage("Incorrect password");
170 return;
171 }
c9dbe80f 172 }
928f3421 173
c9dbe80f 174 /* check for stale nonce */
6b634dc3
FB
175 /* check Auth::Pending to avoid loop */
176
177 if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc) && user()->credentials() != Auth::Pending) {
b20ce974 178 debugs(29, 3, auth_user->username() << "' validated OK but nonce stale: " << digest_request->noncehex);
6b634dc3
FB
179 /* Pending prevent banner and makes a ldap control */
180 auth_user->credentials(Auth::Pending);
181 nonce->flags.valid = false;
182 authDigestNoncePurge(nonce);
c9dbe80f 183 return;
928f3421
AJ
184 }
185
d87154ee 186 auth_user->credentials(Auth::Ok);
928f3421
AJ
187
188 /* password was checked and did match */
65cbd5a7 189 debugs(29, 4, "user '" << auth_user->username() << "' validated OK");
928f3421
AJ
190}
191
51a3dd58 192Auth::Direction
c7baff40 193Auth::Digest::UserRequest::module_direction()
928f3421 194{
616cfc4c 195 if (user()->auth_type != Auth::AUTH_DIGEST)
51a3dd58 196 return Auth::CRED_ERROR;
928f3421 197
d232141d 198 switch (user()->credentials()) {
928f3421 199
d87154ee 200 case Auth::Ok:
51a3dd58 201 return Auth::CRED_VALID;
928f3421 202
572d2e31 203 case Auth::Handshake:
d87154ee 204 case Auth::Failed:
928f3421 205 /* send new challenge */
51a3dd58 206 return Auth::CRED_CHALLENGE;
d232141d 207
d87154ee
AJ
208 case Auth::Unchecked:
209 case Auth::Pending:
51a3dd58 210 return Auth::CRED_LOOKUP;
d232141d
AJ
211
212 default:
51a3dd58 213 return Auth::CRED_ERROR;
928f3421 214 }
928f3421
AJ
215}
216
928f3421 217void
c7baff40 218Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply * rep, int accel)
928f3421 219{
789217a2 220 Http::HdrType type;
928f3421
AJ
221
222 /* don't add to authentication error pages */
9b769c67
AJ
223 if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
224 || (accel && rep->sline.status() == Http::scUnauthorized))
928f3421
AJ
225 return;
226
789217a2 227 type = accel ? Http::HdrType::AUTHENTICATION_INFO : Http::HdrType::PROXY_AUTHENTICATION_INFO;
928f3421
AJ
228
229#if WAITING_FOR_TE
230 /* test for http/1.1 transfer chunked encoding */
231 if (chunkedtest)
232 return;
233#endif
234
dc79fed8 235 if ((static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) {
3dd52a0b 236 flags.authinfo_sent = true;
572d2e31 237 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(user().getRaw());
3d01c5ab
AJ
238 if (!digest_user)
239 return;
240
572d2e31
HN
241 digest_nonce_h *nextnonce = digest_user->currentNonce();
242 if (!nextnonce || authDigestNonceLastRequest(nonce)) {
243 nextnonce = authenticateDigestNonceNew();
244 authDigestUserLinkNonce(digest_user, nextnonce);
245 }
b20ce974 246 debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nextnonce) << "\"");
247 httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nextnonce));
928f3421
AJ
248 }
249}
250
251#if WAITING_FOR_TE
928f3421 252void
c7baff40 253Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply * rep, int accel)
928f3421
AJ
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 */
9b769c67
AJ
265 if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
266 || (accel && rep->sline.status() == Http::scUnauthorized))
928f3421
AJ
267 return;
268
789217a2 269 type = accel ? Http::HdrType::AUTHENTICATION_INFO : Http::HdrType::PROXY_AUTHENTICATION_INFO;
928f3421 270
372fccd6 271 if ((static_cast<Auth::Digest::Config*>(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) {
572d2e31
HN
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 }
b20ce974 278 debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nonce) << "\"");
279 httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nonce));
928f3421
AJ
280 }
281}
282#endif
283
284/* send the initial data to a digest authenticator module */
285void
30c3f584 286Auth::Digest::UserRequest::startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
928f3421 287{
928f3421 288 char buf[8192];
56a49fda 289
616cfc4c 290 assert(user() != NULL && user()->auth_type == Auth::AUTH_DIGEST);
c7baff40 291 debugs(29, 9, HERE << "'\"" << user()->username() << "\":\"" << realm << "\"'");
928f3421 292
dc79fed8 293 if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram == NULL) {
d232141d 294 debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured.");
4c535e87 295 handler(data);
928f3421
AJ
296 return;
297 }
298
d4806c91 299 const char *keyExtras = helperRequestKeyExtras(request, al);
7e851a3e
SK
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);
928f3421 304
c7baff40 305 helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply,
1c756645 306 new Auth::StateData(this, handler, data));
928f3421
AJ
307}
308
309void
24438ec5 310Auth::Digest::UserRequest::HandleReply(void *data, const Helper::Reply &reply)
928f3421 311{
1c756645 312 Auth::StateData *replyData = static_cast<Auth::StateData *>(data);
e166785a 313 debugs(29, 9, HERE << "reply=" << reply);
928f3421
AJ
314
315 assert(replyData->auth_user_request != NULL);
c7baff40 316 Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request;
928f3421 317
71e7400c
AJ
318 // add new helper kv-pair notes to the credentials object
319 // so that any transaction using those credentials can access them
d665de37
A
320 static const NotePairs::Names appendables = { SBuf("group"), SBuf("nonce"), SBuf("tag") };
321 auth_user_request->user()->notes.replaceOrAddOrAppend(&reply.notes, appendables);
c10ebce8
AJ
322 // remove any private credentials detail which got added.
323 auth_user_request->user()->notes.remove("ha1");
71e7400c 324
c69199bb 325 static bool oldHelperWarningDone = false;
dacb64b9 326 switch (reply.result) {
2428ce02 327 case Helper::Unknown: {
c69199bb
AJ
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 }
56a49fda 334
c69199bb
AJ
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);
928f3421 338
c69199bb
AJ
339 CvtBin(reply.other().content(), digest_user->HA1);
340 digest_user->HA1created = 1;
e166785a 341 }
dacb64b9 342 break;
e166785a 343
2428ce02 344 case Helper::Okay: {
56a49fda 345 /* allow this because the digest_request pointer is purely local */
aa110616 346 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
56a49fda
AJ
347 assert(digest_user != NULL);
348
75d47340 349 if (const char *ha1Note = reply.notes.findFirst("ha1")) {
cf9f0261 350 CvtBin(ha1Note, digest_user->HA1);
c69199bb
AJ
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 }
928f3421 355 }
dacb64b9 356 break;
e166785a 357
2428ce02 358 case Helper::TT:
c69199bb 359 debugs(29, DBG_IMPORTANT, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply);
f53969cc 360 // fall through to next case. Handle this as an ERR response.
c69199bb 361
32fd6d8a 362 case Helper::TimedOut:
2428ce02 363 case Helper::BrokenHelper:
f53969cc
SM
364 // TODO retry the broken lookup on another helper?
365 // fall through to next case for now. Handle this as an ERR response silently.
2428ce02 366 case Helper::Error: {
c69199bb
AJ
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);
3dd52a0b 372 digest_request->flags.invalid_password = true;
c69199bb 373
75d47340
CT
374 SBuf msgNote;
375 if (reply.notes.find(msgNote, "message")) {
376 digest_request->setDenyMessage(msgNote.c_str());
c69199bb
AJ
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;
e166785a 387 }
928f3421 388
e166785a 389 void *cbdata = NULL;
928f3421 390 if (cbdataReferenceValidDone(replyData->data, &cbdata))
4c535e87 391 replyData->handler(cbdata);
928f3421 392
1c756645 393 delete replyData;
928f3421 394}
f53969cc 395