2 * Copyright (C) 1996-2014 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 "base/TextException.h"
13 #include "CacheManager.h"
14 #include "comm/Connection.h"
16 #include "errorpage.h"
18 #include "HttpReply.h"
19 #include "HttpRequest.h"
20 #include "mgr/Action.h"
21 #include "mgr/ActionCreator.h"
22 #include "mgr/ActionPasswordList.h"
23 #include "mgr/ActionProfile.h"
24 #include "mgr/BasicActions.h"
25 #include "mgr/Command.h"
26 #include "mgr/Forwarder.h"
27 #include "mgr/FunAction.h"
28 #include "mgr/QueryParams.h"
30 #include "SquidConfig.h"
31 #include "SquidTime.h"
38 /// \ingroup CacheManagerInternal
39 #define MGR_PASSWD_SZ 128
41 /// creates Action using supplied Action::Create method and command
42 class ClassActionCreator
: public Mgr::ActionCreator
45 typedef Mgr::Action::Pointer
Handler(const Mgr::Command::Pointer
&cmd
);
48 ClassActionCreator(Handler
*aHandler
): handler(aHandler
) {}
50 virtual Mgr::Action::Pointer
create(const Mgr::Command::Pointer
&cmd
) const {
58 /// Registers new profiles, ignoring attempts to register a duplicate
60 CacheManager::registerProfile(const Mgr::ActionProfile::Pointer
&profile
)
62 Must(profile
!= NULL
);
63 if (!CacheManager::findAction(profile
->name
)) {
64 menu_
.push_back(profile
);
65 debugs(16, 3, HERE
<< "registered profile: " << *profile
);
67 debugs(16, 2, HERE
<< "skipped duplicate profile: " << *profile
);
72 \ingroup CacheManagerAPI
73 * Registers a C-style action, which is implemented as a pointer to a function
74 * taking as argument a pointer to a StoreEntry and returning void.
75 * Implemented via CacheManagerActionLegacy.
78 CacheManager::registerProfile(char const * action
, char const * desc
, OBJH
* handler
, int pw_req_flag
, int atomic
)
80 debugs(16, 3, HERE
<< "registering legacy " << action
);
81 const Mgr::ActionProfile::Pointer profile
= new Mgr::ActionProfile(action
,
82 desc
, pw_req_flag
, atomic
, new Mgr::FunActionCreator(handler
));
83 registerProfile(profile
);
87 * \ingroup CacheManagerAPI
88 * Registers a C++-style action, via a pointer to a subclass of
89 * a CacheManagerAction object, whose run() method will be invoked when
90 * CacheManager identifies that the user has requested the action.
93 CacheManager::registerProfile(char const * action
, char const * desc
,
94 ClassActionCreator::Handler
*handler
,
95 int pw_req_flag
, int atomic
)
97 const Mgr::ActionProfile::Pointer profile
= new Mgr::ActionProfile(action
,
98 desc
, pw_req_flag
, atomic
, new ClassActionCreator(handler
));
99 registerProfile(profile
);
103 \ingroup CacheManagerInternal
104 * Locates an action in the actions registry ActionsList.
105 \retval NULL if Action not found
106 \retval CacheManagerAction* if the action was found
108 Mgr::ActionProfile::Pointer
109 CacheManager::findAction(char const * action
) const
111 Must(action
!= NULL
);
112 Menu::const_iterator a
;
114 debugs(16, 5, "CacheManager::findAction: looking for action " << action
);
115 for (a
= menu_
.begin(); a
!= menu_
.end(); ++a
) {
116 if (0 == strcmp((*a
)->name
, action
)) {
117 debugs(16, 6, " found");
122 debugs(16, 6, "Action not found.");
123 return Mgr::ActionProfilePointer();
127 CacheManager::createNamedAction(const char *actionName
)
131 Mgr::Command::Pointer cmd
= new Mgr::Command
;
132 cmd
->profile
= findAction(actionName
);
133 cmd
->params
.actionName
= actionName
;
135 Must(cmd
->profile
!= NULL
);
136 return cmd
->profile
->creator
->create(cmd
);
140 CacheManager::createRequestedAction(const Mgr::ActionParams
¶ms
)
142 Mgr::Command::Pointer cmd
= new Mgr::Command
;
143 cmd
->params
= params
;
144 cmd
->profile
= findAction(params
.actionName
.termedBuf());
145 Must(cmd
->profile
!= NULL
);
146 return cmd
->profile
->creator
->create(cmd
);
150 \ingroup CacheManagerInternal
151 * define whether the URL is a cache-manager URL and parse the action
152 * requested by the user. Checks via CacheManager::ActionProtection() that the
153 * item is accessible by the user.
154 \retval CacheManager::cachemgrStateData state object for the following handling
155 \retval NULL if the action can't be found or can't be accessed by the user
157 Mgr::Command::Pointer
158 CacheManager::ParseUrl(const char *url
)
161 LOCAL_ARRAY(char, host
, MAX_URL
);
162 LOCAL_ARRAY(char, request
, MAX_URL
);
163 LOCAL_ARRAY(char, password
, MAX_URL
);
164 LOCAL_ARRAY(char, params
, MAX_URL
);
170 int len
= strlen(url
);
172 t
= sscanf(url
, "cache_object://%[^/]/%[^@?]%n@%[^?]?%s", host
, request
, &pos
, password
, params
);
174 t
= sscanf(url
, "cache_object://%[^/]/%[^?]%n?%s", host
, request
, &pos
, params
);
177 t
= sscanf(url
, "http://%[^/]/squid-internal-mgr/%[^?]%n?%s", host
, request
, &pos
, params
);
180 t
= sscanf(url
, "https://%[^/]/squid-internal-mgr/%[^?]%n?%s", host
, request
, &pos
, params
);
183 if (strncmp("cache_object://",url
,15)==0)
184 xstrncpy(request
, "menu", MAX_URL
);
186 xstrncpy(request
, "index", MAX_URL
);
190 if (t
== 2 && request
[0] == '\0') {
192 * emx's sscanf insists of returning 2 because it sets request
195 if (strncmp("cache_object://",url
,15)==0)
196 xstrncpy(request
, "menu", MAX_URL
);
198 xstrncpy(request
, "index", MAX_URL
);
202 debugs(16, 3, HERE
<< "MGR request: t=" << t
<< ", host='" << host
<< "', request='" << request
<< "', pos=" << pos
<<
203 ", password='" << password
<< "', params='" << params
<< "'");
205 Mgr::ActionProfile::Pointer profile
= findAction(request
);
207 debugs(16, DBG_IMPORTANT
, "CacheManager::ParseUrl: action '" << request
<< "' not found");
211 const char *prot
= ActionProtection(profile
);
212 if (!strcmp(prot
, "disabled") || !strcmp(prot
, "hidden")) {
213 debugs(16, DBG_IMPORTANT
, "CacheManager::ParseUrl: action '" << request
<< "' is " << prot
);
217 Mgr::Command::Pointer cmd
= new Mgr::Command
;
218 if (!Mgr::QueryParams::Parse(params
, cmd
->params
.queryParams
))
220 cmd
->profile
= profile
;
221 cmd
->params
.httpUri
= url
;
222 cmd
->params
.userName
= String();
223 cmd
->params
.password
= password
;
224 cmd
->params
.actionName
= request
;
228 /// \ingroup CacheManagerInternal
230 \ingroup CacheManagerInternal
231 * Decodes the headers needed to perform user authentication and fills
232 * the details into the cachemgrStateData argument
235 CacheManager::ParseHeaders(const HttpRequest
* request
, Mgr::ActionParams
¶ms
)
239 params
.httpMethod
= request
->method
.id();
240 params
.httpFlags
= request
->flags
;
242 #if HAVE_AUTH_MODULE_BASIC
243 // TODO: use the authentication system decode to retrieve these details properly.
245 /* base 64 _decoded_ user:passwd pair */
246 const char *basic_cookie
= request
->header
.getAuth(HDR_AUTHORIZATION
, "Basic");
251 const char *passwd_del
;
252 if (!(passwd_del
= strchr(basic_cookie
, ':'))) {
253 debugs(16, DBG_IMPORTANT
, "CacheManager::ParseHeaders: unknown basic_cookie format '" << basic_cookie
<< "'");
257 /* found user:password pair, reset old values */
258 params
.userName
.limitInit(basic_cookie
, passwd_del
- basic_cookie
);
259 params
.password
= passwd_del
+ 1;
261 /* warning: this prints decoded password which maybe not be what you want to do @?@ @?@ */
262 debugs(16, 9, "CacheManager::ParseHeaders: got user: '" <<
263 params
.userName
<< "' passwd: '" << params
.password
<< "'");
268 \ingroup CacheManagerInternal
270 \retval 0 if mgr->password is good or "none"
271 \retval 1 if mgr->password is "disable"
272 \retval !0 if mgr->password does not match configured password
275 CacheManager::CheckPassword(const Mgr::Command
&cmd
)
277 assert(cmd
.profile
!= NULL
);
278 const char *action
= cmd
.profile
->name
;
279 char *pwd
= PasswdGet(Config
.passwd_list
, action
);
281 debugs(16, 4, "CacheManager::CheckPassword for action " << action
);
284 return cmd
.profile
->isPwReq
;
286 if (strcmp(pwd
, "disable") == 0)
289 if (strcmp(pwd
, "none") == 0)
292 if (!cmd
.params
.password
.size())
295 return cmd
.params
.password
!= pwd
;
299 \ingroup CacheManagerAPI
300 * Main entry point in the Cache Manager's activity. Gets called as part
301 * of the forward chain if the right URL is detected there. Initiates
302 * all needed internal work and renders the response.
305 CacheManager::Start(const Comm::ConnectionPointer
&client
, HttpRequest
* request
, StoreEntry
* entry
)
307 debugs(16, 3, "CacheManager::Start: '" << entry
->url() << "'" );
309 Mgr::Command::Pointer cmd
= ParseUrl(entry
->url());
311 ErrorState
*err
= new ErrorState(ERR_INVALID_URL
, Http::scNotFound
, request
);
312 err
->url
= xstrdup(entry
->url());
313 errorAppendEntry(entry
, err
);
314 entry
->expires
= squid_curtime
;
318 const char *actionName
= cmd
->profile
->name
;
320 entry
->expires
= squid_curtime
;
322 debugs(16, 5, "CacheManager: " << client
<< " requesting '" << actionName
<< "'");
324 /* get additional info from request headers */
325 ParseHeaders(request
, cmd
->params
);
327 const char *userName
= cmd
->params
.userName
.size() ?
328 cmd
->params
.userName
.termedBuf() : "unknown";
332 if (CheckPassword(*cmd
) != 0) {
333 /* build error message */
334 ErrorState
errState(ERR_CACHE_MGR_ACCESS_DENIED
, Http::scUnauthorized
, request
);
335 /* warn if user specified incorrect password */
337 if (cmd
->params
.password
.size()) {
338 debugs(16, DBG_IMPORTANT
, "CacheManager: " <<
340 client
<< ": incorrect password for '" <<
343 debugs(16, DBG_IMPORTANT
, "CacheManager: " <<
345 client
<< ": password needed for '" <<
349 HttpReply
*rep
= errState
.BuildHttpReply();
351 #if HAVE_AUTH_MODULE_BASIC
353 * add Authenticate header using action name as a realm because
354 * password depends on the action
356 rep
->header
.putAuth("Basic", actionName
);
358 // Allow cachemgr and other XHR scripts access to our version string
359 if (request
->header
.has(HDR_ORIGIN
)) {
360 rep
->header
.putExt("Access-Control-Allow-Origin",request
->header
.getStr(HDR_ORIGIN
));
361 #if HAVE_AUTH_MODULE_BASIC
362 rep
->header
.putExt("Access-Control-Allow-Credentials","true");
364 rep
->header
.putExt("Access-Control-Expose-Headers","Server");
367 /* store the reply */
368 entry
->replaceHttpReply(rep
);
370 entry
->expires
= squid_curtime
;
377 if (request
->header
.has(HDR_ORIGIN
)) {
378 cmd
->params
.httpOrigin
= request
->header
.getStr(HDR_ORIGIN
);
381 debugs(16, 2, "CacheManager: " <<
383 client
<< " requesting '" <<
386 // special case: /squid-internal-mgr/ index page
387 if (!strcmp(cmd
->profile
->name
, "index")) {
388 ErrorState
err(MGR_INDEX
, Http::scOkay
, request
);
389 err
.url
= xstrdup(entry
->url());
390 HttpReply
*rep
= err
.BuildHttpReply();
391 if (strncmp(rep
->body
.content(),"Internal Error:", 15) == 0)
392 rep
->sline
.set(Http::ProtocolVersion(1,1), Http::scNotFound
);
393 // Allow cachemgr and other XHR scripts access to our version string
394 if (request
->header
.has(HDR_ORIGIN
)) {
395 rep
->header
.putExt("Access-Control-Allow-Origin",request
->header
.getStr(HDR_ORIGIN
));
396 #if HAVE_AUTH_MODULE_BASIC
397 rep
->header
.putExt("Access-Control-Allow-Credentials","true");
399 rep
->header
.putExt("Access-Control-Expose-Headers","Server");
401 entry
->replaceHttpReply(rep
);
406 if (UsingSmp() && IamWorkerProcess()) {
407 // is client the right connection to pass here?
408 AsyncJob::Start(new Mgr::Forwarder(client
, cmd
->params
, request
, entry
));
412 Mgr::Action::Pointer action
= cmd
->profile
->creator
->create(cmd
);
413 Must(action
!= NULL
);
414 action
->run(entry
, true);
418 \ingroup CacheManagerInternal
419 * Renders the protection level text for an action.
420 * Also doubles as a check for the protection level.
423 CacheManager::ActionProtection(const Mgr::ActionProfile::Pointer
&profile
)
425 assert(profile
!= NULL
);
426 const char *pwd
= PasswdGet(Config
.passwd_list
, profile
->name
);
429 return profile
->isPwReq
? "hidden" : "public";
431 if (!strcmp(pwd
, "disable"))
434 if (strcmp(pwd
, "none") == 0)
441 * \ingroup CacheManagerInternal
442 * gets from the global Config the password the user would need to supply
443 * for the action she queried
446 CacheManager::PasswdGet(Mgr::ActionPasswordList
* a
, const char *action
)
451 for (w
= a
->actions
; w
!= NULL
; w
= w
->next
) {
452 if (0 == strcmp(w
->key
, action
))
455 if (0 == strcmp(w
->key
, "all"))
465 CacheManager
* CacheManager::instance
=0;
468 \ingroup CacheManagerAPI
469 * Singleton accessor method.
472 CacheManager::GetInstance()
475 debugs(16, 6, "CacheManager::GetInstance: starting cachemanager up");
476 instance
= new CacheManager
;
477 Mgr::RegisterBasics();