2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 29 Authenticator */
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 */
16 #include "acl/FilledChecklist.h"
17 #include "auth/Config.h"
18 #include "client_side.h"
19 #include "comm/Connection.h"
21 #include "format/Format.h"
23 #include "helper/Reply.h"
24 #include "http/Stream.h"
25 #include "HttpReply.h"
26 #include "HttpRequest.h"
29 /* Generic Functions */
32 Auth::UserRequest::username() const
34 if (user() != nullptr)
35 return user()->username();
40 /**** PUBLIC FUNCTIONS (ALL GENERIC!) ****/
42 /* send the initial data to an authenticator module */
44 Auth::UserRequest::start(HttpRequest
*request
, AccessLogEntry::Pointer
&al
, AUTHCB
* handler
, void *data
)
49 startHelperLookup(request
, al
, handler
, data
);
53 Auth::UserRequest::valid() const
55 debugs(29, 9, "Validating Auth::UserRequest '" << this << "'.");
57 if (user() == nullptr) {
58 debugs(29, 4, "No associated Auth::User data");
62 if (user()->auth_type
== Auth::AUTH_UNKNOWN
) {
63 debugs(29, 4, "Auth::User '" << user() << "' uses unknown scheme.");
67 if (user()->auth_type
== Auth::AUTH_BROKEN
) {
68 debugs(29, 4, "Auth::User '" << user() << "' is broken for it's scheme.");
72 /* any other sanity checks that we need in the future */
74 /* finally return ok */
75 debugs(29, 5, "Validated. Auth::UserRequest '" << this << "'.");
80 Auth::UserRequest::operator new (size_t)
82 fatal("Auth::UserRequest not directly allocatable\n");
87 Auth::UserRequest::operator delete (void *)
89 fatal("Auth::UserRequest child failed to override operator delete\n");
92 Auth::UserRequest::UserRequest():
95 lastReply(AUTH_ACL_CANNOT_AUTHENTICATE
)
97 debugs(29, 5, "initialised request " << this);
100 Auth::UserRequest::~UserRequest()
102 assert(LockCount()==0);
103 debugs(29, 5, "freeing request " << this);
105 if (user() != nullptr) {
106 /* release our references to the user credentials */
114 Auth::UserRequest::setDenyMessage(char const *aString
)
117 message
= xstrdup(aString
);
121 Auth::UserRequest::getDenyMessage() const
127 Auth::UserRequest::denyMessage(char const * const default_message
) const
129 if (getDenyMessage() == nullptr)
130 return default_message
;
132 return getDenyMessage();
136 Auth::UserRequest::authenticated() const
138 const auto u
= user();
139 if (u
&& u
->credentials() == Auth::Ok
) {
140 debugs(29, 7, "yes");
149 authenticateAuthUserRequestSetIp(Auth::UserRequest::Pointer auth_user_request
, Ip::Address
&ipaddr
)
151 Auth::User::Pointer auth_user
= auth_user_request
->user();
156 auth_user
->addIp(ipaddr
);
160 authenticateAuthUserRequestRemoveIp(Auth::UserRequest::Pointer auth_user_request
, Ip::Address
const &ipaddr
)
162 Auth::User::Pointer auth_user
= auth_user_request
->user();
167 auth_user
->removeIp(ipaddr
);
171 authenticateAuthUserRequestClearIp(Auth::UserRequest::Pointer auth_user_request
)
173 if (auth_user_request
!= nullptr)
174 auth_user_request
->user()->clearIp();
178 authenticateAuthUserRequestIPCount(Auth::UserRequest::Pointer auth_user_request
)
180 assert(auth_user_request
!= nullptr);
181 assert(auth_user_request
->user() != nullptr);
182 return auth_user_request
->user()->ipcount
;
186 * authenticateUserAuthenticated: is this auth_user structure logged in ?
189 authenticateUserAuthenticated(const Auth::UserRequest::Pointer
&auth_user_request
)
191 if (!auth_user_request
|| !auth_user_request
->valid())
194 return auth_user_request
->authenticated();
198 Auth::UserRequest::direction()
200 if (user() == nullptr)
201 return Auth::CRED_ERROR
; // No credentials. Should this be a CHALLENGE instead?
203 if (authenticateUserAuthenticated(this))
204 return Auth::CRED_VALID
;
206 return module_direction();
210 Auth::UserRequest::addAuthenticationInfoHeader(HttpReply
*, int)
214 Auth::UserRequest::addAuthenticationInfoTrailer(HttpReply
*, int)
218 Auth::UserRequest::releaseAuthServer()
222 Auth::UserRequest::connLastHeader()
224 fatal("Auth::UserRequest::connLastHeader should always be overridden by conn based auth schemes");
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.
235 authenticateAuthenticateUser(Auth::UserRequest::Pointer auth_user_request
, HttpRequest
* request
, ConnStateData
* conn
, Http::HdrType type
)
237 assert(auth_user_request
.getRaw() != nullptr);
239 auth_user_request
->authenticate(request
, conn
, type
);
242 static Auth::UserRequest::Pointer
243 authTryGetUser(Auth::UserRequest::Pointer auth_user_request
, ConnStateData
* conn
, HttpRequest
* request
)
245 Auth::UserRequest::Pointer res
;
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();
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
260 UpdateRequestNotes(conn
, *request
, res
->user()->notes
);
267 * AUTH_ACL_CHALLENGE,
269 * AUTH_ACL_CANNOT_AUTHENTICATE,
272 * How to use: In your proxy-auth dependent acl code, use the following
275 * if ((rv = AuthenticateAuthenticate()) != AUTH_AUTHENTICATED)
278 * when this code is reached, the request/connection is authenticated.
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
285 * Caller is responsible for locking and unlocking their *auth_user_request!
288 Auth::UserRequest::authenticate(Auth::UserRequest::Pointer
* auth_user_request
, Http::HdrType headertype
, HttpRequest
* request
, ConnStateData
* conn
, Ip::Address
&src_addr
, AccessLogEntry::Pointer
&al
)
290 const char *proxy_auth
;
291 assert(headertype
!= 0);
293 proxy_auth
= request
->header
.getStr(headertype
);
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.
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.");
307 /* something wrong with the AUTH credentials. Force a new attempt */
309 /* connection auth we must reset on auth errors */
310 if (conn
!= nullptr) {
311 conn
->setAuth(nullptr, "HTTP request missing credentials");
314 *auth_user_request
= nullptr;
315 return AUTH_ACL_CHALLENGE
;
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
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 " <<
332 /* remove this request struct - the link is already authed and it can't be to reauth. */
334 /* This should _only_ ever occur on the first pass through
335 * authenticateAuthenticate
337 assert(*auth_user_request
== nullptr);
338 conn
->setAuth(nullptr, "changed credentials token");
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
: "-") << ".");
345 if (*auth_user_request
== nullptr) {
346 if (conn
!= nullptr) {
347 debugs(29, 9, "This is a new checklist test on:" << conn
->clientConnection
);
350 if (proxy_auth
&& request
->auth_user_request
== nullptr && conn
!= nullptr && conn
->getAuth() != nullptr) {
351 Auth::SchemeConfig
* scheme
= Auth::SchemeConfig::Find(proxy_auth
);
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 " <<
359 conn
->setAuth(nullptr, "changed auth scheme");
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");
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
374 if ((*auth_user_request
)->username()) {
375 request
->auth_user_request
= *auth_user_request
;
378 *auth_user_request
= nullptr;
379 return AUTH_ACL_CHALLENGE
;
382 } else if (request
->auth_user_request
!= nullptr) {
383 *auth_user_request
= request
->auth_user_request
;
385 assert (conn
!= nullptr);
386 if (conn
->getAuth() != nullptr) {
387 *auth_user_request
= conn
->getAuth();
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
;
397 if (!authenticateUserAuthenticated(*auth_user_request
)) {
398 /* User not logged in. Try to log them in */
399 authenticateAuthenticateUser(*auth_user_request
, request
, conn
, headertype
);
401 switch ((*auth_user_request
)->direction()) {
403 case Auth::CRED_CHALLENGE
:
405 if (request
->auth_user_request
== nullptr) {
406 request
->auth_user_request
= *auth_user_request
;
408 *auth_user_request
= nullptr;
409 return AUTH_ACL_CHALLENGE
;
411 case Auth::CRED_ERROR
:
412 /* this ACL check is finished. */
413 *auth_user_request
= nullptr;
414 return AUTH_ACL_CHALLENGE
;
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
;
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
;
432 *auth_user_request
= nullptr;
433 return AUTH_ACL_CHALLENGE
;
435 // otherwise fallthrough to acceptance.
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
);
446 return AUTH_AUTHENTICATED
;
450 Auth::UserRequest::tryToAuthenticateAndSetAuthUser(Auth::UserRequest::Pointer
* aUR
, Http::HdrType headertype
, HttpRequest
* request
, ConnStateData
* conn
, Ip::Address
&src_addr
, AccessLogEntry::Pointer
&al
)
452 // If we have already been called, return the cached value
453 Auth::UserRequest::Pointer t
= authTryGetUser(*aUR
, conn
, request
);
455 if (t
!= nullptr && t
->lastReply
!= AUTH_ACL_CANNOT_AUTHENTICATE
&& t
->lastReply
!= AUTH_ACL_HELPER
) {
459 if (request
->auth_user_request
== nullptr && t
->lastReply
== AUTH_AUTHENTICATED
) {
460 request
->auth_user_request
= t
;
465 // ok, call the actual authenticator routine.
466 AuthAclState result
= authenticate(aUR
, headertype
, request
, conn
, src_addr
, al
);
468 // auth process may have changed the UserRequest we are dealing with
469 t
= authTryGetUser(*aUR
, conn
, request
);
471 if (t
!= nullptr && result
!= AUTH_ACL_CANNOT_AUTHENTICATE
&& result
!= AUTH_ACL_HELPER
)
472 t
->lastReply
= result
;
477 static Auth::ConfigVector
&
478 schemesConfig(HttpRequest
*request
, HttpReply
*rep
)
480 if (!Auth::TheConfig
.schemeLists
.empty() && Auth::TheConfig
.schemeAccess
) {
481 ACLFilledChecklist
ch(nullptr, request
);
483 const auto &answer
= ch
.fastCheck(Auth::TheConfig
.schemeAccess
);
484 if (answer
.allowed())
485 return Auth::TheConfig
.schemeLists
.at(answer
.kind
).authConfigs
;
487 return Auth::TheConfig
.schemes
;
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!) */
496 switch (rep
->sline
.status()) {
498 case Http::scProxyAuthenticationRequired
:
499 /* Proxy authorisation needed */
500 type
= Http::HdrType::PROXY_AUTHENTICATE
;
503 case Http::scUnauthorized
:
504 /* WWW Authorisation needed */
505 type
= Http::HdrType::WWW_AUTHENTICATE
;
510 /* some other HTTP status */
511 type
= Http::HdrType::BAD_HDR
;
515 debugs(29, 9, "headertype:" << type
<< " authuser:" << auth_user_request
);
517 if (((rep
->sline
.status() == Http::scProxyAuthenticationRequired
)
518 || (rep
->sline
.status() == Http::scUnauthorized
)) && internal
)
519 /* this is a authenticate-needed response */
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
);
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
);
533 scheme
->fixHeader(nullptr, rep
, type
, request
);
535 debugs(29, 4, "Configured scheme " << scheme
->type() << " not Active");
542 * allow protocol specific headers to be _added_ to the existing
543 * response - currently Digest or Negotiate auth
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
;
552 Auth::Scheme::Pointer
553 Auth::UserRequest::scheme() const
555 return Auth::Scheme::Find(user()->config
->type());
559 Auth::UserRequest::helperRequestKeyExtras(HttpRequest
*request
, AccessLogEntry::Pointer
&al
)
561 if (Format::Format
*reqFmt
= user()->config
->keyExtras
) {
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());
576 Auth::UserRequest::denyMessageFromHelper(const char *proto
, const Helper::Reply
&reply
)
578 static SBuf messageNote
;
579 if (!reply
.notes
.find(messageNote
, "message")) {
580 messageNote
.append(proto
);
581 messageNote
.append(" Authentication denied with no reason given");
583 setDenyMessage(messageNote
.c_str());