/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /* DEBUG: section 29 Authenticator */ /* The functions in this file handle authentication. * They DO NOT perform access control or auditing. * See acl.c for access control and client_side.c for auditing */ #include "squid.h" #include "acl/FilledChecklist.h" #include "auth/Config.h" #include "client_side.h" #include "comm/Connection.h" #include "fatal.h" #include "format/Format.h" #include "helper.h" #include "helper/Reply.h" #include "http/Stream.h" #include "HttpReply.h" #include "HttpRequest.h" #include "MemBuf.h" /* Generic Functions */ char const * Auth::UserRequest::username() const { if (user() != nullptr) return user()->username(); else return nullptr; } /**** PUBLIC FUNCTIONS (ALL GENERIC!) ****/ /* send the initial data to an authenticator module */ void Auth::UserRequest::start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) { assert(handler); assert(data); debugs(29, 9, this); startHelperLookup(request, al, handler, data); } bool Auth::UserRequest::valid() const { debugs(29, 9, "Validating Auth::UserRequest '" << this << "'."); if (user() == nullptr) { debugs(29, 4, "No associated Auth::User data"); return false; } if (user()->auth_type == Auth::AUTH_UNKNOWN) { debugs(29, 4, "Auth::User '" << user() << "' uses unknown scheme."); return false; } if (user()->auth_type == Auth::AUTH_BROKEN) { debugs(29, 4, "Auth::User '" << user() << "' is broken for it's scheme."); return false; } /* any other sanity checks that we need in the future */ /* finally return ok */ debugs(29, 5, "Validated. Auth::UserRequest '" << this << "'."); return true; } void * Auth::UserRequest::operator new (size_t) { fatal("Auth::UserRequest not directly allocatable\n"); return (void *)1; } void Auth::UserRequest::operator delete (void *) { fatal("Auth::UserRequest child failed to override operator delete\n"); } Auth::UserRequest::UserRequest(): _auth_user(nullptr), message(nullptr), lastReply(AUTH_ACL_CANNOT_AUTHENTICATE) { debugs(29, 5, "initialised request " << this); } Auth::UserRequest::~UserRequest() { assert(LockCount()==0); debugs(29, 5, "freeing request " << this); if (user() != nullptr) { /* release our references to the user credentials */ user(nullptr); } safe_free(message); } void Auth::UserRequest::setDenyMessage(char const *aString) { safe_free(message); message = xstrdup(aString); } char const * Auth::UserRequest::getDenyMessage() const { return message; } char const * Auth::UserRequest::denyMessage(char const * const default_message) const { if (getDenyMessage() == nullptr) return default_message; return getDenyMessage(); } bool Auth::UserRequest::authenticated() const { const auto u = user(); if (u && u->credentials() == Auth::Ok) { debugs(29, 7, "yes"); return true; } debugs(29, 7, "no"); return false; } static void authenticateAuthUserRequestSetIp(Auth::UserRequest::Pointer auth_user_request, Ip::Address &ipaddr) { Auth::User::Pointer auth_user = auth_user_request->user(); if (!auth_user) return; auth_user->addIp(ipaddr); } void authenticateAuthUserRequestRemoveIp(Auth::UserRequest::Pointer auth_user_request, Ip::Address const &ipaddr) { Auth::User::Pointer auth_user = auth_user_request->user(); if (!auth_user) return; auth_user->removeIp(ipaddr); } void authenticateAuthUserRequestClearIp(Auth::UserRequest::Pointer auth_user_request) { if (auth_user_request != nullptr) auth_user_request->user()->clearIp(); } int authenticateAuthUserRequestIPCount(Auth::UserRequest::Pointer auth_user_request) { assert(auth_user_request != nullptr); assert(auth_user_request->user() != nullptr); return auth_user_request->user()->ipcount; } /* * authenticateUserAuthenticated: is this auth_user structure logged in ? */ bool authenticateUserAuthenticated(const Auth::UserRequest::Pointer &auth_user_request) { if (!auth_user_request || !auth_user_request->valid()) return false; return auth_user_request->authenticated(); } Auth::Direction Auth::UserRequest::direction() { if (user() == nullptr) return Auth::CRED_ERROR; // No credentials. Should this be a CHALLENGE instead? if (authenticateUserAuthenticated(this)) return Auth::CRED_VALID; return module_direction(); } void Auth::UserRequest::addAuthenticationInfoHeader(HttpReply *, int) {} void Auth::UserRequest::addAuthenticationInfoTrailer(HttpReply *, int) {} void Auth::UserRequest::releaseAuthServer() {} const char * Auth::UserRequest::connLastHeader() { fatal("Auth::UserRequest::connLastHeader should always be overridden by conn based auth schemes"); return nullptr; } /* * authenticateAuthenticateUser: call the module specific code to * log this user request in. * Cache hits may change the auth_user pointer in the structure if needed. * This is basically a handle approach. */ static void authenticateAuthenticateUser(Auth::UserRequest::Pointer auth_user_request, HttpRequest * request, ConnStateData * conn, Http::HdrType type) { assert(auth_user_request.getRaw() != nullptr); auth_user_request->authenticate(request, conn, type); } static Auth::UserRequest::Pointer authTryGetUser(Auth::UserRequest::Pointer auth_user_request, ConnStateData * conn, HttpRequest * request) { Auth::UserRequest::Pointer res; if (auth_user_request != nullptr) res = auth_user_request; else if (request != nullptr && request->auth_user_request != nullptr) res = request->auth_user_request; else if (conn != nullptr) res = conn->getAuth(); // attach the credential notes from helper to the transaction if (request != nullptr && res != nullptr && res->user() != nullptr) { // XXX: we have no access to the transaction / AccessLogEntry so can't SyncNotes(). // workaround by using anything already set in HttpRequest // OR use new and rely on a later Sync copying these to AccessLogEntry UpdateRequestNotes(conn, *request, res->user()->notes); } return res; } /* returns one of * AUTH_ACL_CHALLENGE, * AUTH_ACL_HELPER, * AUTH_ACL_CANNOT_AUTHENTICATE, * AUTH_AUTHENTICATED * * How to use: In your proxy-auth dependent acl code, use the following * construct: * int rv; * if ((rv = AuthenticateAuthenticate()) != AUTH_AUTHENTICATED) * return rv; * * when this code is reached, the request/connection is authenticated. * * if you have non-acl code, but want to force authentication, you need a * callback mechanism like the acl testing routines that will send a 40[1|7] to * the client when rv==AUTH_ACL_CHALLENGE, and will communicate with * the authenticateStart routine for rv==AUTH_ACL_HELPER * * Caller is responsible for locking and unlocking their *auth_user_request! */ AuthAclState Auth::UserRequest::authenticate(Auth::UserRequest::Pointer * auth_user_request, Http::HdrType headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr, AccessLogEntry::Pointer &al) { const char *proxy_auth; assert(headertype != 0); proxy_auth = request->header.getStr(headertype); /* * a note on proxy_auth logix here: * proxy_auth==NULL -> unauthenticated request || already * authenticated connection so we test for an authenticated * connection when we receive no authentication header. */ /* a) can we find other credentials to use? and b) are they logged in already? */ if (proxy_auth == nullptr && !authenticateUserAuthenticated(authTryGetUser(*auth_user_request,conn,request))) { /* no header or authentication failed/got corrupted - restart */ debugs(29, 4, "No Proxy-Auth header and no working alternative. Requesting auth header."); /* something wrong with the AUTH credentials. Force a new attempt */ /* connection auth we must reset on auth errors */ if (conn != nullptr) { conn->setAuth(nullptr, "HTTP request missing credentials"); } *auth_user_request = nullptr; return AUTH_ACL_CHALLENGE; } /* * Is this an already authenticated connection with a new auth header? * No check for function required in the if: its compulsory for conn based * auth modules */ if (proxy_auth && conn != nullptr && conn->getAuth() != nullptr && authenticateUserAuthenticated(conn->getAuth()) && conn->getAuth()->connLastHeader() != nullptr && strcmp(proxy_auth, conn->getAuth()->connLastHeader())) { debugs(29, 2, "WARNING: DUPLICATE AUTH - authentication header on already authenticated connection!. AU " << conn->getAuth() << ", Current user '" << conn->getAuth()->username() << "' proxy_auth " << proxy_auth); /* remove this request struct - the link is already authed and it can't be to reauth. */ /* This should _only_ ever occur on the first pass through * authenticateAuthenticate */ assert(*auth_user_request == nullptr); conn->setAuth(nullptr, "changed credentials token"); } /* we have a proxy auth header and as far as we know this connection has * not had bungled connection oriented authentication happen on it. */ debugs(29, 9, "header " << (proxy_auth ? proxy_auth : "-") << "."); if (*auth_user_request == nullptr) { if (conn != nullptr) { debugs(29, 9, "This is a new checklist test on:" << conn->clientConnection); } if (proxy_auth && request->auth_user_request == nullptr && conn != nullptr && conn->getAuth() != nullptr) { Auth::SchemeConfig * scheme = Auth::SchemeConfig::Find(proxy_auth); if (conn->getAuth()->user() == nullptr || conn->getAuth()->user()->config != scheme) { debugs(29, DBG_IMPORTANT, "WARNING: Unexpected change of authentication scheme from '" << (conn->getAuth()->user()!=nullptr?conn->getAuth()->user()->config->type():"[no user]") << "' to '" << proxy_auth << "' (client " << src_addr << ")"); conn->setAuth(nullptr, "changed auth scheme"); } } if (request->auth_user_request == nullptr && (conn == nullptr || conn->getAuth() == nullptr)) { /* beginning of a new request check */ debugs(29, 4, "No connection authentication type"); *auth_user_request = Auth::SchemeConfig::CreateAuthUser(proxy_auth, al); if (*auth_user_request == nullptr) return AUTH_ACL_CHALLENGE; else if (!(*auth_user_request)->valid()) { /* the decode might have left a username for logging, or a message to * the user */ if ((*auth_user_request)->username()) { request->auth_user_request = *auth_user_request; } *auth_user_request = nullptr; return AUTH_ACL_CHALLENGE; } } else if (request->auth_user_request != nullptr) { *auth_user_request = request->auth_user_request; } else { assert (conn != nullptr); if (conn->getAuth() != nullptr) { *auth_user_request = conn->getAuth(); } else { /* failed connection based authentication */ debugs(29, 4, "Auth user request " << *auth_user_request << " conn-auth missing and failed to authenticate."); *auth_user_request = nullptr; return AUTH_ACL_CHALLENGE; } } } if (!authenticateUserAuthenticated(*auth_user_request)) { /* User not logged in. Try to log them in */ authenticateAuthenticateUser(*auth_user_request, request, conn, headertype); switch ((*auth_user_request)->direction()) { case Auth::CRED_CHALLENGE: if (request->auth_user_request == nullptr) { request->auth_user_request = *auth_user_request; } *auth_user_request = nullptr; return AUTH_ACL_CHALLENGE; case Auth::CRED_ERROR: /* this ACL check is finished. */ *auth_user_request = nullptr; return AUTH_ACL_CHALLENGE; case Auth::CRED_LOOKUP: /* we are partway through authentication within squid, * the *auth_user_request variables stores the auth_user_request * for the callback to here - Do not Unlock */ return AUTH_ACL_HELPER; case Auth::CRED_VALID: /* authentication is finished */ /* See if user authentication failed for some reason */ if (!authenticateUserAuthenticated(*auth_user_request)) { if ((*auth_user_request)->username()) { if (!request->auth_user_request) { request->auth_user_request = *auth_user_request; } } *auth_user_request = nullptr; return AUTH_ACL_CHALLENGE; } // otherwise fallthrough to acceptance. } } /* copy username to request for logging on client-side */ /* the credentials are correct at this point */ if (request->auth_user_request == nullptr) { request->auth_user_request = *auth_user_request; authenticateAuthUserRequestSetIp(*auth_user_request, src_addr); } return AUTH_AUTHENTICATED; } AuthAclState Auth::UserRequest::tryToAuthenticateAndSetAuthUser(Auth::UserRequest::Pointer * aUR, Http::HdrType headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr, AccessLogEntry::Pointer &al) { // If we have already been called, return the cached value Auth::UserRequest::Pointer t = authTryGetUser(*aUR, conn, request); if (t != nullptr && t->lastReply != AUTH_ACL_CANNOT_AUTHENTICATE && t->lastReply != AUTH_ACL_HELPER) { if (*aUR == nullptr) *aUR = t; if (request->auth_user_request == nullptr && t->lastReply == AUTH_AUTHENTICATED) { request->auth_user_request = t; } return t->lastReply; } // ok, call the actual authenticator routine. AuthAclState result = authenticate(aUR, headertype, request, conn, src_addr, al); // auth process may have changed the UserRequest we are dealing with t = authTryGetUser(*aUR, conn, request); if (t != nullptr && result != AUTH_ACL_CANNOT_AUTHENTICATE && result != AUTH_ACL_HELPER) t->lastReply = result; return result; } static Auth::ConfigVector & schemesConfig(HttpRequest *request, HttpReply *rep) { if (!Auth::TheConfig.schemeLists.empty() && Auth::TheConfig.schemeAccess) { ACLFilledChecklist ch(nullptr, request); ch.updateReply(rep); const auto &answer = ch.fastCheck(Auth::TheConfig.schemeAccess); if (answer.allowed()) return Auth::TheConfig.schemeLists.at(answer.kind).authConfigs; } return Auth::TheConfig.schemes; } void Auth::UserRequest::AddReplyAuthHeader(HttpReply * rep, Auth::UserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated, int internal) /* send the auth types we are configured to support (and have compiled in!) */ { Http::HdrType type; switch (rep->sline.status()) { case Http::scProxyAuthenticationRequired: /* Proxy authorisation needed */ type = Http::HdrType::PROXY_AUTHENTICATE; break; case Http::scUnauthorized: /* WWW Authorisation needed */ type = Http::HdrType::WWW_AUTHENTICATE; break; default: /* Keep GCC happy */ /* some other HTTP status */ type = Http::HdrType::BAD_HDR; break; } debugs(29, 9, "headertype:" << type << " authuser:" << auth_user_request); if (((rep->sline.status() == Http::scProxyAuthenticationRequired) || (rep->sline.status() == Http::scUnauthorized)) && internal) /* this is a authenticate-needed response */ { if (auth_user_request != nullptr && auth_user_request->direction() == Auth::CRED_CHALLENGE) /* add the scheme specific challenge header to the response */ auth_user_request->user()->config->fixHeader(auth_user_request, rep, type, request); else { /* call each configured & running auth scheme */ Auth::ConfigVector &configs = schemesConfig(request, rep); for (auto *scheme : configs) { if (scheme->active()) { if (auth_user_request != nullptr && auth_user_request->scheme()->type() == scheme->type()) scheme->fixHeader(auth_user_request, rep, type, request); else scheme->fixHeader(nullptr, rep, type, request); } else debugs(29, 4, "Configured scheme " << scheme->type() << " not Active"); } } } /* * allow protocol specific headers to be _added_ to the existing * response - currently Digest or Negotiate auth */ if (auth_user_request != nullptr) { auth_user_request->addAuthenticationInfoHeader(rep, accelerated); if (auth_user_request->lastReply != AUTH_AUTHENTICATED) auth_user_request->lastReply = AUTH_ACL_CANNOT_AUTHENTICATE; } } Auth::Scheme::Pointer Auth::UserRequest::scheme() const { return Auth::Scheme::Find(user()->config->type()); } const char * Auth::UserRequest::helperRequestKeyExtras(HttpRequest *request, AccessLogEntry::Pointer &al) { if (Format::Format *reqFmt = user()->config->keyExtras) { static MemBuf mb; mb.reset(); // We should pass AccessLogEntry as second argument .... Auth::UserRequest::Pointer oldReq = request->auth_user_request; request->auth_user_request = this; reqFmt->assemble(mb, al, 0); request->auth_user_request = oldReq; debugs(29, 5, "Assembled line to send :" << mb.content()); return mb.content(); } return nullptr; } void Auth::UserRequest::denyMessageFromHelper(const char *proto, const Helper::Reply &reply) { static SBuf messageNote; if (!reply.notes.find(messageNote, "message")) { messageNote.append(proto); messageNote.append(" Authentication denied with no reason given"); } setDenyMessage(messageNote.c_str()); }