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