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