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