UserRequest.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2025 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/CredentialsCache.h"
12 #include "auth/negotiate/Config.h"
13 #include "auth/negotiate/User.h"
15 #include "auth/State.h"
16 #include "auth/User.h"
17 #include "client_side.h"
18 #include "fatal.h"
19 #include "format/Format.h"
20 #include "globals.h"
21 #include "helper.h"
22 #include "helper/Reply.h"
23 #include "http/Stream.h"
24 #include "HttpReply.h"
25 #include "HttpRequest.h"
26 #include "MemBuf.h"
27 
28 Auth::Negotiate::UserRequest::UserRequest() :
29  server_blob(nullptr),
30  client_blob(nullptr),
31  waiting(0),
32  request(nullptr)
33 {}
34 
35 Auth::Negotiate::UserRequest::~UserRequest()
36 {
37  assert(LockCount()==0);
38  safe_free(server_blob);
39  safe_free(client_blob);
40 
41  releaseAuthServer();
42 
43  if (request) {
44  HTTPMSGUNLOCK(request);
45  request = nullptr;
46  }
47 }
48 
49 const char *
50 Auth::Negotiate::UserRequest::connLastHeader()
51 {
52  return nullptr;
53 }
54 
55 const char *
56 Auth::Negotiate::UserRequest::credentialsStr()
57 {
58  static char buf[MAX_AUTHTOKEN_LEN];
59  int printResult = 0;
60  if (user()->credentials() == Auth::Pending) {
61  printResult = snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here?
62  } else {
63  printResult = snprintf(buf, sizeof(buf), "KK %s\n", client_blob);
64  }
65 
66  // truncation is OK because we are used only for logging
67  if (printResult < 0) {
68  debugs(29, 2, "Can not build negotiate authentication credentials.");
69  buf[0] = '\0';
70  } else if (printResult >= (int)sizeof(buf))
71  debugs(29, 2, "Negotiate authentication credentials truncated.");
72 
73  return buf;
74 }
75 
77 Auth::Negotiate::UserRequest::module_direction()
78 {
79  /* null auth_user is checked for by Auth::UserRequest::direction() */
80 
81  if (waiting || client_blob)
82  return Auth::CRED_LOOKUP; /* need helper response to continue */
83 
84  if (user()->auth_type != Auth::AUTH_NEGOTIATE)
85  return Auth::CRED_ERROR;
86 
87  switch (user()->credentials()) {
88 
89  case Auth::Handshake:
90  assert(server_blob);
91  return Auth::CRED_CHALLENGE;
92 
93  case Auth::Ok:
94  return Auth::CRED_VALID;
95 
96  case Auth::Failed:
97  return Auth::CRED_ERROR; // XXX: really? not VALID or CHALLENGE?
98 
99  default:
100  debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication in unexpected state: " << user()->credentials());
101  return Auth::CRED_ERROR;
102  }
103 }
104 
105 void
106 Auth::Negotiate::UserRequest::startHelperLookup(HttpRequest *, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
107 {
108  static char buf[MAX_AUTHTOKEN_LEN];
109 
110  assert(data);
111  assert(handler);
112 
113  assert(user() != nullptr);
114  assert(user()->auth_type == Auth::AUTH_NEGOTIATE);
115 
116  if (static_cast<Auth::Negotiate::Config*>(Auth::SchemeConfig::Find("negotiate"))->authenticateProgram == nullptr) {
117  debugs(29, DBG_CRITICAL, "ERROR: No Negotiate authentication program configured.");
118  handler(data);
119  return;
120  }
121 
122  debugs(29, 8, "credentials state is '" << user()->credentials() << "'");
123 
124  const char *keyExtras = helperRequestKeyExtras(request, al);
125  int printResult = 0;
126  if (user()->credentials() == Auth::Pending) {
127  if (keyExtras)
128  printResult = snprintf(buf, sizeof(buf), "YR %s %s\n", client_blob, keyExtras);
129  else
130  printResult = snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here?
131  } else {
132  if (keyExtras)
133  printResult = snprintf(buf, sizeof(buf), "KK %s %s\n", client_blob, keyExtras);
134  else
135  printResult = snprintf(buf, sizeof(buf), "KK %s\n", client_blob);
136  }
137 
138  if (printResult < 0 || printResult >= (int)sizeof(buf)) {
139  if (printResult < 0)
140  debugs(29, DBG_CRITICAL, "ERROR: Can not build negotiate authentication helper request");
141  else
142  debugs(29, DBG_CRITICAL, "ERROR: Negotiate authentication helper request too big for the " << sizeof(buf) << "-byte buffer");
143  handler(data);
144  return;
145  }
146 
147  waiting = 1;
148 
149  safe_free(client_blob);
150 
151  helperStatefulSubmit(negotiateauthenticators, buf, Auth::Negotiate::UserRequest::HandleReply,
152  new Auth::StateData(this, handler, data), reservationId);
153 }
154 
159 void
160 Auth::Negotiate::UserRequest::releaseAuthServer()
161 {
162  if (reservationId) {
163  debugs(29, 6, reservationId);
165  reservationId.clear();
166  } else
167  debugs(29, 6, "No Negotiate auth server to release.");
168 }
169 
170 void
172 {
173  /* Check that we are in the client side, where we can generate
174  * auth challenges */
175 
176  if (conn == nullptr || !cbdataReferenceValid(conn)) {
177  user()->credentials(Auth::Failed);
178  debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication attempt to perform authentication without a connection!");
179  return;
180  }
181 
182  if (waiting) {
183  debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication waiting for helper reply!");
184  return;
185  }
186 
187  if (server_blob) {
188  debugs(29, 2, "need to challenge client '" << server_blob << "'!");
189  return;
190  }
191 
192  /* get header */
193  const char *proxy_auth = aRequest->header.getStr(type);
194 
195  /* locate second word */
196  const char *blob = proxy_auth;
197 
198  if (blob) {
199  while (xisspace(*blob) && *blob)
200  ++blob;
201 
202  while (!xisspace(*blob) && *blob)
203  ++blob;
204 
205  while (xisspace(*blob) && *blob)
206  ++blob;
207  }
208 
209  switch (user()->credentials()) {
210 
211  case Auth::Unchecked:
212  /* we've received a negotiate request. pass to a helper */
213  debugs(29, 9, "auth state negotiate none. Received blob: '" << proxy_auth << "'");
214  user()->credentials(Auth::Pending);
215  safe_free(client_blob);
216  client_blob=xstrdup(blob);
217  assert(conn->getAuth() == nullptr);
218  conn->setAuth(this, "new Negotiate handshake request");
219  request = aRequest;
220  HTTPMSGLOCK(request);
221  break;
222 
223  case Auth::Pending:
224  debugs(29, DBG_IMPORTANT, "need to ask helper");
225  break;
226 
227  case Auth::Handshake:
228  /* we should have received a blob from the client. Hand it off to
229  * some helper */
230  safe_free(client_blob);
231  client_blob = xstrdup(blob);
232  if (request)
233  HTTPMSGUNLOCK(request);
234  request = aRequest;
235  HTTPMSGLOCK(request);
236  break;
237 
238  case Auth::Ok:
239  fatal("Auth::Negotiate::UserRequest::authenticate: unexpected auth state DONE! Report a bug to the squid developers.\n");
240  break;
241 
242  case Auth::Failed:
243  /* we've failed somewhere in authentication */
244  debugs(29, 9, "auth state negotiate failed. " << proxy_auth);
245  break;
246  }
247 }
248 
249 void
250 Auth::Negotiate::UserRequest::HandleReply(void *data, const Helper::Reply &reply)
251 {
252  Auth::StateData *r = static_cast<Auth::StateData *>(data);
253 
254  debugs(29, 8, reply.reservationId << " got reply=" << reply);
255 
256  if (!cbdataReferenceValid(r->data)) {
257  debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication invalid callback data (" << reply.reservationId << ")");
258  delete r;
259  return;
260  }
261 
262  Auth::UserRequest::Pointer auth_user_request = r->auth_user_request;
263  assert(auth_user_request != nullptr);
264 
265  // add new helper kv-pair notes to the credentials object
266  // so that any transaction using those credentials can access them
267  static const NotePairs::Names appendables = { SBuf("group"), SBuf("tag") };
268  auth_user_request->user()->notes.replaceOrAddOrAppend(&reply.notes, appendables);
269  // remove any private credentials detail which got added.
270  auth_user_request->user()->notes.remove("token");
271 
272  Auth::Negotiate::UserRequest *lm_request = dynamic_cast<Auth::Negotiate::UserRequest *>(auth_user_request.getRaw());
273  assert(lm_request != nullptr);
274  assert(lm_request->waiting);
275 
276  lm_request->waiting = 0;
277  safe_free(lm_request->client_blob);
278 
279  assert(auth_user_request->user() != nullptr);
280  assert(auth_user_request->user()->auth_type == Auth::AUTH_NEGOTIATE);
281 
282  if (!lm_request->reservationId)
283  lm_request->reservationId = reply.reservationId;
284  else
285  assert(reply.reservationId == lm_request->reservationId);
286 
287  switch (reply.result) {
288  case Helper::TT:
289  /* we have been given a blob to send to the client */
290  safe_free(lm_request->server_blob);
291 
292  if (lm_request->request)
293  lm_request->request->flags.mustKeepalive = true;
294 
295  if (lm_request->request && lm_request->request->flags.proxyKeepalive) {
296  const char *tokenNote = reply.notes.findFirst("token");
297  lm_request->server_blob = xstrdup(tokenNote);
298  auth_user_request->user()->credentials(Auth::Handshake);
299  auth_user_request->setDenyMessage("Authentication in progress");
300  debugs(29, 4, "Need to challenge the client with a server token: '" << tokenNote << "'");
301  } else {
302  auth_user_request->user()->credentials(Auth::Failed);
303  auth_user_request->setDenyMessage("Negotiate authentication requires a persistent connection");
304  }
305  break;
306 
307  case Helper::Okay: {
308  const char *userNote = reply.notes.findFirst("user");
309  const char *tokenNote = reply.notes.findFirst("token");
310  if (userNote == nullptr || tokenNote == nullptr) {
311  // XXX: handle a success with no username better
312  /* protocol error */
313  fatalf("authenticateNegotiateHandleReply: *** Unsupported helper response ***, '%s'\n", reply.other().content());
314  break;
315  }
316 
317  /* we're finished, release the helper */
318  auth_user_request->user()->username(userNote);
319  auth_user_request->setDenyMessage("Login successful");
320  safe_free(lm_request->server_blob);
321  lm_request->server_blob = xstrdup(tokenNote);
322  lm_request->releaseAuthServer();
323 
324  /* connection is authenticated */
325  debugs(29, 4, "authenticated user " << auth_user_request->user()->username());
326  auto local_auth_user = lm_request->user();
327  auto cached_user = Auth::Negotiate::User::Cache()->lookup(auth_user_request->user()->userKey());
328  if (!cached_user) {
329  local_auth_user->addToNameCache();
330  } else {
331  /* we can't seamlessly recheck the username due to the
332  * challenge-response nature of the protocol.
333  * Just free the temporary auth_user after merging as
334  * much of it new state into the existing one as possible */
335  cached_user->absorb(local_auth_user);
336  /* from here on we are working with the original cached credentials. */
337  local_auth_user = cached_user;
338  auth_user_request->user(local_auth_user);
339  }
340  /* set these to now because this is either a new login from an
341  * existing user or a new user */
342  local_auth_user->expiretime = current_time.tv_sec;
343  auth_user_request->user()->credentials(Auth::Ok);
344  debugs(29, 4, "Successfully validated user via Negotiate. Username '" << auth_user_request->user()->username() << "'");
345  }
346  break;
347 
348  case Helper::Error:
349  /* authentication failure (wrong password, etc.) */
350  auth_user_request->denyMessageFromHelper("Negotiate", reply);
351  auth_user_request->user()->credentials(Auth::Failed);
352  safe_free(lm_request->server_blob);
353  if (const char *tokenNote = reply.notes.findFirst("token"))
354  lm_request->server_blob = xstrdup(tokenNote);
355  lm_request->releaseAuthServer();
356  debugs(29, 4, "Failed validating user via Negotiate. Result: " << reply);
357  break;
358 
359  case Helper::Unknown:
360  debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication Helper crashed (" << reply.reservationId << ")");
361  [[fallthrough]];
362 
363  case Helper::TimedOut:
365  /* TODO kick off a refresh process. This can occur after a YR or after
366  * a KK. If after a YR release the helper and resubmit the request via
367  * Authenticate Negotiate start.
368  * If after a KK deny the user's request w/ 407 and mark the helper as
369  * Needing YR. */
370  if (reply.result == Helper::Unknown)
371  auth_user_request->setDenyMessage("Internal Error");
372  else
373  auth_user_request->denyMessageFromHelper("Negotiate", reply);
374  auth_user_request->user()->credentials(Auth::Failed);
375  safe_free(lm_request->server_blob);
376  lm_request->releaseAuthServer();
377  debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication validating user. Result: " << reply);
378  break;
379  }
380 
381  if (lm_request->request) {
382  HTTPMSGUNLOCK(lm_request->request);
383  lm_request->request = nullptr;
384  }
385  r->handler(r->data);
386  delete r;
387 }
388 
void fatal(const char *message)
Definition: fatal.cc:28
#define DBG_CRITICAL
Definition: Stream.h:37
const char * findFirst(const char *noteKey) const
Definition: Notes.cc:307
const char * getStr(Http::HdrType id) const
Definition: HttpHeader.cc:1296
@ Error
Definition: ResultCode.h:19
void setAuth(const Auth::UserRequest::Pointer &aur, const char *cause)
Definition: client_side.cc:494
#define MAX_AUTHTOKEN_LEN
HttpHeader header
Definition: Message.h:74
@ Unknown
Definition: ResultCode.h:17
void * data
Definition: State.h:38
AUTHCB * handler
Definition: State.h:40
Definition: SBuf.h:93
@ AUTH_NEGOTIATE
Definition: Type.h:22
static void authenticate(int socket_fd, const char *username, const char *passwd)
#define xstrdup
@ CRED_LOOKUP
Credentials need to be validated with the backend helper.
Definition: UserRequest.h:67
C * getRaw() const
Definition: RefCount.h:89
int cbdataReferenceValid(const void *p)
Definition: cbdata.cc:270
void HTTPMSGUNLOCK(M *&a)
Definition: Message.h:150
UserRequest::Pointer auth_user_request
Definition: State.h:39
void setDenyMessage(char const *)
Definition: UserRequest.cc:115
virtual User::Pointer user()
Definition: UserRequest.h:143
Direction
Definition: UserRequest.h:64
std::vector< SBuf > Names
Definition: Notes.h:207
void helperStatefulSubmit(const statefulhelper::Pointer &hlp, const char *buf, HLPCB *callback, void *data, const Helper::ReservationId &reservation)
Definition: helper.cc:587
static SchemeConfig * Find(const char *proxy_auth)
Definition: SchemeConfig.cc:59
void cancelReservation(const Helper::ReservationId reservation)
undo reserveServer(), clear the reservation and kick the queue
Definition: helper.cc:618
struct timeval current_time
the current UNIX time in timeval {seconds, microseconds} format
Definition: gadgets.cc:18
const MemBuf & other() const
Definition: Reply.h:42
@ CRED_CHALLENGE
Client needs to be challenged. secure token.
Definition: UserRequest.h:65
Helper::ResultCode result
The helper response 'result' field.
Definition: Reply.h:59
@ CRED_ERROR
ERROR in the auth module. Cannot determine the state of this request.
Definition: UserRequest.h:68
struct _Cache Cache
NotePairs notes
Definition: Reply.h:62
#define safe_free(x)
Definition: xalloc.h:73
#define assert(EX)
Definition: assert.h:17
@ Okay
Definition: ResultCode.h:18
void fatalf(const char *fmt,...)
Definition: fatal.cc:68
void HTTPMSGLOCK(Http::Message *a)
Definition: Message.h:161
void denyMessageFromHelper(char const *proto, const Helper::Reply &reply)
Sets the reason of 'authentication denied' helper response.
Definition: UserRequest.cc:577
const Auth::UserRequest::Pointer & getAuth() const
Definition: client_side.h:123
@ CRED_VALID
Credentials are valid and a up to date. The OK/Failed state is accurate.
Definition: UserRequest.h:66
Helper::ReservationId reservationId
The stateful replies should include the reservation ID.
Definition: Reply.h:65
char * content()
start of the added data
Definition: MemBuf.h:41
#define DBG_IMPORTANT
Definition: Stream.h:38
@ BrokenHelper
Definition: ResultCode.h:20
Helper::StatefulClientPointer negotiateauthenticators
Definition: Config.cc:35
#define xisspace(x)
Definition: xis.h:15
void AUTHCB(void *)
Definition: UserRequest.h:57
#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