]> git.ipfire.org Git - thirdparty/squid.git/blame - src/cache_manager.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / cache_manager.cc
CommitLineData
22f3fd98 1
2/*
262a0e14 3 * $Id$
22f3fd98 4 *
5 * DEBUG: section 16 Cache Manager Objects
6 * AUTHOR: Duane Wessels
7 *
2b6662ba 8 * SQUID Web Proxy Cache http://www.squid-cache.org/
e25c139f 9 * ----------------------------------------------------------
22f3fd98 10 *
2b6662ba 11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
22f3fd98 19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
26ac0430 24 *
22f3fd98 25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
26ac0430 29 *
22f3fd98 30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
cbdec147 32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 33 *
22f3fd98 34 */
35
f7f3304a 36#include "squid.h"
8822ebee 37#include "base/TextException.h"
62ee09ca 38#include "CacheManager.h"
5c336a3b 39#include "comm/Connection.h"
8822ebee 40#include "Debug.h"
aa839030 41#include "errorpage.h"
8822ebee 42#include "fde.h"
528b2c61 43#include "HttpReply.h"
44#include "HttpRequest.h"
8822ebee
AR
45#include "mgr/ActionCreator.h"
46#include "mgr/Action.h"
47#include "mgr/ActionProfile.h"
48#include "mgr/BasicActions.h"
49#include "mgr/Command.h"
50#include "mgr/Forwarder.h"
51#include "mgr/FunAction.h"
b8151fa1 52#include "mgr/QueryParams.h"
8822ebee 53#include "protos.h" /* rotate_logs() */
985c86bc 54#include "SquidTime.h"
8822ebee 55#include "Store.h"
d295d770 56#include "wordlist.h"
8822ebee 57#include <algorithm>
22f3fd98 58
63be0a78 59/// \ingroup CacheManagerInternal
22f3fd98 60#define MGR_PASSWD_SZ 128
61
8822ebee
AR
62/// creates Action using supplied Action::Create method and command
63class ClassActionCreator: public Mgr::ActionCreator
64{
65public:
66 typedef Mgr::Action::Pointer Handler(const Mgr::Command::Pointer &cmd);
c83f0bd5 67
8822ebee
AR
68public:
69 ClassActionCreator(Handler *aHandler): handler(aHandler) {}
70
d9fc6862 71 virtual Mgr::Action::Pointer create(const Mgr::Command::Pointer &cmd) const {
8822ebee
AR
72 return handler(cmd);
73 }
74
75private:
76 Handler *handler;
77};
78
8822ebee
AR
79/// Registers new profiles, ignoring attempts to register a duplicate
80void
81CacheManager::registerProfile(const Mgr::ActionProfile::Pointer &profile)
62ee09ca 82{
8822ebee 83 Must(profile != NULL);
ef890f9d 84 if (std::find(menu_.begin(), menu_.end(), profile) == menu_.end()) {
8822ebee
AR
85 menu_.push_back(profile);
86 debugs(16, 3, HERE << "registered profile: " << *profile);
87 } else {
88 debugs(16, 2, HERE << "skipped duplicate profile: " << *profile);
89 }
62ee09ca 90}
22f3fd98 91
50847dca
AJ
92/**
93 \ingroup CacheManagerAPI
94 * Registers a C-style action, which is implemented as a pointer to a function
95 * taking as argument a pointer to a StoreEntry and returning void.
96 * Implemented via CacheManagerActionLegacy.
97 */
22f3fd98 98void
8822ebee 99CacheManager::registerProfile(char const * action, char const * desc, OBJH * handler, int pw_req_flag, int atomic)
22f3fd98 100{
8822ebee
AR
101 debugs(16, 3, HERE << "registering legacy " << action);
102 const Mgr::ActionProfile::Pointer profile = new Mgr::ActionProfile(action,
d9fc6862 103 desc, pw_req_flag, atomic, new Mgr::FunActionCreator(handler));
8822ebee 104 registerProfile(profile);
1d8395bd 105}
62e76326 106
d154d3ec 107/**
e0d28505 108 * \ingroup CacheManagerAPI
a750e510 109 * Registers a C++-style action, via a pointer to a subclass of
d154d3ec
FC
110 * a CacheManagerAction object, whose run() method will be invoked when
111 * CacheManager identifies that the user has requested the action.
112 */
1d8395bd 113void
8822ebee 114CacheManager::registerProfile(char const * action, char const * desc,
d9fc6862
A
115 ClassActionCreator::Handler *handler,
116 int pw_req_flag, int atomic)
1d8395bd 117{
8822ebee 118 const Mgr::ActionProfile::Pointer profile = new Mgr::ActionProfile(action,
d9fc6862 119 desc, pw_req_flag, atomic, new ClassActionCreator(handler));
8822ebee 120 registerProfile(profile);
62ee09ca 121}
122
d154d3ec
FC
123/**
124 \ingroup CacheManagerInternal
125 * Locates an action in the actions registry ActionsList.
126\retval NULL if Action not found
127\retval CacheManagerAction* if the action was found
128 */
8822ebee
AR
129Mgr::ActionProfile::Pointer
130CacheManager::findAction(char const * action) const
22f3fd98 131{
8822ebee
AR
132 Must(action != NULL);
133 Menu::const_iterator a;
03c4599f
K
134
135 debugs(16, 5, "CacheManager::findAction: looking for action " << action);
8822ebee
AR
136 for (a = menu_.begin(); a != menu_.end(); ++a) {
137 if (0 == strcmp((*a)->name, action)) {
03c4599f
K
138 debugs(16, 6, " found");
139 return *a;
140 }
22f3fd98 141 }
62e76326 142
03c4599f 143 debugs(16, 6, "Action not found.");
8822ebee
AR
144 return Mgr::ActionProfilePointer();
145}
146
147Mgr::Action::Pointer
148CacheManager::createNamedAction(const char *actionName)
149{
150 Must(actionName);
151
152 Mgr::Command::Pointer cmd = new Mgr::Command;
153 cmd->profile = findAction(actionName);
154 cmd->params.actionName = actionName;
155
156 Must(cmd->profile != NULL);
157 return cmd->profile->creator->create(cmd);
158}
159
160Mgr::Action::Pointer
161CacheManager::createRequestedAction(const Mgr::ActionParams &params)
162{
163 Mgr::Command::Pointer cmd = new Mgr::Command;
164 cmd->params = params;
165 cmd->profile = findAction(params.actionName.termedBuf());
166 Must(cmd->profile != NULL);
167 return cmd->profile->creator->create(cmd);
22f3fd98 168}
169
832c08ab
FC
170/**
171 \ingroup CacheManagerInternal
172 * define whether the URL is a cache-manager URL and parse the action
173 * requested by the user. Checks via CacheManager::ActionProtection() that the
174 * item is accessible by the user.
175 \retval CacheManager::cachemgrStateData state object for the following handling
176 \retval NULL if the action can't be found or can't be accessed by the user
177 */
8822ebee 178Mgr::Command::Pointer
c83f0bd5 179CacheManager::ParseUrl(const char *url)
22f3fd98 180{
181 int t;
182 LOCAL_ARRAY(char, host, MAX_URL);
183 LOCAL_ARRAY(char, request, MAX_URL);
184 LOCAL_ARRAY(char, password, MAX_URL);
b8151fa1
CT
185 LOCAL_ARRAY(char, params, MAX_URL);
186 host[0] = 0;
187 request[0] = 0;
188 password[0] = 0;
189 params[0] = 0;
190 int pos = -1;
191 int len = strlen(url);
192 Must(len > 0);
193 t = sscanf(url, "cache_object://%[^/]/%[^@?]%n@%[^?]?%s", host, request, &pos, password, params);
5366b99b
AJ
194 if (t < 3) {
195 t = sscanf(url, "cache_object://%[^/]/%[^?]%n?%s", host, request, &pos, params);
196 }
e37bd29b
AJ
197 if (t < 1) {
198 t = sscanf(url, "http://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
199 }
200 if (t < 1) {
201 t = sscanf(url, "https://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
202 }
f9c6f861
AJ
203 if (t < 2) {
204 if (strncmp("cache_object://",url,15)==0)
205 xstrncpy(request, "menu", MAX_URL);
206 else
207 xstrncpy(request, "index", MAX_URL);
208 }
8822ebee 209
1191b93b 210#if _SQUID_OS2_
8822ebee 211 if (t == 2 && request[0] == '\0') {
62e76326 212 /*
213 * emx's sscanf insists of returning 2 because it sets request
214 * to null
215 */
f9c6f861
AJ
216 if (strncmp("cache_object://",url,15)==0)
217 xstrncpy(request, "menu", MAX_URL);
218 else
219 xstrncpy(request, "index", MAX_URL);
8822ebee 220 }
cd377065 221#endif
62e76326 222
5366b99b
AJ
223 debugs(16, 3, HERE << "MGR request: t=" << t << ", host='" << host << "', request='" << request << "', pos=" << pos <<
224 ", password='" << password << "', params='" << params << "'");
225
8822ebee
AR
226 Mgr::ActionProfile::Pointer profile = findAction(request);
227 if (!profile) {
3e1da049 228 debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' not found");
62e76326 229 return NULL;
22f3fd98 230 }
62e76326 231
8822ebee
AR
232 const char *prot = ActionProtection(profile);
233 if (!strcmp(prot, "disabled") || !strcmp(prot, "hidden")) {
234 debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' is " << prot);
235 return NULL;
236 }
62e76326 237
8822ebee 238 Mgr::Command::Pointer cmd = new Mgr::Command;
b8151fa1
CT
239 if (!Mgr::QueryParams::Parse(params, cmd->params.queryParams))
240 return NULL;
8822ebee
AR
241 cmd->profile = profile;
242 cmd->params.httpUri = url;
243 cmd->params.userName = String();
b8151fa1 244 cmd->params.password = password;
8822ebee
AR
245 cmd->params.actionName = request;
246 return cmd;
22f3fd98 247}
248
63be0a78 249/// \ingroup CacheManagerInternal
832c08ab
FC
250/*
251 \ingroup CacheManagerInternal
252 * Decodes the headers needed to perform user authentication and fills
253 * the details into the cachemgrStateData argument
254 */
c83f0bd5 255void
8822ebee 256CacheManager::ParseHeaders(const HttpRequest * request, Mgr::ActionParams &params)
63259c34 257{
8822ebee
AR
258 assert(request);
259
260 params.httpMethod = request->method.id();
261 params.httpFlags = request->flags;
262
9da6b594
AJ
263#if HAVE_AUTH_MODULE_BASIC
264 // TODO: use the authentication system decode to retrieve these details properly.
265
266 /* base 64 _decoded_ user:passwd pair */
267 const char *basic_cookie = request->header.getAuth(HDR_AUTHORIZATION, "Basic");
62e76326 268
99edd1c3 269 if (!basic_cookie)
62e76326 270 return;
271
9da6b594 272 const char *passwd_del;
63259c34 273 if (!(passwd_del = strchr(basic_cookie, ':'))) {
3e1da049 274 debugs(16, DBG_IMPORTANT, "CacheManager::ParseHeaders: unknown basic_cookie format '" << basic_cookie << "'");
62e76326 275 return;
63259c34 276 }
62e76326 277
63259c34 278 /* found user:password pair, reset old values */
8822ebee
AR
279 params.userName.limitInit(basic_cookie, passwd_del - basic_cookie);
280 params.password = passwd_del + 1;
62e76326 281
9da6b594 282 /* warning: this prints decoded password which maybe not be what you want to do @?@ @?@ */
8822ebee 283 debugs(16, 9, "CacheManager::ParseHeaders: got user: '" <<
d9fc6862 284 params.userName << "' passwd: '" << params.password << "'");
9da6b594 285#endif
63259c34 286}
287
63be0a78 288/**
289 \ingroup CacheManagerInternal
290 *
291 \retval 0 if mgr->password is good or "none"
292 \retval 1 if mgr->password is "disable"
293 \retval !0 if mgr->password does not match configured password
22f3fd98 294 */
c83f0bd5 295int
8822ebee 296CacheManager::CheckPassword(const Mgr::Command &cmd)
22f3fd98 297{
8822ebee
AR
298 assert(cmd.profile != NULL);
299 const char *action = cmd.profile->name;
300 char *pwd = PasswdGet(Config.passwd_list, action);
03c4599f 301
8822ebee 302 debugs(16, 4, "CacheManager::CheckPassword for action " << action);
62e76326 303
22f3fd98 304 if (pwd == NULL)
8822ebee 305 return cmd.profile->isPwReq;
62e76326 306
22f3fd98 307 if (strcmp(pwd, "disable") == 0)
62e76326 308 return 1;
309
22f3fd98 310 if (strcmp(pwd, "none") == 0)
62e76326 311 return 0;
312
8822ebee 313 if (!cmd.params.password.size())
62e76326 314 return 1;
315
8822ebee 316 return cmd.params.password != pwd;
22f3fd98 317}
318
832c08ab
FC
319/**
320 \ingroup CacheManagerAPI
321 * Main entry point in the Cache Manager's activity. Gets called as part
322 * of the forward chain if the right URL is detected there. Initiates
323 * all needed internal work and renders the response.
324 */
22f3fd98 325void
5c336a3b 326CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request, StoreEntry * entry)
22f3fd98 327{
832c08ab 328 debugs(16, 3, "CacheManager::Start: '" << entry->url() << "'" );
62e76326 329
8822ebee
AR
330 Mgr::Command::Pointer cmd = ParseUrl(entry->url());
331 if (!cmd) {
913524f0 332 ErrorState *err = new ErrorState(ERR_INVALID_URL, HTTP_NOT_FOUND, request);
3900307b 333 err->url = xstrdup(entry->url());
62e76326 334 errorAppendEntry(entry, err);
335 entry->expires = squid_curtime;
336 return;
22f3fd98 337 }
62e76326 338
8822ebee 339 const char *actionName = cmd->profile->name;
34266cde 340
22f3fd98 341 entry->expires = squid_curtime;
34266cde 342
a750e510 343 debugs(16, 5, "CacheManager: " << client << " requesting '" << actionName << "'");
34266cde 344
63259c34 345 /* get additional info from request headers */
8822ebee
AR
346 ParseHeaders(request, cmd->params);
347
348 const char *userName = cmd->params.userName.size() ?
d9fc6862 349 cmd->params.userName.termedBuf() : "unknown";
34266cde 350
22f3fd98 351 /* Check password */
62e76326 352
8822ebee 353 if (CheckPassword(*cmd) != 0) {
62e76326 354 /* build error message */
913524f0 355 ErrorState errState(ERR_CACHE_MGR_ACCESS_DENIED, HTTP_UNAUTHORIZED, request);
62e76326 356 /* warn if user specified incorrect password */
357
8822ebee 358 if (cmd->params.password.size()) {
26ac0430 359 debugs(16, DBG_IMPORTANT, "CacheManager: " <<
8822ebee 360 userName << "@" <<
5c336a3b 361 client << ": incorrect password for '" <<
8822ebee
AR
362 actionName << "'" );
363 } else {
26ac0430 364 debugs(16, DBG_IMPORTANT, "CacheManager: " <<
8822ebee 365 userName << "@" <<
5c336a3b 366 client << ": password needed for '" <<
8822ebee
AR
367 actionName << "'" );
368 }
62e76326 369
913524f0 370 HttpReply *rep = errState.BuildHttpReply();
62e76326 371
9da6b594 372#if HAVE_AUTH_MODULE_BASIC
62e76326 373 /*
8822ebee
AR
374 * add Authenticate header using action name as a realm because
375 * password depends on the action
62e76326 376 */
8822ebee 377 rep->header.putAuth("Basic", actionName);
9da6b594 378#endif
3865965d
AJ
379 // Allow cachemgr and other XHR scripts access to our version string
380 if (request->header.has(HDR_ORIGIN)) {
381 rep->header.putExt("Access-Control-Allow-Origin",request->header.getStr(HDR_ORIGIN));
382#if HAVE_AUTH_MODULE_BASIC
383 rep->header.putExt("Access-Control-Allow-Credentials","true");
384#endif
385 rep->header.putExt("Access-Control-Expose-Headers","Server");
386 }
62e76326 387
388 /* store the reply */
db237875 389 entry->replaceHttpReply(rep);
62e76326 390
391 entry->expires = squid_curtime;
392
393 entry->complete();
394
62e76326 395 return;
22f3fd98 396 }
62e76326 397
3865965d
AJ
398 if (request->header.has(HDR_ORIGIN)) {
399 cmd->params.httpOrigin = request->header.getStr(HDR_ORIGIN);
400 }
401
0be039f4 402 debugs(16, 2, "CacheManager: " <<
8822ebee 403 userName << "@" <<
5c336a3b 404 client << " requesting '" <<
8822ebee 405 actionName << "'" );
62e76326 406
b073fc4b
AJ
407 // special case: /squid-internal-mgr/ index page
408 if (!strcmp(cmd->profile->name, "index")) {
409 ErrorState err(MGR_INDEX, HTTP_OK, request);
410 err.url = xstrdup(entry->url());
411 HttpReply *rep = err.BuildHttpReply();
412 if (strncmp(rep->body.content(),"Internal Error:", 15) == 0)
413 rep->sline.status = HTTP_NOT_FOUND;
414 // Allow cachemgr and other XHR scripts access to our version string
415 if (request->header.has(HDR_ORIGIN)) {
416 rep->header.putExt("Access-Control-Allow-Origin",request->header.getStr(HDR_ORIGIN));
417#if HAVE_AUTH_MODULE_BASIC
418 rep->header.putExt("Access-Control-Allow-Credentials","true");
419#endif
420 rep->header.putExt("Access-Control-Expose-Headers","Server");
421 }
422 entry->replaceHttpReply(rep);
423 entry->complete();
424 return;
425 }
426
8822ebee 427 if (UsingSmp() && IamWorkerProcess()) {
1b76e6c1 428 // is client the right connection to pass here?
25b481e6 429 AsyncJob::Start(new Mgr::Forwarder(client, cmd->params, request, entry));
8822ebee 430 return;
cb69b4c7 431 }
62e76326 432
8822ebee
AR
433 Mgr::Action::Pointer action = cmd->profile->creator->create(cmd);
434 Must(action != NULL);
435 action->run(entry, true);
22f3fd98 436}
437
832c08ab
FC
438/*
439 \ingroup CacheManagerInternal
440 * Renders the protection level text for an action.
441 * Also doubles as a check for the protection level.
832c08ab 442 */
c83f0bd5 443const char *
8822ebee 444CacheManager::ActionProtection(const Mgr::ActionProfile::Pointer &profile)
7395afb8 445{
8822ebee
AR
446 assert(profile != NULL);
447 const char *pwd = PasswdGet(Config.passwd_list, profile->name);
62e76326 448
7395afb8 449 if (!pwd)
8822ebee 450 return profile->isPwReq ? "hidden" : "public";
62e76326 451
7395afb8 452 if (!strcmp(pwd, "disable"))
62e76326 453 return "disabled";
454
7395afb8 455 if (strcmp(pwd, "none") == 0)
62e76326 456 return "public";
457
7395afb8 458 return "protected";
459}
460
832c08ab
FC
461/*
462 \ingroup CacheManagerInternal
463 * gets from the global Config the password the user would need to supply
464 * for the action she queried
465 */
c83f0bd5
K
466char *
467CacheManager::PasswdGet(cachemgr_passwd * a, const char *action)
22f3fd98 468{
469 wordlist *w;
62e76326 470
22f3fd98 471 while (a != NULL) {
62e76326 472 for (w = a->actions; w != NULL; w = w->next) {
473 if (0 == strcmp(w->key, action))
474 return a->passwd;
475
476 if (0 == strcmp(w->key, "all"))
477 return a->passwd;
478 }
479
480 a = a->next;
22f3fd98 481 }
62e76326 482
22f3fd98 483 return NULL;
484}
c83f0bd5
K
485
486CacheManager* CacheManager::instance=0;
487
832c08ab
FC
488/**
489 \ingroup CacheManagerAPI
490 * Singleton accessor method.
491 */
c83f0bd5 492CacheManager*
26ac0430
AJ
493CacheManager::GetInstance()
494{
495 if (instance == 0) {
496 debugs(16, 6, "CacheManager::GetInstance: starting cachemanager up");
497 instance = new CacheManager;
8822ebee 498 Mgr::RegisterBasics();
26ac0430
AJ
499 }
500 return instance;
c83f0bd5 501}