UserRequest.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2023 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"
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
23Auth::Digest::UserRequest::UserRequest() :
24 noncehex(nullptr),
25 cnonce(nullptr),
26 realm(nullptr),
27 pszPass(nullptr),
28 algorithm(nullptr),
29 pszMethod(nullptr),
30 qop(nullptr),
31 uri(nullptr),
32 response(nullptr),
33 nonce(nullptr)
34{
35 memset(nc, 0, sizeof(nc));
36 memset(&flags, 0, sizeof(flags));
37}
38
43Auth::Digest::UserRequest::~UserRequest()
44{
45 assert(LockCount()==0);
46
47 safe_free(noncehex);
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)
59}
60
61int
62Auth::Digest::UserRequest::authenticated() const
63{
64 if (user() != nullptr && user()->credentials() == Auth::Ok)
65 return 1;
66
67 return 0;
68}
69
70const char *
71Auth::Digest::UserRequest::credentialsStr()
72{
73 return realm;
74}
75
78void
80{
81 HASHHEX SESSIONKEY;
82 HASHHEX HA2 = "";
83 HASHHEX Response;
84
85 /* if the check has corrupted the user, just return */
86 if (user() == nullptr || 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 != nullptr);
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 == nullptr) {
104 /* this isn't a nonce we issued */
105 auth_user->credentials(Auth::Failed);
106 return;
107 }
108
109 DigestCalcHA1(digest_request->algorithm, nullptr, nullptr, nullptr,
110 authenticateDigestNonceNonceHex(digest_request->nonce),
111 digest_request->cnonce,
112 digest_user->HA1, SESSIONKEY);
113 SBuf sTmp = request->method.image();
114 DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(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::SchemeConfig::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 */
136 DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(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(Http::HdrType::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, "ERROR: User agent Digest Authentication 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 /* check Auth::Pending to avoid loop */
175
176 if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc) && user()->credentials() != Auth::Pending) {
177 debugs(29, 3, auth_user->username() << "' validated OK but nonce stale: " << digest_request->noncehex);
178 /* Pending prevent banner and makes a ldap control */
179 auth_user->credentials(Auth::Pending);
180 nonce->flags.valid = false;
182 return;
183 }
184
185 auth_user->credentials(Auth::Ok);
186
187 /* password was checked and did match */
188 debugs(29, 4, "user '" << auth_user->username() << "' validated OK");
189}
190
192Auth::Digest::UserRequest::module_direction()
193{
194 if (user()->auth_type != Auth::AUTH_DIGEST)
195 return Auth::CRED_ERROR;
196
197 switch (user()->credentials()) {
198
199 case Auth::Ok:
200 return Auth::CRED_VALID;
201
202 case Auth::Handshake:
203 case Auth::Failed:
204 /* send new challenge */
206
207 case Auth::Unchecked:
208 case Auth::Pending:
209 return Auth::CRED_LOOKUP;
210
211 default:
212 return Auth::CRED_ERROR;
213 }
214}
215
216void
217Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply * rep, int accel)
218{
219 Http::HdrType type;
220
221 /* don't add to authentication error pages */
222 if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
223 || (accel && rep->sline.status() == Http::scUnauthorized))
224 return;
225
227
228#if WAITING_FOR_TE
229 /* test for http/1.1 transfer chunked encoding */
230 if (chunkedtest)
231 return;
232#endif
233
234 if ((static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) {
235 flags.authinfo_sent = true;
236 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(user().getRaw());
237 if (!digest_user)
238 return;
239
240 digest_nonce_h *nextnonce = digest_user->currentNonce();
241 if (!nextnonce || authDigestNonceLastRequest(nonce)) {
242 nextnonce = authenticateDigestNonceNew();
243 authDigestUserLinkNonce(digest_user, nextnonce);
244 }
245 debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nextnonce) << "\"");
246 httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nextnonce));
247 }
248}
249
250#if WAITING_FOR_TE
251void
252Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply * rep, int accel)
253{
254 int type;
255
256 if (!auth_user_request)
257 return;
258
259 /* has the header already been send? */
260 if (flags.authinfo_sent)
261 return;
262
263 /* don't add to authentication error pages */
264 if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
265 || (accel && rep->sline.status() == Http::scUnauthorized))
266 return;
267
269
270 if ((static_cast<Auth::Digest::Config*>(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) {
271 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
272 nonce = digest_user->currentNonce();
273 if (!nonce) {
275 authDigestUserLinkNonce(digest_user, nonce);
276 }
277 debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nonce) << "\"");
278 httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nonce));
279 }
280}
281#endif
282
283/* send the initial data to a digest authenticator module */
284void
285Auth::Digest::UserRequest::startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
286{
287 char buf[8192];
288
289 assert(user() != nullptr && user()->auth_type == Auth::AUTH_DIGEST);
290 debugs(29, 9, "'\"" << user()->username() << "\":\"" << realm << "\"'");
291
292 if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram == nullptr) {
293 debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured.");
294 handler(data);
295 return;
296 }
297
298 const char *keyExtras = helperRequestKeyExtras(request, al);
299 if (keyExtras)
300 snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras);
301 else
302 snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm);
303
304 helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply,
305 new Auth::StateData(this, handler, data));
306}
307
308void
309Auth::Digest::UserRequest::HandleReply(void *data, const Helper::Reply &reply)
310{
311 Auth::StateData *replyData = static_cast<Auth::StateData *>(data);
312 debugs(29, 9, "reply=" << reply);
313
314 assert(replyData->auth_user_request != nullptr);
315 Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request;
316
317 // add new helper kv-pair notes to the credentials object
318 // so that any transaction using those credentials can access them
319 static const NotePairs::Names appendables = { SBuf("group"), SBuf("nonce"), SBuf("tag") };
320 auth_user_request->user()->notes.replaceOrAddOrAppend(&reply.notes, appendables);
321 // remove any private credentials detail which got added.
322 auth_user_request->user()->notes.remove("ha1");
323
324 static bool oldHelperWarningDone = false;
325 switch (reply.result) {
326 case Helper::Unknown: {
327 // Squid 3.3 and older the digest helper only returns a HA1 hash (no "OK")
328 // the HA1 will be found in content() for these responses.
329 if (!oldHelperWarningDone) {
330 debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format HA1 response. It needs to be upgraded.");
331 oldHelperWarningDone=true;
332 }
333
334 /* allow this because the digest_request pointer is purely local */
335 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
336 assert(digest_user != nullptr);
337
338 CvtBin(reply.other().content(), digest_user->HA1);
339 digest_user->HA1created = 1;
340 }
341 break;
342
343 case Helper::Okay: {
344 /* allow this because the digest_request pointer is purely local */
345 Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
346 assert(digest_user != nullptr);
347
348 if (const char *ha1Note = reply.notes.findFirst("ha1")) {
349 CvtBin(ha1Note, digest_user->HA1);
350 digest_user->HA1created = 1;
351 } else {
352 debugs(29, DBG_IMPORTANT, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply);
353 }
354 }
355 break;
356
357 case Helper::TT:
358 debugs(29, DBG_IMPORTANT, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply);
359 [[fallthrough]]; // to handle this as an ERR response
360
361 case Helper::TimedOut:
363 [[fallthrough]]; // to (silently) handle this as an ERR response
364
365 // TODO retry the broken lookup on another helper?
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 // TODO deprecate and remove old auth digest helper protocol
380 digest_request->setDenyMessage(reply.other().content());
381 if (!oldHelperWarningDone) {
382 debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format ERR response. It needs to be upgraded.");
383 oldHelperWarningDone=true;
384 }
385 }
386 }
387 break;
388 }
389
390 void *cbdata = nullptr;
391 if (cbdataReferenceValidDone(replyData->data, &cbdata))
392 replyData->handler(cbdata);
393
394 delete replyData;
395}
396
void httpHeaderPutStrf(HttpHeader *hdr, Http::HdrType id, const char *fmt,...)
class SquidConfig Config
Definition: SquidConfig.cc:12
void AUTHCB(void *)
Definition: UserRequest.h:57
#define assert(EX)
Definition: assert.h:17
Helper::ClientPointer digestauthenticators
Definition: Config.cc:49
void authDigestUserLinkNonce(Auth::Digest::User *user, digest_nonce_h *nonce)
Definition: Config.cc:650
void authDigestNonceUnlink(digest_nonce_h *nonce)
Definition: Config.cc:279
int authDigestNonceIsValid(digest_nonce_h *nonce, char nc[9])
Definition: Config.cc:325
const char * authenticateDigestNonceNonceHex(const digest_nonce_h *nonce)
Definition: Config.cc:296
int authDigestNonceLastRequest(digest_nonce_h *nonce)
Definition: Config.cc:406
void authDigestNoncePurge(digest_nonce_h *nonce)
Definition: Config.cc:426
digest_nonce_h * authenticateDigestNonceNew(void)
Definition: Config.cc:120
static void authenticate(int socket_fd, const char *username, const char *passwd)
#define cbdataReferenceValidDone(var, ptr)
Definition: cbdata.h:239
static SchemeConfig * Find(const char *proxy_auth)
Definition: SchemeConfig.cc:59
UserRequest::Pointer auth_user_request
Definition: State.h:39
AUTHCB * handler
Definition: State.h:40
void * data
Definition: State.h:38
virtual User::Pointer user()
Definition: UserRequest.h:143
NotePairs notes
Definition: Reply.h:62
Helper::ResultCode result
The helper response 'result' field.
Definition: Reply.h:59
const MemBuf & other() const
Definition: Reply.h:42
const char * getStr(Http::HdrType id) const
Definition: HttpHeader.cc:1164
Http::StatusLine sline
Definition: HttpReply.h:56
const SBuf & image() const
HttpRequestMethod method
Definition: HttpRequest.h:114
Ip::Address client_addr
Definition: HttpRequest.h:149
HttpHeader header
Definition: Message.h:74
Http::StatusCode status() const
retrieve the status code for this status line
Definition: StatusLine.h:45
void setNoAddr()
Definition: Address.cc:292
char * content()
start of the added data
Definition: MemBuf.h:41
bool hasContent() const
Definition: MemBuf.h:54
std::vector< SBuf > Names
Definition: Notes.h:199
bool find(SBuf &resultNote, const char *noteKey, const char *sep=",") const
Definition: Notes.cc:272
const char * findFirst(const char *noteKey) const
Definition: Notes.cc:297
C * getRaw() const
Definition: RefCount.h:89
Definition: SBuf.h:94
const char * c_str()
Definition: SBuf.cc:516
Definition: cbdata.cc:38
#define DBG_IMPORTANT
Definition: Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:194
#define DBG_CRITICAL
Definition: Stream.h:37
void helperSubmit(const Helper::Client::Pointer &hlp, const char *const buf, HLPCB *const callback, void *const data)
Definition: helper.cc:480
@ AUTH_DIGEST
Definition: Type.h:21
Direction
Definition: UserRequest.h:64
@ CRED_ERROR
ERROR in the auth module. Cannot determine the state of this request.
Definition: UserRequest.h:68
@ CRED_CHALLENGE
Client needs to be challenged. secure token.
Definition: UserRequest.h:65
@ CRED_LOOKUP
Credentials need to be validated with the backend helper.
Definition: UserRequest.h:67
@ CRED_VALID
Credentials are valid and a up to date. The OK/Failed state is accurate.
Definition: UserRequest.h:66
@ Unknown
Definition: ResultCode.h:17
@ BrokenHelper
Definition: ResultCode.h:20
@ Error
Definition: ResultCode.h:19
@ TimedOut
Definition: ResultCode.h:21
@ Okay
Definition: ResultCode.h:18
@ scUnauthorized
Definition: StatusCode.h:45
@ scProxyAuthenticationRequired
Definition: StatusCode.h:51
@ METHOD_GET
Definition: MethodType.h:25
@ PROXY_AUTHENTICATION_INFO
@ AUTHENTICATION_INFO
static char credentials[MAX_USERNAME_LEN+MAX_DOMAIN_LEN+2]
static void handler(int signo)
Definition: purge.cc:858
void DigestCalcHA1(const char *pszAlg, const char *pszUserName, const char *pszRealm, const char *pszPassword, const char *pszNonce, const char *pszCNonce, HASH HA1, HASHHEX SessionKey)
Definition: rfc2617.c:88
void CvtBin(const HASHHEX Hex, HASH Bin)
Definition: rfc2617.c:49
char HASHHEX[HASHHEXLEN+1]
Definition: rfc2617.h:33
void DigestCalcResponse(const HASHHEX HA1, const char *pszNonce, const char *pszNonceCount, const char *pszCNonce, const char *pszQop, const char *pszMethod, const char *pszDigestUri, const HASHHEX HEntity, HASHHEX Response)
Definition: rfc2617.c:126
static Auth::Config * getConfig(char const *type_str)
Definition: testAuth.cc:69
#define safe_free(x)
Definition: xalloc.h:73

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors