]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/UserRequest.cc
3a20a67b5f89c8127256d651aa0faaa2515dbb2e
[thirdparty/squid.git] / src / auth / UserRequest.cc
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 /* DEBUG: section 29 Authenticator */
10
11 /* The functions in this file handle authentication.
12 * They DO NOT perform access control or auditing.
13 * See acl.c for access control and client_side.c for auditing */
14
15 #include "squid.h"
16 #include "acl/FilledChecklist.h"
17 #include "auth/Config.h"
18 #include "client_side.h"
19 #include "comm/Connection.h"
20 #include "fatal.h"
21 #include "format/Format.h"
22 #include "helper.h"
23 #include "helper/Reply.h"
24 #include "http/Stream.h"
25 #include "HttpReply.h"
26 #include "HttpRequest.h"
27 #include "MemBuf.h"
28
29 /* Generic Functions */
30
31 char const *
32 Auth::UserRequest::username() const
33 {
34 if (user() != nullptr)
35 return user()->username();
36 else
37 return nullptr;
38 }
39
40 /**** PUBLIC FUNCTIONS (ALL GENERIC!) ****/
41
42 /* send the initial data to an authenticator module */
43 void
44 Auth::UserRequest::start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data)
45 {
46 assert(handler);
47 assert(data);
48 debugs(29, 9, this);
49 startHelperLookup(request, al, handler, data);
50 }
51
52 bool
53 Auth::UserRequest::valid() const
54 {
55 debugs(29, 9, "Validating Auth::UserRequest '" << this << "'.");
56
57 if (user() == nullptr) {
58 debugs(29, 4, "No associated Auth::User data");
59 return false;
60 }
61
62 if (user()->auth_type == Auth::AUTH_UNKNOWN) {
63 debugs(29, 4, "Auth::User '" << user() << "' uses unknown scheme.");
64 return false;
65 }
66
67 if (user()->auth_type == Auth::AUTH_BROKEN) {
68 debugs(29, 4, "Auth::User '" << user() << "' is broken for it's scheme.");
69 return false;
70 }
71
72 /* any other sanity checks that we need in the future */
73
74 /* finally return ok */
75 debugs(29, 5, "Validated. Auth::UserRequest '" << this << "'.");
76 return true;
77 }
78
79 void *
80 Auth::UserRequest::operator new (size_t)
81 {
82 fatal("Auth::UserRequest not directly allocatable\n");
83 return (void *)1;
84 }
85
86 void
87 Auth::UserRequest::operator delete (void *)
88 {
89 fatal("Auth::UserRequest child failed to override operator delete\n");
90 }
91
92 Auth::UserRequest::UserRequest():
93 _auth_user(nullptr),
94 message(nullptr),
95 lastReply(AUTH_ACL_CANNOT_AUTHENTICATE)
96 {
97 debugs(29, 5, "initialised request " << this);
98 }
99
100 Auth::UserRequest::~UserRequest()
101 {
102 assert(LockCount()==0);
103 debugs(29, 5, "freeing request " << this);
104
105 if (user() != nullptr) {
106 /* release our references to the user credentials */
107 user(nullptr);
108 }
109
110 safe_free(message);
111 }
112
113 void
114 Auth::UserRequest::setDenyMessage(char const *aString)
115 {
116 safe_free(message);
117 message = xstrdup(aString);
118 }
119
120 char const *
121 Auth::UserRequest::getDenyMessage() const
122 {
123 return message;
124 }
125
126 char const *
127 Auth::UserRequest::denyMessage(char const * const default_message) const
128 {
129 if (getDenyMessage() == nullptr)
130 return default_message;
131
132 return getDenyMessage();
133 }
134
135 bool
136 Auth::UserRequest::authenticated() const
137 {
138 const auto u = user();
139 if (u && u->credentials() == Auth::Ok) {
140 debugs(29, 7, "yes");
141 return true;
142 }
143
144 debugs(29, 7, "no");
145 return false;
146 }
147
148 static void
149 authenticateAuthUserRequestSetIp(Auth::UserRequest::Pointer auth_user_request, Ip::Address &ipaddr)
150 {
151 Auth::User::Pointer auth_user = auth_user_request->user();
152
153 if (!auth_user)
154 return;
155
156 auth_user->addIp(ipaddr);
157 }
158
159 void
160 authenticateAuthUserRequestRemoveIp(Auth::UserRequest::Pointer auth_user_request, Ip::Address const &ipaddr)
161 {
162 Auth::User::Pointer auth_user = auth_user_request->user();
163
164 if (!auth_user)
165 return;
166
167 auth_user->removeIp(ipaddr);
168 }
169
170 void
171 authenticateAuthUserRequestClearIp(Auth::UserRequest::Pointer auth_user_request)
172 {
173 if (auth_user_request != nullptr)
174 auth_user_request->user()->clearIp();
175 }
176
177 int
178 authenticateAuthUserRequestIPCount(Auth::UserRequest::Pointer auth_user_request)
179 {
180 assert(auth_user_request != nullptr);
181 assert(auth_user_request->user() != nullptr);
182 return auth_user_request->user()->ipcount;
183 }
184
185 /*
186 * authenticateUserAuthenticated: is this auth_user structure logged in ?
187 */
188 bool
189 authenticateUserAuthenticated(const Auth::UserRequest::Pointer &auth_user_request)
190 {
191 if (!auth_user_request || !auth_user_request->valid())
192 return false;
193
194 return auth_user_request->authenticated();
195 }
196
197 Auth::Direction
198 Auth::UserRequest::direction()
199 {
200 if (user() == nullptr)
201 return Auth::CRED_ERROR; // No credentials. Should this be a CHALLENGE instead?
202
203 if (authenticateUserAuthenticated(this))
204 return Auth::CRED_VALID;
205
206 return module_direction();
207 }
208
209 void
210 Auth::UserRequest::addAuthenticationInfoHeader(HttpReply *, int)
211 {}
212
213 void
214 Auth::UserRequest::addAuthenticationInfoTrailer(HttpReply *, int)
215 {}
216
217 void
218 Auth::UserRequest::releaseAuthServer()
219 {}
220
221 const char *
222 Auth::UserRequest::connLastHeader()
223 {
224 fatal("Auth::UserRequest::connLastHeader should always be overridden by conn based auth schemes");
225 return nullptr;
226 }
227
228 /*
229 * authenticateAuthenticateUser: call the module specific code to
230 * log this user request in.
231 * Cache hits may change the auth_user pointer in the structure if needed.
232 * This is basically a handle approach.
233 */
234 static void
235 authenticateAuthenticateUser(Auth::UserRequest::Pointer auth_user_request, HttpRequest * request, ConnStateData * conn, Http::HdrType type)
236 {
237 assert(auth_user_request.getRaw() != nullptr);
238
239 auth_user_request->authenticate(request, conn, type);
240 }
241
242 static Auth::UserRequest::Pointer
243 authTryGetUser(Auth::UserRequest::Pointer auth_user_request, ConnStateData * conn, HttpRequest * request)
244 {
245 Auth::UserRequest::Pointer res;
246
247 if (auth_user_request != nullptr)
248 res = auth_user_request;
249 else if (request != nullptr && request->auth_user_request != nullptr)
250 res = request->auth_user_request;
251 else if (conn != nullptr)
252 res = conn->getAuth();
253
254 // attach the credential notes from helper to the transaction
255 if (request != nullptr && res != nullptr && res->user() != nullptr) {
256 // XXX: we have no access to the transaction / AccessLogEntry so can't SyncNotes().
257 // workaround by using anything already set in HttpRequest
258 // OR use new and rely on a later Sync copying these to AccessLogEntry
259
260 UpdateRequestNotes(conn, *request, res->user()->notes);
261 }
262
263 return res;
264 }
265
266 /* returns one of
267 * AUTH_ACL_CHALLENGE,
268 * AUTH_ACL_HELPER,
269 * AUTH_ACL_CANNOT_AUTHENTICATE,
270 * AUTH_AUTHENTICATED
271 *
272 * How to use: In your proxy-auth dependent acl code, use the following
273 * construct:
274 * int rv;
275 * if ((rv = AuthenticateAuthenticate()) != AUTH_AUTHENTICATED)
276 * return rv;
277 *
278 * when this code is reached, the request/connection is authenticated.
279 *
280 * if you have non-acl code, but want to force authentication, you need a
281 * callback mechanism like the acl testing routines that will send a 40[1|7] to
282 * the client when rv==AUTH_ACL_CHALLENGE, and will communicate with
283 * the authenticateStart routine for rv==AUTH_ACL_HELPER
284 *
285 * Caller is responsible for locking and unlocking their *auth_user_request!
286 */
287 AuthAclState
288 Auth::UserRequest::authenticate(Auth::UserRequest::Pointer * auth_user_request, Http::HdrType headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr, AccessLogEntry::Pointer &al)
289 {
290 const char *proxy_auth;
291 assert(headertype != 0);
292
293 proxy_auth = request->header.getStr(headertype);
294
295 /*
296 * a note on proxy_auth logix here:
297 * proxy_auth==NULL -> unauthenticated request || already
298 * authenticated connection so we test for an authenticated
299 * connection when we receive no authentication header.
300 */
301
302 /* a) can we find other credentials to use? and b) are they logged in already? */
303 if (proxy_auth == nullptr && !authenticateUserAuthenticated(authTryGetUser(*auth_user_request,conn,request))) {
304 /* no header or authentication failed/got corrupted - restart */
305 debugs(29, 4, "No Proxy-Auth header and no working alternative. Requesting auth header.");
306
307 /* something wrong with the AUTH credentials. Force a new attempt */
308
309 /* connection auth we must reset on auth errors */
310 if (conn != nullptr) {
311 conn->setAuth(nullptr, "HTTP request missing credentials");
312 }
313
314 *auth_user_request = nullptr;
315 return AUTH_ACL_CHALLENGE;
316 }
317
318 /*
319 * Is this an already authenticated connection with a new auth header?
320 * No check for function required in the if: its compulsory for conn based
321 * auth modules
322 */
323 if (proxy_auth && conn != nullptr && conn->getAuth() != nullptr &&
324 authenticateUserAuthenticated(conn->getAuth()) &&
325 conn->getAuth()->connLastHeader() != nullptr &&
326 strcmp(proxy_auth, conn->getAuth()->connLastHeader())) {
327 debugs(29, 2, "WARNING: DUPLICATE AUTH - authentication header on already authenticated connection!. AU " <<
328 conn->getAuth() << ", Current user '" <<
329 conn->getAuth()->username() << "' proxy_auth " <<
330 proxy_auth);
331
332 /* remove this request struct - the link is already authed and it can't be to reauth. */
333
334 /* This should _only_ ever occur on the first pass through
335 * authenticateAuthenticate
336 */
337 assert(*auth_user_request == nullptr);
338 conn->setAuth(nullptr, "changed credentials token");
339 }
340
341 /* we have a proxy auth header and as far as we know this connection has
342 * not had bungled connection oriented authentication happen on it. */
343 debugs(29, 9, "header " << (proxy_auth ? proxy_auth : "-") << ".");
344
345 if (*auth_user_request == nullptr) {
346 if (conn != nullptr) {
347 debugs(29, 9, "This is a new checklist test on:" << conn->clientConnection);
348 }
349
350 if (proxy_auth && request->auth_user_request == nullptr && conn != nullptr && conn->getAuth() != nullptr) {
351 Auth::SchemeConfig * scheme = Auth::SchemeConfig::Find(proxy_auth);
352
353 if (conn->getAuth()->user() == nullptr || conn->getAuth()->user()->config != scheme) {
354 debugs(29, DBG_IMPORTANT, "WARNING: Unexpected change of authentication scheme from '" <<
355 (conn->getAuth()->user()!=nullptr?conn->getAuth()->user()->config->type():"[no user]") <<
356 "' to '" << proxy_auth << "' (client " <<
357 src_addr << ")");
358
359 conn->setAuth(nullptr, "changed auth scheme");
360 }
361 }
362
363 if (request->auth_user_request == nullptr && (conn == nullptr || conn->getAuth() == nullptr)) {
364 /* beginning of a new request check */
365 debugs(29, 4, "No connection authentication type");
366
367 *auth_user_request = Auth::SchemeConfig::CreateAuthUser(proxy_auth, al);
368 if (*auth_user_request == nullptr)
369 return AUTH_ACL_CHALLENGE;
370 else if (!(*auth_user_request)->valid()) {
371 /* the decode might have left a username for logging, or a message to
372 * the user */
373
374 if ((*auth_user_request)->username()) {
375 request->auth_user_request = *auth_user_request;
376 }
377
378 *auth_user_request = nullptr;
379 return AUTH_ACL_CHALLENGE;
380 }
381
382 } else if (request->auth_user_request != nullptr) {
383 *auth_user_request = request->auth_user_request;
384 } else {
385 assert (conn != nullptr);
386 if (conn->getAuth() != nullptr) {
387 *auth_user_request = conn->getAuth();
388 } else {
389 /* failed connection based authentication */
390 debugs(29, 4, "Auth user request " << *auth_user_request << " conn-auth missing and failed to authenticate.");
391 *auth_user_request = nullptr;
392 return AUTH_ACL_CHALLENGE;
393 }
394 }
395 }
396
397 if (!authenticateUserAuthenticated(*auth_user_request)) {
398 /* User not logged in. Try to log them in */
399 authenticateAuthenticateUser(*auth_user_request, request, conn, headertype);
400
401 switch ((*auth_user_request)->direction()) {
402
403 case Auth::CRED_CHALLENGE:
404
405 if (request->auth_user_request == nullptr) {
406 request->auth_user_request = *auth_user_request;
407 }
408 *auth_user_request = nullptr;
409 return AUTH_ACL_CHALLENGE;
410
411 case Auth::CRED_ERROR:
412 /* this ACL check is finished. */
413 *auth_user_request = nullptr;
414 return AUTH_ACL_CHALLENGE;
415
416 case Auth::CRED_LOOKUP:
417 /* we are partway through authentication within squid,
418 * the *auth_user_request variables stores the auth_user_request
419 * for the callback to here - Do not Unlock */
420 return AUTH_ACL_HELPER;
421
422 case Auth::CRED_VALID:
423 /* authentication is finished */
424 /* See if user authentication failed for some reason */
425 if (!authenticateUserAuthenticated(*auth_user_request)) {
426 if ((*auth_user_request)->username()) {
427 if (!request->auth_user_request) {
428 request->auth_user_request = *auth_user_request;
429 }
430 }
431
432 *auth_user_request = nullptr;
433 return AUTH_ACL_CHALLENGE;
434 }
435 // otherwise fallthrough to acceptance.
436 }
437 }
438
439 /* copy username to request for logging on client-side */
440 /* the credentials are correct at this point */
441 if (request->auth_user_request == nullptr) {
442 request->auth_user_request = *auth_user_request;
443 authenticateAuthUserRequestSetIp(*auth_user_request, src_addr);
444 }
445
446 return AUTH_AUTHENTICATED;
447 }
448
449 AuthAclState
450 Auth::UserRequest::tryToAuthenticateAndSetAuthUser(Auth::UserRequest::Pointer * aUR, Http::HdrType headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr, AccessLogEntry::Pointer &al)
451 {
452 // If we have already been called, return the cached value
453 Auth::UserRequest::Pointer t = authTryGetUser(*aUR, conn, request);
454
455 if (t != nullptr && t->lastReply != AUTH_ACL_CANNOT_AUTHENTICATE && t->lastReply != AUTH_ACL_HELPER) {
456 if (*aUR == nullptr)
457 *aUR = t;
458
459 if (request->auth_user_request == nullptr && t->lastReply == AUTH_AUTHENTICATED) {
460 request->auth_user_request = t;
461 }
462 return t->lastReply;
463 }
464
465 // ok, call the actual authenticator routine.
466 AuthAclState result = authenticate(aUR, headertype, request, conn, src_addr, al);
467
468 // auth process may have changed the UserRequest we are dealing with
469 t = authTryGetUser(*aUR, conn, request);
470
471 if (t != nullptr && result != AUTH_ACL_CANNOT_AUTHENTICATE && result != AUTH_ACL_HELPER)
472 t->lastReply = result;
473
474 return result;
475 }
476
477 static Auth::ConfigVector &
478 schemesConfig(HttpRequest *request, HttpReply *rep)
479 {
480 if (!Auth::TheConfig.schemeLists.empty() && Auth::TheConfig.schemeAccess) {
481 ACLFilledChecklist ch(nullptr, request);
482 ch.updateReply(rep);
483 const auto &answer = ch.fastCheck(Auth::TheConfig.schemeAccess);
484 if (answer.allowed())
485 return Auth::TheConfig.schemeLists.at(answer.kind).authConfigs;
486 }
487 return Auth::TheConfig.schemes;
488 }
489
490 void
491 Auth::UserRequest::AddReplyAuthHeader(HttpReply * rep, Auth::UserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated, int internal)
492 /* send the auth types we are configured to support (and have compiled in!) */
493 {
494 Http::HdrType type;
495
496 switch (rep->sline.status()) {
497
498 case Http::scProxyAuthenticationRequired:
499 /* Proxy authorisation needed */
500 type = Http::HdrType::PROXY_AUTHENTICATE;
501 break;
502
503 case Http::scUnauthorized:
504 /* WWW Authorisation needed */
505 type = Http::HdrType::WWW_AUTHENTICATE;
506 break;
507
508 default:
509 /* Keep GCC happy */
510 /* some other HTTP status */
511 type = Http::HdrType::BAD_HDR;
512 break;
513 }
514
515 debugs(29, 9, "headertype:" << type << " authuser:" << auth_user_request);
516
517 if (((rep->sline.status() == Http::scProxyAuthenticationRequired)
518 || (rep->sline.status() == Http::scUnauthorized)) && internal)
519 /* this is a authenticate-needed response */
520 {
521
522 if (auth_user_request != nullptr && auth_user_request->direction() == Auth::CRED_CHALLENGE)
523 /* add the scheme specific challenge header to the response */
524 auth_user_request->user()->config->fixHeader(auth_user_request, rep, type, request);
525 else {
526 /* call each configured & running auth scheme */
527 Auth::ConfigVector &configs = schemesConfig(request, rep);
528 for (auto *scheme : configs) {
529 if (scheme->active()) {
530 if (auth_user_request != nullptr && auth_user_request->scheme()->type() == scheme->type())
531 scheme->fixHeader(auth_user_request, rep, type, request);
532 else
533 scheme->fixHeader(nullptr, rep, type, request);
534 } else
535 debugs(29, 4, "Configured scheme " << scheme->type() << " not Active");
536 }
537 }
538
539 }
540
541 /*
542 * allow protocol specific headers to be _added_ to the existing
543 * response - currently Digest or Negotiate auth
544 */
545 if (auth_user_request != nullptr) {
546 auth_user_request->addAuthenticationInfoHeader(rep, accelerated);
547 if (auth_user_request->lastReply != AUTH_AUTHENTICATED)
548 auth_user_request->lastReply = AUTH_ACL_CANNOT_AUTHENTICATE;
549 }
550 }
551
552 Auth::Scheme::Pointer
553 Auth::UserRequest::scheme() const
554 {
555 return Auth::Scheme::Find(user()->config->type());
556 }
557
558 const char *
559 Auth::UserRequest::helperRequestKeyExtras(HttpRequest *request, AccessLogEntry::Pointer &al)
560 {
561 if (Format::Format *reqFmt = user()->config->keyExtras) {
562 static MemBuf mb;
563 mb.reset();
564 // We should pass AccessLogEntry as second argument ....
565 Auth::UserRequest::Pointer oldReq = request->auth_user_request;
566 request->auth_user_request = this;
567 reqFmt->assemble(mb, al, 0);
568 request->auth_user_request = oldReq;
569 debugs(29, 5, "Assembled line to send :" << mb.content());
570 return mb.content();
571 }
572 return nullptr;
573 }
574
575 void
576 Auth::UserRequest::denyMessageFromHelper(const char *proto, const Helper::Reply &reply)
577 {
578 static SBuf messageNote;
579 if (!reply.notes.find(messageNote, "message")) {
580 messageNote.append(proto);
581 messageNote.append(" Authentication denied with no reason given");
582 }
583 setDenyMessage(messageNote.c_str());
584 }
585