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