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