]> git.ipfire.org Git - thirdparty/squid.git/blob - src/cache_manager.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / cache_manager.cc
1 /*
2 * Copyright (C) 1996-2019 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9 /* DEBUG: section 16 Cache Manager Objects */
10
11 #include "squid.h"
12 #include "base/TextException.h"
13 #include "CacheManager.h"
14 #include "comm/Connection.h"
15 #include "Debug.h"
16 #include "errorpage.h"
17 #include "fde.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"
29 #include "protos.h"
30 #include "SquidConfig.h"
31 #include "SquidTime.h"
32 #include "Store.h"
33 #include "tools.h"
34 #include "wordlist.h"
35
36 #include <algorithm>
37
38 /// \ingroup CacheManagerInternal
39 #define MGR_PASSWD_SZ 128
40
41 /// creates Action using supplied Action::Create method and command
42 class ClassActionCreator: public Mgr::ActionCreator
43 {
44 public:
45 typedef Mgr::Action::Pointer Handler(const Mgr::Command::Pointer &cmd);
46
47 public:
48 ClassActionCreator(Handler *aHandler): handler(aHandler) {}
49
50 virtual Mgr::Action::Pointer create(const Mgr::Command::Pointer &cmd) const {
51 return handler(cmd);
52 }
53
54 private:
55 Handler *handler;
56 };
57
58 /// Registers new profiles, ignoring attempts to register a duplicate
59 void
60 CacheManager::registerProfile(const Mgr::ActionProfile::Pointer &profile)
61 {
62 Must(profile != NULL);
63 if (!CacheManager::findAction(profile->name)) {
64 menu_.push_back(profile);
65 debugs(16, 3, HERE << "registered profile: " << *profile);
66 } else {
67 debugs(16, 2, HERE << "skipped duplicate profile: " << *profile);
68 }
69 }
70
71 /**
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.
76 */
77 void
78 CacheManager::registerProfile(char const * action, char const * desc, OBJH * handler, int pw_req_flag, int atomic)
79 {
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);
84 }
85
86 /**
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.
91 */
92 void
93 CacheManager::registerProfile(char const * action, char const * desc,
94 ClassActionCreator::Handler *handler,
95 int pw_req_flag, int atomic)
96 {
97 const Mgr::ActionProfile::Pointer profile = new Mgr::ActionProfile(action,
98 desc, pw_req_flag, atomic, new ClassActionCreator(handler));
99 registerProfile(profile);
100 }
101
102 /**
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
107 */
108 Mgr::ActionProfile::Pointer
109 CacheManager::findAction(char const * action) const
110 {
111 Must(action != NULL);
112 Menu::const_iterator a;
113
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");
118 return *a;
119 }
120 }
121
122 debugs(16, 6, "Action not found.");
123 return Mgr::ActionProfilePointer();
124 }
125
126 Mgr::Action::Pointer
127 CacheManager::createNamedAction(const char *actionName)
128 {
129 Must(actionName);
130
131 Mgr::Command::Pointer cmd = new Mgr::Command;
132 cmd->profile = findAction(actionName);
133 cmd->params.actionName = actionName;
134
135 Must(cmd->profile != NULL);
136 return cmd->profile->creator->create(cmd);
137 }
138
139 Mgr::Action::Pointer
140 CacheManager::createRequestedAction(const Mgr::ActionParams &params)
141 {
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);
147 }
148
149 /**
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
156 */
157 Mgr::Command::Pointer
158 CacheManager::ParseUrl(const char *url)
159 {
160 int t;
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);
165 host[0] = 0;
166 request[0] = 0;
167 password[0] = 0;
168 params[0] = 0;
169 int pos = -1;
170 int len = strlen(url);
171 Must(len > 0);
172 t = sscanf(url, "cache_object://%[^/]/%[^@?]%n@%[^?]?%s", host, request, &pos, password, params);
173 if (t < 3) {
174 t = sscanf(url, "cache_object://%[^/]/%[^?]%n?%s", host, request, &pos, params);
175 }
176 if (t < 1) {
177 t = sscanf(url, "http://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
178 }
179 if (t < 1) {
180 t = sscanf(url, "https://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
181 }
182 if (t < 2) {
183 if (strncmp("cache_object://",url,15)==0)
184 xstrncpy(request, "menu", MAX_URL);
185 else
186 xstrncpy(request, "index", MAX_URL);
187 }
188
189 #if _SQUID_OS2_
190 if (t == 2 && request[0] == '\0') {
191 /*
192 * emx's sscanf insists of returning 2 because it sets request
193 * to null
194 */
195 if (strncmp("cache_object://",url,15)==0)
196 xstrncpy(request, "menu", MAX_URL);
197 else
198 xstrncpy(request, "index", MAX_URL);
199 }
200 #endif
201
202 debugs(16, 3, HERE << "MGR request: t=" << t << ", host='" << host << "', request='" << request << "', pos=" << pos <<
203 ", password='" << password << "', params='" << params << "'");
204
205 Mgr::ActionProfile::Pointer profile = findAction(request);
206 if (!profile) {
207 debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' not found");
208 return NULL;
209 }
210
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);
214 return NULL;
215 }
216
217 Mgr::Command::Pointer cmd = new Mgr::Command;
218 if (!Mgr::QueryParams::Parse(params, cmd->params.queryParams))
219 return NULL;
220 cmd->profile = profile;
221 cmd->params.httpUri = url;
222 cmd->params.userName = String();
223 cmd->params.password = password;
224 cmd->params.actionName = request;
225 return cmd;
226 }
227
228 /// \ingroup CacheManagerInternal
229 /*
230 \ingroup CacheManagerInternal
231 * Decodes the headers needed to perform user authentication and fills
232 * the details into the cachemgrStateData argument
233 */
234 void
235 CacheManager::ParseHeaders(const HttpRequest * request, Mgr::ActionParams &params)
236 {
237 assert(request);
238
239 params.httpMethod = request->method.id();
240 params.httpFlags = request->flags;
241
242 #if HAVE_AUTH_MODULE_BASIC
243 // TODO: use the authentication system decode to retrieve these details properly.
244
245 /* base 64 _decoded_ user:passwd pair */
246 const char *basic_cookie = request->header.getAuth(Http::HdrType::AUTHORIZATION, "Basic");
247
248 if (!basic_cookie)
249 return;
250
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 << "'");
254 return;
255 }
256
257 /* found user:password pair, reset old values */
258 params.userName.limitInit(basic_cookie, passwd_del - basic_cookie);
259 params.password = passwd_del + 1;
260
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 << "'");
264 #endif
265 }
266
267 /**
268 \ingroup CacheManagerInternal
269 *
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
273 */
274 int
275 CacheManager::CheckPassword(const Mgr::Command &cmd)
276 {
277 assert(cmd.profile != NULL);
278 const char *action = cmd.profile->name;
279 char *pwd = PasswdGet(Config.passwd_list, action);
280
281 debugs(16, 4, "CacheManager::CheckPassword for action " << action);
282
283 if (pwd == NULL)
284 return cmd.profile->isPwReq;
285
286 if (strcmp(pwd, "disable") == 0)
287 return 1;
288
289 if (strcmp(pwd, "none") == 0)
290 return 0;
291
292 if (!cmd.params.password.size())
293 return 1;
294
295 return cmd.params.password != pwd;
296 }
297
298 /**
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.
303 */
304 void
305 CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request, StoreEntry * entry)
306 {
307 debugs(16, 3, "CacheManager::Start: '" << entry->url() << "'" );
308
309 Mgr::Command::Pointer cmd = ParseUrl(entry->url());
310 if (!cmd) {
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;
315 return;
316 }
317
318 const char *actionName = cmd->profile->name;
319
320 entry->expires = squid_curtime;
321
322 debugs(16, 5, "CacheManager: " << client << " requesting '" << actionName << "'");
323
324 /* get additional info from request headers */
325 ParseHeaders(request, cmd->params);
326
327 const char *userName = cmd->params.userName.size() ?
328 cmd->params.userName.termedBuf() : "unknown";
329
330 /* Check password */
331
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 */
336
337 if (cmd->params.password.size()) {
338 debugs(16, DBG_IMPORTANT, "CacheManager: " <<
339 userName << "@" <<
340 client << ": incorrect password for '" <<
341 actionName << "'" );
342 } else {
343 debugs(16, DBG_IMPORTANT, "CacheManager: " <<
344 userName << "@" <<
345 client << ": password needed for '" <<
346 actionName << "'" );
347 }
348
349 HttpReply *rep = errState.BuildHttpReply();
350
351 #if HAVE_AUTH_MODULE_BASIC
352 /*
353 * add Authenticate header using action name as a realm because
354 * password depends on the action
355 */
356 rep->header.putAuth("Basic", actionName);
357 #endif
358 // Allow cachemgr and other XHR scripts access to our version string
359 if (request->header.has(Http::HdrType::ORIGIN)) {
360 rep->header.putExt("Access-Control-Allow-Origin",request->header.getStr(Http::HdrType::ORIGIN));
361 #if HAVE_AUTH_MODULE_BASIC
362 rep->header.putExt("Access-Control-Allow-Credentials","true");
363 #endif
364 rep->header.putExt("Access-Control-Expose-Headers","Server");
365 }
366
367 /* store the reply */
368 entry->replaceHttpReply(rep);
369
370 entry->expires = squid_curtime;
371
372 entry->complete();
373
374 return;
375 }
376
377 if (request->header.has(Http::HdrType::ORIGIN)) {
378 cmd->params.httpOrigin = request->header.getStr(Http::HdrType::ORIGIN);
379 }
380
381 debugs(16, 2, "CacheManager: " <<
382 userName << "@" <<
383 client << " requesting '" <<
384 actionName << "'" );
385
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(Http::HdrType::ORIGIN)) {
395 rep->header.putExt("Access-Control-Allow-Origin",request->header.getStr(Http::HdrType::ORIGIN));
396 #if HAVE_AUTH_MODULE_BASIC
397 rep->header.putExt("Access-Control-Allow-Credentials","true");
398 #endif
399 rep->header.putExt("Access-Control-Expose-Headers","Server");
400 }
401 entry->replaceHttpReply(rep);
402 entry->complete();
403 return;
404 }
405
406 if (UsingSmp() && IamWorkerProcess()) {
407 // is client the right connection to pass here?
408 AsyncJob::Start(new Mgr::Forwarder(client, cmd->params, request, entry));
409 return;
410 }
411
412 Mgr::Action::Pointer action = cmd->profile->creator->create(cmd);
413 Must(action != NULL);
414 action->run(entry, true);
415 }
416
417 /*
418 \ingroup CacheManagerInternal
419 * Renders the protection level text for an action.
420 * Also doubles as a check for the protection level.
421 */
422 const char *
423 CacheManager::ActionProtection(const Mgr::ActionProfile::Pointer &profile)
424 {
425 assert(profile != NULL);
426 const char *pwd = PasswdGet(Config.passwd_list, profile->name);
427
428 if (!pwd)
429 return profile->isPwReq ? "hidden" : "public";
430
431 if (!strcmp(pwd, "disable"))
432 return "disabled";
433
434 if (strcmp(pwd, "none") == 0)
435 return "public";
436
437 return "protected";
438 }
439
440 /*
441 * \ingroup CacheManagerInternal
442 * gets from the global Config the password the user would need to supply
443 * for the action she queried
444 */
445 char *
446 CacheManager::PasswdGet(Mgr::ActionPasswordList * a, const char *action)
447 {
448 while (a) {
449 for (auto &w : a->actions) {
450 if (w.cmp(action) == 0)
451 return a->passwd;
452
453 static const SBuf allAction("all");
454 if (w == allAction)
455 return a->passwd;
456 }
457
458 a = a->next;
459 }
460
461 return NULL;
462 }
463
464 CacheManager*
465 CacheManager::GetInstance()
466 {
467 static CacheManager *instance = nullptr;
468 if (!instance) {
469 debugs(16, 6, "starting cachemanager up");
470 instance = new CacheManager;
471 Mgr::RegisterBasics();
472 }
473 return instance;
474 }
475