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