2 * Copyright (C) 1996-2020 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"
17 #include "errorpage.h"
19 #include "HttpReply.h"
20 #include "HttpRequest.h"
21 #include "mgr/Action.h"
22 #include "mgr/ActionCreator.h"
23 #include "mgr/ActionPasswordList.h"
24 #include "mgr/ActionProfile.h"
25 #include "mgr/BasicActions.h"
26 #include "mgr/Command.h"
27 #include "mgr/Forwarder.h"
28 #include "mgr/FunAction.h"
29 #include "mgr/QueryParams.h"
31 #include "sbuf/StringConvert.h"
32 #include "SquidConfig.h"
33 #include "SquidTime.h"
40 /// \ingroup CacheManagerInternal
41 #define MGR_PASSWD_SZ 128
43 /// creates Action using supplied Action::Create method and command
44 class ClassActionCreator
: public Mgr::ActionCreator
47 typedef Mgr::Action::Pointer
Handler(const Mgr::Command::Pointer
&cmd
);
50 ClassActionCreator(Handler
*aHandler
): handler(aHandler
) {}
52 virtual Mgr::Action::Pointer
create(const Mgr::Command::Pointer
&cmd
) const {
60 /// Registers new profiles, ignoring attempts to register a duplicate
62 CacheManager::registerProfile(const Mgr::ActionProfile::Pointer
&profile
)
64 Must(profile
!= NULL
);
65 if (!CacheManager::findAction(profile
->name
)) {
66 menu_
.push_back(profile
);
67 debugs(16, 3, HERE
<< "registered profile: " << *profile
);
69 debugs(16, 2, HERE
<< "skipped duplicate profile: " << *profile
);
74 \ingroup CacheManagerAPI
75 * Registers a C-style action, which is implemented as a pointer to a function
76 * taking as argument a pointer to a StoreEntry and returning void.
77 * Implemented via CacheManagerActionLegacy.
80 CacheManager::registerProfile(char const * action
, char const * desc
, OBJH
* handler
, int pw_req_flag
, int atomic
)
82 debugs(16, 3, HERE
<< "registering legacy " << action
);
83 const Mgr::ActionProfile::Pointer profile
= new Mgr::ActionProfile(action
,
84 desc
, pw_req_flag
, atomic
, new Mgr::FunActionCreator(handler
));
85 registerProfile(profile
);
89 * \ingroup CacheManagerAPI
90 * Registers a C++-style action, via a pointer to a subclass of
91 * a CacheManagerAction object, whose run() method will be invoked when
92 * CacheManager identifies that the user has requested the action.
95 CacheManager::registerProfile(char const * action
, char const * desc
,
96 ClassActionCreator::Handler
*handler
,
97 int pw_req_flag
, int atomic
)
99 const Mgr::ActionProfile::Pointer profile
= new Mgr::ActionProfile(action
,
100 desc
, pw_req_flag
, atomic
, new ClassActionCreator(handler
));
101 registerProfile(profile
);
105 \ingroup CacheManagerInternal
106 * Locates an action in the actions registry ActionsList.
107 \retval NULL if Action not found
108 \retval CacheManagerAction* if the action was found
110 Mgr::ActionProfile::Pointer
111 CacheManager::findAction(char const * action
) const
113 Must(action
!= NULL
);
114 Menu::const_iterator a
;
116 debugs(16, 5, "CacheManager::findAction: looking for action " << action
);
117 for (a
= menu_
.begin(); a
!= menu_
.end(); ++a
) {
118 if (0 == strcmp((*a
)->name
, action
)) {
119 debugs(16, 6, " found");
124 debugs(16, 6, "Action not found.");
125 return Mgr::ActionProfilePointer();
129 CacheManager::createNamedAction(const char *actionName
)
133 Mgr::Command::Pointer cmd
= new Mgr::Command
;
134 cmd
->profile
= findAction(actionName
);
135 cmd
->params
.actionName
= actionName
;
137 Must(cmd
->profile
!= NULL
);
138 return cmd
->profile
->creator
->create(cmd
);
142 CacheManager::createRequestedAction(const Mgr::ActionParams
¶ms
)
144 Mgr::Command::Pointer cmd
= new Mgr::Command
;
145 cmd
->params
= params
;
146 cmd
->profile
= findAction(params
.actionName
.termedBuf());
147 Must(cmd
->profile
!= NULL
);
148 return cmd
->profile
->creator
->create(cmd
);
152 \ingroup CacheManagerInternal
153 * define whether the URL is a cache-manager URL and parse the action
154 * requested by the user. Checks via CacheManager::ActionProtection() that the
155 * item is accessible by the user.
156 \retval CacheManager::cachemgrStateData state object for the following handling
157 \retval NULL if the action can't be found or can't be accessed by the user
159 Mgr::Command::Pointer
160 CacheManager::ParseUrl(const char *url
)
163 LOCAL_ARRAY(char, host
, MAX_URL
);
164 LOCAL_ARRAY(char, request
, MAX_URL
);
165 LOCAL_ARRAY(char, password
, MAX_URL
);
166 LOCAL_ARRAY(char, params
, MAX_URL
);
172 int len
= strlen(url
);
174 t
= sscanf(url
, "cache_object://%[^/]/%[^@?]%n@%[^?]?%s", host
, request
, &pos
, password
, params
);
176 t
= sscanf(url
, "cache_object://%[^/]/%[^?]%n?%s", host
, request
, &pos
, params
);
179 t
= sscanf(url
, "http://%[^/]/squid-internal-mgr/%[^?]%n?%s", host
, request
, &pos
, params
);
182 t
= sscanf(url
, "https://%[^/]/squid-internal-mgr/%[^?]%n?%s", host
, request
, &pos
, params
);
185 if (strncmp("cache_object://",url
,15)==0)
186 xstrncpy(request
, "menu", MAX_URL
);
188 xstrncpy(request
, "index", MAX_URL
);
192 if (t
== 2 && request
[0] == '\0') {
194 * emx's sscanf insists of returning 2 because it sets request
197 if (strncmp("cache_object://",url
,15)==0)
198 xstrncpy(request
, "menu", MAX_URL
);
200 xstrncpy(request
, "index", MAX_URL
);
204 debugs(16, 3, HERE
<< "MGR request: t=" << t
<< ", host='" << host
<< "', request='" << request
<< "', pos=" << pos
<<
205 ", password='" << password
<< "', params='" << params
<< "'");
207 Mgr::ActionProfile::Pointer profile
= findAction(request
);
209 debugs(16, DBG_IMPORTANT
, "CacheManager::ParseUrl: action '" << request
<< "' not found");
213 const char *prot
= ActionProtection(profile
);
214 if (!strcmp(prot
, "disabled") || !strcmp(prot
, "hidden")) {
215 debugs(16, DBG_IMPORTANT
, "CacheManager::ParseUrl: action '" << request
<< "' is " << prot
);
219 Mgr::Command::Pointer cmd
= new Mgr::Command
;
220 if (!Mgr::QueryParams::Parse(params
, cmd
->params
.queryParams
))
222 cmd
->profile
= profile
;
223 cmd
->params
.httpUri
= url
;
224 cmd
->params
.userName
= String();
225 cmd
->params
.password
= password
;
226 cmd
->params
.actionName
= request
;
230 /// \ingroup CacheManagerInternal
232 \ingroup CacheManagerInternal
233 * Decodes the headers needed to perform user authentication and fills
234 * the details into the cachemgrStateData argument
237 CacheManager::ParseHeaders(const HttpRequest
* request
, Mgr::ActionParams
¶ms
)
241 params
.httpMethod
= request
->method
.id();
242 params
.httpFlags
= request
->flags
;
244 #if HAVE_AUTH_MODULE_BASIC
245 // TODO: use the authentication system decode to retrieve these details properly.
247 /* base 64 _decoded_ user:passwd pair */
248 const auto basic_cookie(request
->header
.getAuthToken(Http::HdrType::AUTHORIZATION
, "Basic"));
250 if (basic_cookie
.isEmpty())
253 const auto colonPos
= basic_cookie
.find(':');
254 if (colonPos
== SBuf::npos
) {
255 debugs(16, DBG_IMPORTANT
, "CacheManager::ParseHeaders: unknown basic_cookie format '" << basic_cookie
<< "'");
259 /* found user:password pair, reset old values */
260 params
.userName
= SBufToString(basic_cookie
.substr(0, colonPos
));
261 params
.password
= SBufToString(basic_cookie
.substr(colonPos
+1));
263 /* warning: this prints decoded password which maybe not be what you want to do @?@ @?@ */
264 debugs(16, 9, "CacheManager::ParseHeaders: got user: '" <<
265 params
.userName
<< "' passwd: '" << params
.password
<< "'");
270 \ingroup CacheManagerInternal
272 \retval 0 if mgr->password is good or "none"
273 \retval 1 if mgr->password is "disable"
274 \retval !0 if mgr->password does not match configured password
277 CacheManager::CheckPassword(const Mgr::Command
&cmd
)
279 assert(cmd
.profile
!= NULL
);
280 const char *action
= cmd
.profile
->name
;
281 char *pwd
= PasswdGet(Config
.passwd_list
, action
);
283 debugs(16, 4, "CacheManager::CheckPassword for action " << action
);
286 return cmd
.profile
->isPwReq
;
288 if (strcmp(pwd
, "disable") == 0)
291 if (strcmp(pwd
, "none") == 0)
294 if (!cmd
.params
.password
.size())
297 return cmd
.params
.password
!= pwd
;
301 \ingroup CacheManagerAPI
302 * Main entry point in the Cache Manager's activity. Gets called as part
303 * of the forward chain if the right URL is detected there. Initiates
304 * all needed internal work and renders the response.
307 CacheManager::start(const Comm::ConnectionPointer
&client
, HttpRequest
*request
, StoreEntry
*entry
, const AccessLogEntry::Pointer
&ale
)
309 debugs(16, 3, "CacheManager::Start: '" << entry
->url() << "'" );
311 Mgr::Command::Pointer cmd
= ParseUrl(entry
->url());
313 const auto err
= new ErrorState(ERR_INVALID_URL
, Http::scNotFound
, request
, ale
);
314 err
->url
= xstrdup(entry
->url());
315 errorAppendEntry(entry
, err
);
316 entry
->expires
= squid_curtime
;
320 const char *actionName
= cmd
->profile
->name
;
322 entry
->expires
= squid_curtime
;
324 debugs(16, 5, "CacheManager: " << client
<< " requesting '" << actionName
<< "'");
326 /* get additional info from request headers */
327 ParseHeaders(request
, cmd
->params
);
329 const char *userName
= cmd
->params
.userName
.size() ?
330 cmd
->params
.userName
.termedBuf() : "unknown";
334 if (CheckPassword(*cmd
) != 0) {
335 /* build error message */
336 ErrorState
errState(ERR_CACHE_MGR_ACCESS_DENIED
, Http::scUnauthorized
, request
, ale
);
337 /* warn if user specified incorrect password */
339 if (cmd
->params
.password
.size()) {
340 debugs(16, DBG_IMPORTANT
, "CacheManager: " <<
342 client
<< ": incorrect password for '" <<
345 debugs(16, DBG_IMPORTANT
, "CacheManager: " <<
347 client
<< ": password needed for '" <<
351 HttpReply
*rep
= errState
.BuildHttpReply();
353 #if HAVE_AUTH_MODULE_BASIC
355 * add Authenticate header using action name as a realm because
356 * password depends on the action
358 rep
->header
.putAuth("Basic", actionName
);
360 // Allow cachemgr and other XHR scripts access to our version string
361 if (request
->header
.has(Http::HdrType::ORIGIN
)) {
362 rep
->header
.putExt("Access-Control-Allow-Origin",request
->header
.getStr(Http::HdrType::ORIGIN
));
363 #if HAVE_AUTH_MODULE_BASIC
364 rep
->header
.putExt("Access-Control-Allow-Credentials","true");
366 rep
->header
.putExt("Access-Control-Expose-Headers","Server");
369 /* store the reply */
370 entry
->replaceHttpReply(rep
);
372 entry
->expires
= squid_curtime
;
379 if (request
->header
.has(Http::HdrType::ORIGIN
)) {
380 cmd
->params
.httpOrigin
= request
->header
.getStr(Http::HdrType::ORIGIN
);
383 debugs(16, 2, "CacheManager: " <<
385 client
<< " requesting '" <<
388 // special case: /squid-internal-mgr/ index page
389 if (!strcmp(cmd
->profile
->name
, "index")) {
390 ErrorState
err(MGR_INDEX
, Http::scOkay
, request
, ale
);
391 err
.url
= xstrdup(entry
->url());
392 HttpReply
*rep
= err
.BuildHttpReply();
393 if (strncmp(rep
->body
.content(),"Internal Error:", 15) == 0)
394 rep
->sline
.set(Http::ProtocolVersion(1,1), Http::scNotFound
);
395 // Allow cachemgr and other XHR scripts access to our version string
396 if (request
->header
.has(Http::HdrType::ORIGIN
)) {
397 rep
->header
.putExt("Access-Control-Allow-Origin",request
->header
.getStr(Http::HdrType::ORIGIN
));
398 #if HAVE_AUTH_MODULE_BASIC
399 rep
->header
.putExt("Access-Control-Allow-Credentials","true");
401 rep
->header
.putExt("Access-Control-Expose-Headers","Server");
403 entry
->replaceHttpReply(rep
);
408 if (UsingSmp() && IamWorkerProcess()) {
409 // is client the right connection to pass here?
410 AsyncJob::Start(new Mgr::Forwarder(client
, cmd
->params
, request
, entry
, ale
));
414 Mgr::Action::Pointer action
= cmd
->profile
->creator
->create(cmd
);
415 Must(action
!= NULL
);
416 action
->run(entry
, true);
420 \ingroup CacheManagerInternal
421 * Renders the protection level text for an action.
422 * Also doubles as a check for the protection level.
425 CacheManager::ActionProtection(const Mgr::ActionProfile::Pointer
&profile
)
427 assert(profile
!= NULL
);
428 const char *pwd
= PasswdGet(Config
.passwd_list
, profile
->name
);
431 return profile
->isPwReq
? "hidden" : "public";
433 if (!strcmp(pwd
, "disable"))
436 if (strcmp(pwd
, "none") == 0)
443 * \ingroup CacheManagerInternal
444 * gets from the global Config the password the user would need to supply
445 * for the action she queried
448 CacheManager::PasswdGet(Mgr::ActionPasswordList
* a
, const char *action
)
451 for (auto &w
: a
->actions
) {
452 if (w
.cmp(action
) == 0)
455 static const SBuf
allAction("all");
467 CacheManager::GetInstance()
469 static CacheManager
*instance
= nullptr;
471 debugs(16, 6, "starting cachemanager up");
472 instance
= new CacheManager
;
473 Mgr::RegisterBasics();