2 * Copyright (C) 1996-2023 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 16 Cache Manager Objects */
12 #include "AccessLogEntry.h"
13 #include "base/TextException.h"
14 #include "CacheManager.h"
15 #include "comm/Connection.h"
16 #include "debug/Stream.h"
17 #include "error/ExceptionErrorDetail.h"
18 #include "errorpage.h"
20 #include "HttpHdrCc.h"
21 #include "HttpReply.h"
22 #include "HttpRequest.h"
23 #include "mgr/Action.h"
24 #include "mgr/ActionCreator.h"
25 #include "mgr/ActionPasswordList.h"
26 #include "mgr/ActionProfile.h"
27 #include "mgr/BasicActions.h"
28 #include "mgr/Command.h"
29 #include "mgr/Forwarder.h"
30 #include "mgr/FunAction.h"
31 #include "mgr/QueryParams.h"
32 #include "parser/Tokenizer.h"
34 #include "sbuf/Stream.h"
35 #include "sbuf/StringConvert.h"
36 #include "SquidConfig.h"
44 /// \ingroup CacheManagerInternal
45 #define MGR_PASSWD_SZ 128
47 /// creates Action using supplied Action::Create method and command
48 class ClassActionCreator
: public Mgr::ActionCreator
51 typedef Mgr::Action::Pointer
Handler(const Mgr::Command::Pointer
&cmd
);
54 ClassActionCreator(Handler
*aHandler
): handler(aHandler
) {}
56 Mgr::Action::Pointer
create(const Mgr::Command::Pointer
&cmd
) const override
{
64 /// Registers new profiles, ignoring attempts to register a duplicate
66 CacheManager::registerProfile(const Mgr::ActionProfile::Pointer
&profile
)
68 Must(profile
!= nullptr);
69 if (!CacheManager::findAction(profile
->name
)) {
70 menu_
.push_back(profile
);
71 debugs(16, 3, "registered profile: " << *profile
);
73 debugs(16, 2, "skipped duplicate profile: " << *profile
);
78 \ingroup CacheManagerAPI
79 * Registers a C-style action, which is implemented as a pointer to a function
80 * taking as argument a pointer to a StoreEntry and returning void.
81 * Implemented via CacheManagerActionLegacy.
84 CacheManager::registerProfile(char const * action
, char const * desc
, OBJH
* handler
, int pw_req_flag
, int atomic
)
86 debugs(16, 3, "registering legacy " << action
);
87 const Mgr::ActionProfile::Pointer profile
= new Mgr::ActionProfile(action
,
88 desc
, pw_req_flag
, atomic
, new Mgr::FunActionCreator(handler
));
89 registerProfile(profile
);
93 * \ingroup CacheManagerAPI
94 * Registers a C++-style action, via a pointer to a subclass of
95 * a CacheManagerAction object, whose run() method will be invoked when
96 * CacheManager identifies that the user has requested the action.
99 CacheManager::registerProfile(char const * action
, char const * desc
,
100 ClassActionCreator::Handler
*handler
,
101 int pw_req_flag
, int atomic
)
103 const Mgr::ActionProfile::Pointer profile
= new Mgr::ActionProfile(action
,
104 desc
, pw_req_flag
, atomic
, new ClassActionCreator(handler
));
105 registerProfile(profile
);
109 \ingroup CacheManagerInternal
110 * Locates an action in the actions registry ActionsList.
111 \retval NULL if Action not found
112 \retval CacheManagerAction* if the action was found
114 Mgr::ActionProfile::Pointer
115 CacheManager::findAction(char const * action
) const
117 Must(action
!= nullptr);
118 Menu::const_iterator a
;
120 debugs(16, 5, "CacheManager::findAction: looking for action " << action
);
121 for (a
= menu_
.begin(); a
!= menu_
.end(); ++a
) {
122 if (0 == strcmp((*a
)->name
, action
)) {
123 debugs(16, 6, " found");
128 debugs(16, 6, "Action not found.");
129 return Mgr::ActionProfilePointer();
133 CacheManager::createNamedAction(const char *actionName
)
137 Mgr::Command::Pointer cmd
= new Mgr::Command
;
138 cmd
->profile
= findAction(actionName
);
139 cmd
->params
.actionName
= actionName
;
141 Must(cmd
->profile
!= nullptr);
142 return cmd
->profile
->creator
->create(cmd
);
146 CacheManager::createRequestedAction(const Mgr::ActionParams
¶ms
)
148 Mgr::Command::Pointer cmd
= new Mgr::Command
;
149 cmd
->params
= params
;
150 cmd
->profile
= findAction(params
.actionName
.termedBuf());
151 Must(cmd
->profile
!= nullptr);
152 return cmd
->profile
->creator
->create(cmd
);
156 CacheManager::WellKnownUrlPathPrefix()
158 static const SBuf
prefix("/squid-internal-mgr/");
163 * Parses the action requested by the user and checks via
164 * CacheManager::ActionProtection() that the item is accessible by the user.
168 * [ scheme "://" authority ] '/squid-internal-mgr' path-absolute [ "?" query ] [ "#" fragment ]
170 * see RFC 3986 for definitions of scheme, authority, path-absolute, query
172 * \returns Mgr::Command object with action to perform and parameters it might use
174 Mgr::Command::Pointer
175 CacheManager::ParseUrl(const AnyP::Uri
&uri
)
177 Parser::Tokenizer
tok(uri
.path());
179 Assure(tok
.skip(WellKnownUrlPathPrefix()));
181 Mgr::Command::Pointer cmd
= new Mgr::Command();
182 cmd
->params
.httpUri
= SBufToString(uri
.absolute());
184 static const auto fieldChars
= CharacterSet("mgr-field", "?#").complement();
187 if (!tok
.prefix(action
, fieldChars
)) {
188 static const SBuf
indexReport("index");
189 action
= indexReport
;
191 cmd
->params
.actionName
= SBufToString(action
);
193 const auto profile
= findAction(action
.c_str());
195 throw TextException(ToSBuf("action '", action
, "' not found"), Here());
197 const char *prot
= ActionProtection(profile
);
198 if (!strcmp(prot
, "disabled") || !strcmp(prot
, "hidden"))
199 throw TextException(ToSBuf("action '", action
, "' is ", prot
), Here());
200 cmd
->profile
= profile
;
202 // TODO: fix when AnyP::Uri::parse() separates path?query#fragment
205 params
= tok
.remaining();
206 Mgr::QueryParams::Parse(tok
, cmd
->params
.queryParams
);
209 if (!tok
.skip('#') && !tok
.atEnd())
210 throw TextException("invalid characters in URL", Here());
211 // else ignore #fragment (if any)
213 debugs(16, 3, "MGR request: host=" << uri
.host() << ", action=" << action
<< ", params=" << params
);
218 /// \ingroup CacheManagerInternal
220 \ingroup CacheManagerInternal
221 * Decodes the headers needed to perform user authentication and fills
222 * the details into the cachemgrStateData argument
225 CacheManager::ParseHeaders(const HttpRequest
* request
, Mgr::ActionParams
¶ms
)
229 params
.httpMethod
= request
->method
.id();
230 params
.httpFlags
= request
->flags
;
232 #if HAVE_AUTH_MODULE_BASIC
233 // TODO: use the authentication system decode to retrieve these details properly.
235 /* base 64 _decoded_ user:passwd pair */
236 const auto basic_cookie(request
->header
.getAuthToken(Http::HdrType::AUTHORIZATION
, "Basic"));
238 if (basic_cookie
.isEmpty())
241 const auto colonPos
= basic_cookie
.find(':');
242 if (colonPos
== SBuf::npos
) {
243 debugs(16, DBG_IMPORTANT
, "ERROR: CacheManager::ParseHeaders: unknown basic_cookie format '" << basic_cookie
<< "'");
247 /* found user:password pair, reset old values */
248 params
.userName
= SBufToString(basic_cookie
.substr(0, colonPos
));
249 params
.password
= SBufToString(basic_cookie
.substr(colonPos
+1));
251 /* warning: this prints decoded password which maybe not be what you want to do @?@ @?@ */
252 debugs(16, 9, "CacheManager::ParseHeaders: got user: '" <<
253 params
.userName
<< "' passwd: '" << params
.password
<< "'");
258 \ingroup CacheManagerInternal
260 \retval 0 if mgr->password is good or "none"
261 \retval 1 if mgr->password is "disable"
262 \retval !0 if mgr->password does not match configured password
265 CacheManager::CheckPassword(const Mgr::Command
&cmd
)
267 assert(cmd
.profile
!= nullptr);
268 const char *action
= cmd
.profile
->name
;
269 char *pwd
= PasswdGet(Config
.passwd_list
, action
);
271 debugs(16, 4, "CacheManager::CheckPassword for action " << action
);
274 return cmd
.profile
->isPwReq
;
276 if (strcmp(pwd
, "disable") == 0)
279 if (strcmp(pwd
, "none") == 0)
282 if (!cmd
.params
.password
.size())
285 return cmd
.params
.password
!= pwd
;
289 \ingroup CacheManagerAPI
290 * Main entry point in the Cache Manager's activity. Gets called as part
291 * of the forward chain if the right URL is detected there. Initiates
292 * all needed internal work and renders the response.
295 CacheManager::start(const Comm::ConnectionPointer
&client
, HttpRequest
*request
, StoreEntry
*entry
, const AccessLogEntry::Pointer
&ale
)
297 debugs(16, 3, "request-url= '" << request
->url
<< "', entry-url='" << entry
->url() << "'");
299 Mgr::Command::Pointer cmd
;
301 cmd
= ParseUrl(request
->url
);
304 debugs(16, 2, "request URL error: " << CurrentException
);
305 const auto err
= new ErrorState(ERR_INVALID_URL
, Http::scNotFound
, request
, ale
);
306 err
->url
= xstrdup(entry
->url());
307 err
->detailError(new ExceptionErrorDetail(Here().id()));
308 errorAppendEntry(entry
, err
);
312 const char *actionName
= cmd
->profile
->name
;
314 entry
->expires
= squid_curtime
;
316 debugs(16, 5, "CacheManager: " << client
<< " requesting '" << actionName
<< "'");
318 /* get additional info from request headers */
319 ParseHeaders(request
, cmd
->params
);
321 const char *userName
= cmd
->params
.userName
.size() ?
322 cmd
->params
.userName
.termedBuf() : "unknown";
326 if (CheckPassword(*cmd
) != 0) {
327 /* build error message */
328 ErrorState
errState(ERR_CACHE_MGR_ACCESS_DENIED
, Http::scUnauthorized
, request
, ale
);
329 /* warn if user specified incorrect password */
331 if (cmd
->params
.password
.size()) {
332 debugs(16, DBG_IMPORTANT
, "CacheManager: " <<
334 client
<< ": incorrect password for '" <<
337 debugs(16, DBG_IMPORTANT
, "CacheManager: " <<
339 client
<< ": password needed for '" <<
343 HttpReply
*rep
= errState
.BuildHttpReply();
345 #if HAVE_AUTH_MODULE_BASIC
347 * add Authenticate header using action name as a realm because
348 * password depends on the action
350 rep
->header
.putAuth("Basic", actionName
);
353 const auto originOrNil
= request
->header
.getStr(Http::HdrType::ORIGIN
);
354 PutCommonResponseHeaders(*rep
, originOrNil
);
356 /* store the reply */
357 entry
->replaceHttpReply(rep
);
359 entry
->expires
= squid_curtime
;
366 if (request
->header
.has(Http::HdrType::ORIGIN
)) {
367 cmd
->params
.httpOrigin
= request
->header
.getStr(Http::HdrType::ORIGIN
);
370 debugs(16, 2, "CacheManager: " <<
372 client
<< " requesting '" <<
375 // special case: an index page
376 if (!strcmp(cmd
->profile
->name
, "index")) {
377 ErrorState
err(MGR_INDEX
, Http::scOkay
, request
, ale
);
378 err
.url
= xstrdup(entry
->url());
379 HttpReply
*rep
= err
.BuildHttpReply();
380 if (strncmp(rep
->body
.content(),"Internal Error:", 15) == 0)
381 rep
->sline
.set(Http::ProtocolVersion(1,1), Http::scNotFound
);
383 const auto originOrNil
= request
->header
.getStr(Http::HdrType::ORIGIN
);
384 PutCommonResponseHeaders(*rep
, originOrNil
);
386 entry
->replaceHttpReply(rep
);
391 if (UsingSmp() && IamWorkerProcess()) {
392 // is client the right connection to pass here?
393 AsyncJob::Start(new Mgr::Forwarder(client
, cmd
->params
, request
, entry
, ale
));
397 Mgr::Action::Pointer action
= cmd
->profile
->creator
->create(cmd
);
398 Must(action
!= nullptr);
399 action
->run(entry
, true);
403 \ingroup CacheManagerInternal
404 * Renders the protection level text for an action.
405 * Also doubles as a check for the protection level.
408 CacheManager::ActionProtection(const Mgr::ActionProfile::Pointer
&profile
)
410 assert(profile
!= nullptr);
411 const char *pwd
= PasswdGet(Config
.passwd_list
, profile
->name
);
414 return profile
->isPwReq
? "hidden" : "public";
416 if (!strcmp(pwd
, "disable"))
419 if (strcmp(pwd
, "none") == 0)
426 * \ingroup CacheManagerInternal
427 * gets from the global Config the password the user would need to supply
428 * for the action she queried
431 CacheManager::PasswdGet(Mgr::ActionPasswordList
* a
, const char *action
)
434 for (auto &w
: a
->actions
) {
435 if (w
.cmp(action
) == 0)
438 static const SBuf
allAction("all");
450 CacheManager::PutCommonResponseHeaders(HttpReply
&response
, const char *httpOrigin
)
452 // Allow cachemgr and other XHR scripts access to our version string
454 response
.header
.putExt("Access-Control-Allow-Origin", httpOrigin
);
455 #if HAVE_AUTH_MODULE_BASIC
456 response
.header
.putExt("Access-Control-Allow-Credentials", "true");
458 response
.header
.putExt("Access-Control-Expose-Headers", "Server");
462 // this is honored by more caches but allows pointless revalidation;
463 // revalidation will always fail because we do not support it (yet?)
464 cc
.noCache(String());
465 // this is honored by fewer caches but prohibits pointless revalidation
471 CacheManager::GetInstance()
473 static CacheManager
*instance
= nullptr;
475 debugs(16, 6, "starting cachemanager up");
476 instance
= new CacheManager
;
477 Mgr::RegisterBasics();