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 
23 Auth::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 
43 Auth::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)
58  authDigestNonceUnlink(nonce);
59 }
60 
61 const char *
62 Auth::Digest::UserRequest::credentialsStr()
63 {
64  return realm;
65 }
66 
69 void
71 {
72  HASHHEX SESSIONKEY;
73  HASHHEX HA2 = "";
74  HASHHEX Response;
75 
76  /* if the check has corrupted the user, just return */
77  if (user() == nullptr || user()->credentials() == Auth::Failed) {
78  return;
79  }
80 
81  Auth::User::Pointer auth_user = user();
82 
83  Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User*>(auth_user.getRaw());
84  assert(digest_user != nullptr);
85 
86  Auth::Digest::UserRequest *digest_request = this;
87 
88  /* do we have the HA1 */
89  if (!digest_user->HA1created) {
90  auth_user->credentials(Auth::Pending);
91  return;
92  }
93 
94  if (digest_request->nonce == nullptr) {
95  /* this isn't a nonce we issued */
96  auth_user->credentials(Auth::Failed);
97  return;
98  }
99 
100  DigestCalcHA1(digest_request->algorithm, nullptr, nullptr, nullptr,
101  authenticateDigestNonceNonceHex(digest_request->nonce),
102  digest_request->cnonce,
103  digest_user->HA1, SESSIONKEY);
104  SBuf sTmp = request->method.image();
105  DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(digest_request->nonce),
106  digest_request->nc, digest_request->cnonce, digest_request->qop,
107  sTmp.c_str(), digest_request->uri, HA2, Response);
108 
109  debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'");
110 
111  if (strcasecmp(digest_request->response, Response) != 0) {
112  if (!digest_request->flags.helper_queried) {
113  /* Query the helper in case the password has changed */
114  digest_request->flags.helper_queried = true;
115  auth_user->credentials(Auth::Pending);
116  return;
117  }
118 
119  if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->PostWorkaround && request->method != Http::METHOD_GET) {
120  /* Ugly workaround for certain very broken browsers using the
121  * wrong method to calculate the request-digest on POST request.
122  * This should be deleted once Digest authentication becomes more
123  * widespread and such broken browsers no longer are commonly
124  * used.
125  */
127  DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceHex(digest_request->nonce),
128  digest_request->nc, digest_request->cnonce, digest_request->qop,
129  sTmp.c_str(), digest_request->uri, HA2, Response);
130 
131  if (strcasecmp(digest_request->response, Response)) {
132  auth_user->credentials(Auth::Failed);
133  digest_request->flags.invalid_password = true;
134  digest_request->setDenyMessage("Incorrect password");
135  return;
136  } else {
137  const char *useragent = request->header.getStr(Http::HdrType::USER_AGENT);
138 
139  static Ip::Address last_broken_addr;
140  static int seen_broken_client = 0;
141 
142  if (!seen_broken_client) {
143  last_broken_addr.setNoAddr();
144  seen_broken_client = 1;
145  }
146 
147  if (last_broken_addr != request->client_addr) {
148  debugs(29, DBG_IMPORTANT, "ERROR: User agent Digest Authentication POST bug detected from " <<
149  request->client_addr << " using '" <<
150  (useragent ? useragent : "-") <<
151  "'. Please upgrade browser. See Bug #630 for details.");
152 
153  last_broken_addr = request->client_addr;
154  }
155  }
156  } else {
157  auth_user->credentials(Auth::Failed);
158  digest_request->flags.invalid_password = true;
159  digest_request->setDenyMessage("Incorrect password");
160  return;
161  }
162  }
163 
164  /* check for stale nonce */
165  /* check Auth::Pending to avoid loop */
166 
167  if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc) && user()->credentials() != Auth::Pending) {
168  debugs(29, 3, auth_user->username() << "' validated OK but nonce stale: " << digest_request->noncehex);
169  /* Pending prevent banner and makes a ldap control */
170  auth_user->credentials(Auth::Pending);
171  nonce->flags.valid = false;
172  authDigestNoncePurge(nonce);
173  return;
174  }
175 
176  auth_user->credentials(Auth::Ok);
177 
178  /* password was checked and did match */
179  debugs(29, 4, "user '" << auth_user->username() << "' validated OK");
180 }
181 
183 Auth::Digest::UserRequest::module_direction()
184 {
185  if (user()->auth_type != Auth::AUTH_DIGEST)
186  return Auth::CRED_ERROR;
187 
188  switch (user()->credentials()) {
189 
190  case Auth::Ok:
191  return Auth::CRED_VALID;
192 
193  case Auth::Handshake:
194  case Auth::Failed:
195  /* send new challenge */
196  return Auth::CRED_CHALLENGE;
197 
198  case Auth::Unchecked:
199  case Auth::Pending:
200  return Auth::CRED_LOOKUP;
201 
202  default:
203  return Auth::CRED_ERROR;
204  }
205 }
206 
207 void
208 Auth::Digest::UserRequest::addAuthenticationInfoHeader(HttpReply * rep, int accel)
209 {
210  Http::HdrType type;
211 
212  /* don't add to authentication error pages */
213  if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
214  || (accel && rep->sline.status() == Http::scUnauthorized))
215  return;
216 
218 
219 #if WAITING_FOR_TE
220  /* test for http/1.1 transfer chunked encoding */
221  if (chunkedtest)
222  return;
223 #endif
224 
225  if ((static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram) && authDigestNonceLastRequest(nonce)) {
226  flags.authinfo_sent = true;
227  Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(user().getRaw());
228  if (!digest_user)
229  return;
230 
231  digest_nonce_h *nextnonce = digest_user->currentNonce();
232  if (!nextnonce || authDigestNonceLastRequest(nonce)) {
233  nextnonce = authenticateDigestNonceNew();
234  authDigestUserLinkNonce(digest_user, nextnonce);
235  }
236  debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nextnonce) << "\"");
237  httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nextnonce));
238  }
239 }
240 
241 #if WAITING_FOR_TE
242 void
243 Auth::Digest::UserRequest::addAuthenticationInfoTrailer(HttpReply * rep, int accel)
244 {
245  int type;
246 
247  if (!auth_user_request)
248  return;
249 
250  /* has the header already been send? */
251  if (flags.authinfo_sent)
252  return;
253 
254  /* don't add to authentication error pages */
255  if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired)
256  || (accel && rep->sline.status() == Http::scUnauthorized))
257  return;
258 
260 
261  if ((static_cast<Auth::Digest::Config*>(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) {
262  Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
263  nonce = digest_user->currentNonce();
264  if (!nonce) {
265  nonce = authenticateDigestNonceNew();
266  authDigestUserLinkNonce(digest_user, nonce);
267  }
268  debugs(29, 9, "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceHex(nonce) << "\"");
269  httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceHex(nonce));
270  }
271 }
272 #endif
273 
274 /* send the initial data to a digest authenticator module */
275 void
276 Auth::Digest::UserRequest::startHelperLookup(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
277 {
278  char buf[8192];
279 
280  assert(user() != nullptr && user()->auth_type == Auth::AUTH_DIGEST);
281  debugs(29, 9, "'\"" << user()->username() << "\":\"" << realm << "\"'");
282 
283  if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->authenticateProgram == nullptr) {
284  debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured.");
285  handler(data);
286  return;
287  }
288 
289  const char *keyExtras = helperRequestKeyExtras(request, al);
290  if (keyExtras)
291  snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras);
292  else
293  snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm);
294 
295  helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply,
296  new Auth::StateData(this, handler, data));
297 }
298 
299 void
300 Auth::Digest::UserRequest::HandleReply(void *data, const Helper::Reply &reply)
301 {
302  Auth::StateData *replyData = static_cast<Auth::StateData *>(data);
303  debugs(29, 9, "reply=" << reply);
304 
305  assert(replyData->auth_user_request != nullptr);
306  Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request;
307 
308  // add new helper kv-pair notes to the credentials object
309  // so that any transaction using those credentials can access them
310  static const NotePairs::Names appendables = { SBuf("group"), SBuf("nonce"), SBuf("tag") };
311  auth_user_request->user()->notes.replaceOrAddOrAppend(&reply.notes, appendables);
312  // remove any private credentials detail which got added.
313  auth_user_request->user()->notes.remove("ha1");
314 
315  static bool oldHelperWarningDone = false;
316  switch (reply.result) {
317  case Helper::Unknown: {
318  // Squid 3.3 and older the digest helper only returns a HA1 hash (no "OK")
319  // the HA1 will be found in content() for these responses.
320  if (!oldHelperWarningDone) {
321  debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format HA1 response. It needs to be upgraded.");
322  oldHelperWarningDone=true;
323  }
324 
325  /* allow this because the digest_request pointer is purely local */
326  Auth::Digest::User *digest_user = dynamic_cast<Auth::Digest::User *>(auth_user_request->user().getRaw());
327  assert(digest_user != nullptr);
328 
329  CvtBin(reply.other().content(), digest_user->HA1);
330  digest_user->HA1created = 1;
331  }
332  break;
333 
334  case Helper::Okay: {
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 != nullptr);
338 
339  if (const char *ha1Note = reply.notes.findFirst("ha1")) {
340  CvtBin(ha1Note, digest_user->HA1);
341  digest_user->HA1created = 1;
342  } else {
343  debugs(29, DBG_IMPORTANT, "ERROR: Digest auth helper did not produce a HA1. Using the wrong helper program? received: " << reply);
344  }
345  }
346  break;
347 
348  case Helper::TT:
349  debugs(29, DBG_IMPORTANT, "ERROR: Digest auth does not support the result code received. Using the wrong helper program? received: " << reply);
350  [[fallthrough]]; // to handle this as an ERR response
351 
352  case Helper::TimedOut:
354  [[fallthrough]]; // to (silently) handle this as an ERR response
355 
356  // TODO retry the broken lookup on another helper?
357  case Helper::Error: {
358  /* allow this because the digest_request pointer is purely local */
359  Auth::Digest::UserRequest *digest_request = dynamic_cast<Auth::Digest::UserRequest *>(auth_user_request.getRaw());
360  assert(digest_request);
361 
362  digest_request->user()->credentials(Auth::Failed);
363  digest_request->flags.invalid_password = true;
364 
365  SBuf msgNote;
366  if (reply.notes.find(msgNote, "message")) {
367  digest_request->setDenyMessage(msgNote.c_str());
368  } else if (reply.other().hasContent()) {
369  // old helpers did send ERR result but a bare message string instead of message= key name.
370  // TODO deprecate and remove old auth digest helper protocol
371  digest_request->setDenyMessage(reply.other().content());
372  if (!oldHelperWarningDone) {
373  debugs(29, DBG_IMPORTANT, "WARNING: Digest auth helper returned old format ERR response. It needs to be upgraded.");
374  oldHelperWarningDone=true;
375  }
376  }
377  }
378  break;
379  }
380 
381  void *cbdata = nullptr;
382  if (cbdataReferenceValidDone(replyData->data, &cbdata))
383  replyData->handler(cbdata);
384 
385  delete replyData;
386 }
387 
static Auth::Config * getConfig(char const *type_str)
Definition: testAuth.cc:69
@ scUnauthorized
Definition: StatusCode.h:46
#define DBG_CRITICAL
Definition: Stream.h:37
const char * findFirst(const char *noteKey) const
Definition: Notes.cc:302
#define cbdataReferenceValidDone(var, ptr)
Definition: cbdata.h:239
const char * getStr(Http::HdrType id) const
Definition: HttpHeader.cc:1163
@ Error
Definition: ResultCode.h:19
HttpHeader header
Definition: Message.h:74
@ Unknown
Definition: ResultCode.h:17
Helper::ClientPointer digestauthenticators
Definition: Config.cc:49
void * data
Definition: State.h:38
int authDigestNonceIsValid(digest_nonce_h *nonce, char nc[9])
Definition: Config.cc:327
AUTHCB * handler
Definition: State.h:40
bool hasContent() const
Definition: MemBuf.h:54
Definition: SBuf.h:93
static void authenticate(int socket_fd, const char *username, const char *passwd)
@ CRED_LOOKUP
Credentials need to be validated with the backend helper.
Definition: UserRequest.h:67
Definition: cbdata.cc:37
C * getRaw() const
Definition: RefCount.h:89
const SBuf & image() const
Http::StatusLine sline
Definition: HttpReply.h:56
UserRequest::Pointer auth_user_request
Definition: State.h:39
virtual User::Pointer user()
Definition: UserRequest.h:143
@ AUTHENTICATION_INFO
Direction
Definition: UserRequest.h:64
void httpHeaderPutStrf(HttpHeader *hdr, Http::HdrType id, const char *fmt,...)
std::vector< SBuf > Names
Definition: Notes.h:206
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 SchemeConfig * Find(const char *proxy_auth)
Definition: SchemeConfig.cc:59
const MemBuf & other() const
Definition: Reply.h:42
@ CRED_CHALLENGE
Client needs to be challenged. secure token.
Definition: UserRequest.h:65
Http::StatusCode status() const
retrieve the status code for this status line
Definition: StatusLine.h:45
void setNoAddr()
Definition: Address.cc:312
Helper::ResultCode result
The helper response 'result' field.
Definition: Reply.h:59
void authDigestUserLinkNonce(Auth::Digest::User *user, digest_nonce_h *nonce)
Definition: Config.cc:652
@ CRED_ERROR
ERROR in the auth module. Cannot determine the state of this request.
Definition: UserRequest.h:68
NotePairs notes
Definition: Reply.h:62
digest_nonce_h * authenticateDigestNonceNew(void)
Definition: Config.cc:122
bool find(SBuf &resultNote, const char *noteKey, const char *sep=",") const
Definition: Notes.cc:281
#define safe_free(x)
Definition: xalloc.h:73
void CvtBin(const HASHHEX Hex, HASH Bin)
Definition: rfc2617.c:49
#define assert(EX)
Definition: assert.h:17
@ Okay
Definition: ResultCode.h:18
void helperSubmit(const Helper::Client::Pointer &hlp, const char *const buf, HLPCB *const callback, void *const data)
Definition: helper.cc:480
const char * c_str()
Definition: SBuf.cc:516
void authDigestNoncePurge(digest_nonce_h *nonce)
Definition: Config.cc:428
char HASHHEX[HASHHEXLEN+1]
Definition: rfc2617.h:33
@ AUTH_DIGEST
Definition: Type.h:21
int authDigestNonceLastRequest(digest_nonce_h *nonce)
Definition: Config.cc:408
HttpRequestMethod method
Definition: HttpRequest.h:114
@ CRED_VALID
Credentials are valid and a up to date. The OK/Failed state is accurate.
Definition: UserRequest.h:66
char * content()
start of the added data
Definition: MemBuf.h:41
static char credentials[MAX_USERNAME_LEN+MAX_DOMAIN_LEN+2]
@ PROXY_AUTHENTICATION_INFO
#define DBG_IMPORTANT
Definition: Stream.h:38
@ scProxyAuthenticationRequired
Definition: StatusCode.h:52
void authDigestNonceUnlink(digest_nonce_h *nonce)
Definition: Config.cc:281
@ BrokenHelper
Definition: ResultCode.h:20
const char * authenticateDigestNonceNonceHex(const digest_nonce_h *nonce)
Definition: Config.cc:298
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 AUTHCB(void *)
Definition: UserRequest.h:57
@ METHOD_GET
Definition: MethodType.h:25
Ip::Address client_addr
Definition: HttpRequest.h:149
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
@ TimedOut
Definition: ResultCode.h:21
class SquidConfig Config
Definition: SquidConfig.cc:12

 

Introduction

Documentation

Support

Miscellaneous