]>
Commit | Line | Data |
---|---|---|
30a4f2a8 | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
e25c139f | 3 | * |
bbc27441 AJ |
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. | |
30a4f2a8 | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 61 Redirector */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
3ad63615 | 12 | #include "acl/Checklist.h" |
32fd6d8a | 13 | #include "cache_cf.h" |
a46d2c0e | 14 | #include "client_side.h" |
7561850e | 15 | #include "client_side_reply.h" |
582c2af2 FC |
16 | #include "client_side_request.h" |
17 | #include "comm/Connection.h" | |
18 | #include "fde.h" | |
b11724bb | 19 | #include "format/Format.h" |
582c2af2 | 20 | #include "globals.h" |
24438ec5 AJ |
21 | #include "helper.h" |
22 | #include "helper/Reply.h" | |
d3dddfb5 | 23 | #include "http/Stream.h" |
582c2af2 | 24 | #include "HttpRequest.h" |
582c2af2 | 25 | #include "mgr/Registration.h" |
c548327a | 26 | #include "redirect.h" |
1fa9b1a7 | 27 | #include "rfc1738.h" |
65e41a45 | 28 | #include "sbuf/SBuf.h" |
4d5904f7 | 29 | #include "SquidConfig.h" |
582c2af2 FC |
30 | #include "Store.h" |
31 | #if USE_AUTH | |
32 | #include "auth/UserRequest.h" | |
33 | #endif | |
cb4f4424 | 34 | #if USE_OPENSSL |
4db984be CT |
35 | #include "ssl/support.h" |
36 | #endif | |
30a4f2a8 | 37 | |
2f8abb64 | 38 | /// url maximum length + extra information passed to redirector |
7561850e AJ |
39 | #define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024) |
40 | ||
dd1efef8 AJ |
41 | class RedirectStateData |
42 | { | |
5c2f68b7 AJ |
43 | CBDATA_CLASS(RedirectStateData); |
44 | ||
dd1efef8 AJ |
45 | public: |
46 | explicit RedirectStateData(const char *url); | |
47 | ~RedirectStateData(); | |
48 | ||
d2af9477 | 49 | void *data; |
dd1efef8 | 50 | SBuf orig_url; |
62e76326 | 51 | |
e166785a | 52 | HLPCB *handler; |
dd1efef8 | 53 | }; |
30a4f2a8 | 54 | |
74addf6c | 55 | static HLPCB redirectHandleReply; |
a8a0b1c2 | 56 | static HLPCB storeIdHandleReply; |
e05a9d51 EB |
57 | static Helper::Client::Pointer redirectors; |
58 | static Helper::Client::Pointer storeIds; | |
74addf6c | 59 | static OBJH redirectStats; |
a8a0b1c2 EC |
60 | static OBJH storeIdStats; |
61 | static int redirectorBypassed = 0; | |
62 | static int storeIdBypassed = 0; | |
aee3523a AR |
63 | static Format::Format *redirectorExtrasFmt = nullptr; |
64 | static Format::Format *storeIdExtrasFmt = nullptr; | |
dd1efef8 AJ |
65 | |
66 | CBDATA_CLASS_INIT(RedirectStateData); | |
67 | ||
68 | RedirectStateData::RedirectStateData(const char *url) : | |
aee3523a | 69 | data(nullptr), |
f53969cc | 70 | orig_url(url), |
aee3523a | 71 | handler(nullptr) |
dd1efef8 AJ |
72 | { |
73 | } | |
74 | ||
75 | RedirectStateData::~RedirectStateData() | |
76 | { | |
77 | } | |
30a4f2a8 | 78 | |
582b6456 | 79 | static void |
24438ec5 | 80 | redirectHandleReply(void *data, const Helper::Reply &reply) |
30a4f2a8 | 81 | { |
dd1efef8 | 82 | RedirectStateData *r = static_cast<RedirectStateData *>(data); |
bf95c10a | 83 | debugs(61, 5, "reply=" << reply); |
e166785a | 84 | |
d06e17ea AJ |
85 | // XXX: This function is now kept only to check for and display the garbage use-case |
86 | // and to map the old helper response format(s) into new format result code and key=value pairs | |
fd7f26ea | 87 | // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format |
e166785a | 88 | |
2428ce02 | 89 | if (reply.result == Helper::Unknown) { |
e166785a AJ |
90 | // BACKWARD COMPATIBILITY 2012-06-15: |
91 | // Some nasty old helpers send back the entire input line including extra format keys. | |
92 | // This is especially bad for simple perl search-replace filter scripts. | |
93 | // | |
94 | // * trim all but the first word off the response. | |
95 | // * warn once every 50 responses that this will stop being fixed-up soon. | |
96 | // | |
ddc77a2e CT |
97 | if (reply.other().hasContent()) { |
98 | const char * res = reply.other().content(); | |
99 | size_t replySize = 0; | |
e166785a AJ |
100 | if (const char *t = strchr(res, ' ')) { |
101 | static int warn = 0; | |
d816f28d | 102 | debugs(61, (!(warn++%50)? DBG_CRITICAL:2), "WARNING: UPGRADE: URL rewriter reponded with garbage '" << t << |
dacb64b9 | 103 | "'. Future Squid will treat this as part of the URL."); |
ddc77a2e CT |
104 | replySize = t - res; |
105 | } else | |
106 | replySize = reply.other().contentSize(); | |
d06e17ea AJ |
107 | |
108 | // if we still have anything in other() after all that | |
109 | // parse it into status=, url= and rewrite-url= keys | |
ddc77a2e | 110 | if (replySize) { |
ddc77a2e CT |
111 | MemBuf replyBuffer; |
112 | replyBuffer.init(replySize, replySize); | |
113 | replyBuffer.append(reply.other().content(), reply.other().contentSize()); | |
114 | char * result = replyBuffer.content(); | |
d06e17ea | 115 | |
24438ec5 | 116 | Helper::Reply newReply; |
95c0dbfc | 117 | // BACKWARD COMPATIBILITY 2012-06-15: |
2428ce02 AJ |
118 | // We got Helper::Unknown reply result but new |
119 | // RedirectStateData handlers require Helper::Okay, | |
d74740bd | 120 | // else will drop the helper reply |
2428ce02 | 121 | newReply.result = Helper::Okay; |
95c0dbfc | 122 | newReply.notes.append(&reply.notes); |
d06e17ea | 123 | |
d074f918 TT |
124 | // check and parse for obsoleted Squid-2 urlgroup feature |
125 | if (*result == '!') { | |
75e71988 A |
126 | static int urlgroupWarning = 0; |
127 | if (!urlgroupWarning++) | |
d816f28d | 128 | debugs(85, DBG_IMPORTANT, "WARNING: UPGRADE: URL rewriter using obsolete Squid-2 urlgroup feature needs updating."); |
75e71988 A |
129 | if (char *t = strchr(result+1, '!')) { |
130 | *t = '\0'; | |
131 | newReply.notes.add("urlgroup", result+1); | |
132 | result = t + 1; | |
133 | } | |
d074f918 TT |
134 | } |
135 | ||
136 | const Http::StatusCode status = static_cast<Http::StatusCode>(atoi(result)); | |
137 | ||
955394ce | 138 | if (status == Http::scMovedPermanently |
f11c8e2f | 139 | || status == Http::scFound |
955394ce AJ |
140 | || status == Http::scSeeOther |
141 | || status == Http::scPermanentRedirect | |
142 | || status == Http::scTemporaryRedirect) { | |
d06e17ea AJ |
143 | |
144 | if (const char *t = strchr(result, ':')) { | |
145 | char statusBuf[4]; | |
146 | snprintf(statusBuf, sizeof(statusBuf),"%3u",status); | |
147 | newReply.notes.add("status", statusBuf); | |
148 | ++t; | |
149 | // TODO: validate the URL produced here is RFC 2616 compliant URI | |
150 | newReply.notes.add("url", t); | |
151 | } else { | |
152 | debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid " << status << " redirect Location: " << result); | |
153 | } | |
154 | } else { | |
155 | // status code is not a redirect code (or does not exist) | |
156 | // treat as a re-write URL request | |
157 | // TODO: validate the URL produced here is RFC 2616 compliant URI | |
d074f918 TT |
158 | if (*result) |
159 | newReply.notes.add("rewrite-url", result); | |
d06e17ea AJ |
160 | } |
161 | ||
162 | void *cbdata; | |
163 | if (cbdataReferenceValidDone(r->data, &cbdata)) | |
164 | r->handler(cbdata, newReply); | |
165 | ||
dd1efef8 | 166 | delete r; |
d06e17ea AJ |
167 | return; |
168 | } | |
e166785a | 169 | } |
c68e9c6b | 170 | } |
62e76326 | 171 | |
e166785a | 172 | void *cbdata; |
fa80a8ef | 173 | if (cbdataReferenceValidDone(r->data, &cbdata)) |
62e76326 | 174 | r->handler(cbdata, reply); |
175 | ||
dd1efef8 | 176 | delete r; |
30a4f2a8 | 177 | } |
178 | ||
a8a0b1c2 | 179 | static void |
24438ec5 | 180 | storeIdHandleReply(void *data, const Helper::Reply &reply) |
a8a0b1c2 | 181 | { |
dd1efef8 | 182 | RedirectStateData *r = static_cast<RedirectStateData *>(data); |
a8a0b1c2 EC |
183 | debugs(61, 5,"StoreId helper: reply=" << reply); |
184 | ||
185 | // XXX: This function is now kept only to check for and display the garbage use-case | |
186 | // and to map the old helper response format(s) into new format result code and key=value pairs | |
187 | // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format | |
188 | void *cbdata; | |
189 | if (cbdataReferenceValidDone(r->data, &cbdata)) | |
190 | r->handler(cbdata, reply); | |
191 | ||
dd1efef8 | 192 | delete r; |
e7a22b88 | 193 | } |
194 | ||
b8d8561b | 195 | static void |
74addf6c | 196 | redirectStats(StoreEntry * sentry) |
30a4f2a8 | 197 | { |
aee3523a | 198 | if (redirectors == nullptr) { |
587609e9 | 199 | storeAppendPrintf(sentry, "No redirectors defined\n"); |
200 | return; | |
201 | } | |
202 | ||
bf3e8d5a | 203 | redirectors->packStatsInto(sentry, "Redirector Statistics"); |
62e76326 | 204 | |
07476a7f | 205 | if (Config.onoff.redirector_bypass) |
62e76326 | 206 | storeAppendPrintf(sentry, "\nNumber of requests bypassed " |
a8a0b1c2 | 207 | "because all redirectors were busy: %d\n", redirectorBypassed); |
30a4f2a8 | 208 | } |
209 | ||
a8a0b1c2 EC |
210 | static void |
211 | storeIdStats(StoreEntry * sentry) | |
212 | { | |
aee3523a | 213 | if (storeIds == nullptr) { |
a8a0b1c2 EC |
214 | storeAppendPrintf(sentry, "No StoreId helpers defined\n"); |
215 | return; | |
216 | } | |
d2af9477 | 217 | |
bf3e8d5a | 218 | storeIds->packStatsInto(sentry, "StoreId helper Statistics"); |
a8a0b1c2 EC |
219 | |
220 | if (Config.onoff.store_id_bypass) | |
221 | storeAppendPrintf(sentry, "\nNumber of requests bypassed " | |
222 | "because all StoreId helpers were busy: %d\n", storeIdBypassed); | |
223 | } | |
224 | ||
225 | static void | |
e05a9d51 | 226 | constructHelperQuery(const char * const name, const Helper::Client::Pointer &hlp, HLPCB * const replyHandler, ClientHttpRequest * const http, HLPCB * const handler, void * const data, Format::Format * const requestExtrasFmt) |
d2af9477 | 227 | { |
7561850e AJ |
228 | char buf[MAX_REDIRECTOR_REQUEST_STRLEN]; |
229 | int sz; | |
955394ce | 230 | Http::StatusCode status; |
62e76326 | 231 | |
a8a0b1c2 | 232 | /** TODO: create a standalone method to initialize |
dd1efef8 | 233 | * the RedirectStateData for all the helpers. |
a8a0b1c2 | 234 | */ |
dd1efef8 | 235 | RedirectStateData *r = new RedirectStateData(http->uri); |
d2af9477 | 236 | r->handler = handler; |
fa80a8ef | 237 | r->data = cbdataReference(data); |
62e76326 | 238 | |
b11724bb CT |
239 | static MemBuf requestExtras; |
240 | requestExtras.reset(); | |
241 | if (requestExtrasFmt) | |
242 | requestExtrasFmt->assemble(requestExtras, http->al, 0); | |
243 | ||
244 | sz = snprintf(buf, MAX_REDIRECTOR_REQUEST_STRLEN, "%s%s%s\n", | |
dd1efef8 | 245 | r->orig_url.c_str(), |
b11724bb CT |
246 | requestExtras.hasContent() ? " " : "", |
247 | requestExtras.hasContent() ? requestExtras.content() : ""); | |
7561850e AJ |
248 | |
249 | if ((sz<=0) || (sz>=MAX_REDIRECTOR_REQUEST_STRLEN)) { | |
e9f8ec2d | 250 | if (sz<=0) { |
955394ce | 251 | status = Http::scInternalServerError; |
a8a0b1c2 | 252 | debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Can not build request to be passed to " << name << ". Request ABORTED."); |
e9f8ec2d | 253 | } else { |
f11c8e2f | 254 | status = Http::scUriTooLong; |
a8a0b1c2 | 255 | debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Request passed to " << name << " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN << "). Request ABORTED."); |
e9f8ec2d A |
256 | } |
257 | ||
258 | clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data; | |
259 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); | |
260 | assert (repContext); | |
e9f8ec2d | 261 | repContext->setReplyToError(ERR_GATEWAY_FAILURE, status, |
eb026889 | 262 | nullptr, |
7976fed3 | 263 | http->getConn(), |
e9f8ec2d | 264 | http->request, |
aee3523a | 265 | nullptr, |
2f1431ea | 266 | #if USE_AUTH |
aee3523a | 267 | http->getConn() != nullptr && http->getConn()->getAuth() != nullptr ? |
cc1e110a | 268 | http->getConn()->getAuth() : http->request->auth_user_request); |
2f1431ea | 269 | #else |
a1b1756c | 270 | nullptr); |
2f1431ea | 271 | #endif |
e9f8ec2d A |
272 | |
273 | node = (clientStreamNode *)http->client_stream.tail->data; | |
274 | clientStreamRead(node, http, node->readBuffer); | |
275 | return; | |
7561850e | 276 | } |
62e76326 | 277 | |
bf95c10a | 278 | debugs(61,6, "sending '" << buf << "' to the " << name << " helper"); |
a8a0b1c2 EC |
279 | helperSubmit(hlp, buf, replyHandler, r); |
280 | } | |
281 | ||
282 | /**** PUBLIC FUNCTIONS ****/ | |
283 | ||
284 | void | |
285 | redirectStart(ClientHttpRequest * http, HLPCB * handler, void *data) | |
286 | { | |
287 | assert(http); | |
288 | assert(handler); | |
289 | debugs(61, 5, "redirectStart: '" << http->uri << "'"); | |
290 | ||
6082a0e2 EB |
291 | // TODO: Deprecate Config.onoff.redirector_bypass in favor of either |
292 | // onPersistentOverload or a new onOverload option that applies to all helpers. | |
293 | if (Config.onoff.redirector_bypass && redirectors->willOverload()) { | |
6825b101 | 294 | /* Skip redirector if the queue is full */ |
a8a0b1c2 | 295 | ++redirectorBypassed; |
24438ec5 | 296 | Helper::Reply bypassReply; |
2428ce02 | 297 | bypassReply.result = Helper::Okay; |
a8a0b1c2 EC |
298 | bypassReply.notes.add("message","URL rewrite/redirect queue too long. Bypassed."); |
299 | handler(data, bypassReply); | |
300 | return; | |
301 | } | |
302 | ||
b11724bb | 303 | constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data, redirectorExtrasFmt); |
a8a0b1c2 EC |
304 | } |
305 | ||
306 | /** | |
307 | * Handles the StoreID feature helper starting. | |
308 | * For now it cannot be done using the redirectStart method. | |
309 | */ | |
310 | void | |
311 | storeIdStart(ClientHttpRequest * http, HLPCB * handler, void *data) | |
312 | { | |
313 | assert(http); | |
314 | assert(handler); | |
315 | debugs(61, 5, "storeIdStart: '" << http->uri << "'"); | |
316 | ||
6082a0e2 | 317 | if (Config.onoff.store_id_bypass && storeIds->willOverload()) { |
6825b101 | 318 | /* Skip StoreID Helper if the queue is full */ |
a8a0b1c2 | 319 | ++storeIdBypassed; |
24438ec5 | 320 | Helper::Reply bypassReply; |
a8a0b1c2 | 321 | |
2428ce02 | 322 | bypassReply.result = Helper::Okay; |
a8a0b1c2 EC |
323 | |
324 | bypassReply.notes.add("message","StoreId helper queue too long. Bypassed."); | |
325 | handler(data, bypassReply); | |
326 | return; | |
327 | } | |
328 | ||
b11724bb | 329 | constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data, storeIdExtrasFmt); |
0a21bd84 | 330 | } |
331 | ||
b8d8561b | 332 | void |
74addf6c | 333 | redirectInit(void) |
30a4f2a8 | 334 | { |
dd1efef8 | 335 | static bool init = false; |
d120ed12 | 336 | |
dd1efef8 AJ |
337 | if (!init) { |
338 | Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats, 0, 1); | |
339 | Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats, 0, 1); | |
340 | } | |
62e76326 | 341 | |
a8a0b1c2 | 342 | if (Config.Program.redirect) { |
62e76326 | 343 | |
aee3523a | 344 | if (redirectors == nullptr) |
e05a9d51 | 345 | redirectors = Helper::Client::Make("redirector"); |
62e76326 | 346 | |
a8a0b1c2 | 347 | redirectors->cmdline = Config.Program.redirect; |
07eca7e0 | 348 | |
6825b101 CT |
349 | // BACKWARD COMPATIBILITY: |
350 | // if redirectot_bypass is set then use queue_size=0 as default size | |
351 | if (Config.onoff.redirector_bypass && Config.redirectChildren.defaultQueueSize) | |
352 | Config.redirectChildren.queue_size = 0; | |
353 | ||
a8a0b1c2 | 354 | redirectors->childs.updateLimits(Config.redirectChildren); |
62e76326 | 355 | |
a8a0b1c2 EC |
356 | redirectors->ipc_type = IPC_STREAM; |
357 | ||
32fd6d8a CT |
358 | redirectors->timeout = Config.Timeout.urlRewrite; |
359 | ||
360 | redirectors->retryTimedOut = (Config.onUrlRewriteTimeout.action == toutActRetry); | |
361 | redirectors->retryBrokenHelper = true; // XXX: make this configurable ? | |
362 | redirectors->onTimedOutResponse.clear(); | |
363 | if (Config.onUrlRewriteTimeout.action == toutActUseConfiguredResponse) | |
364 | redirectors->onTimedOutResponse.assign(Config.onUrlRewriteTimeout.response); | |
365 | ||
bd71920d | 366 | redirectors->openSessions(); |
a8a0b1c2 EC |
367 | } |
368 | ||
369 | if (Config.Program.store_id) { | |
370 | ||
aee3523a | 371 | if (storeIds == nullptr) |
e05a9d51 | 372 | storeIds = Helper::Client::Make("store_id"); |
a8a0b1c2 EC |
373 | |
374 | storeIds->cmdline = Config.Program.store_id; | |
375 | ||
6825b101 CT |
376 | // BACKWARD COMPATIBILITY: |
377 | // if store_id_bypass is set then use queue_size=0 as default size | |
378 | if (Config.onoff.store_id_bypass && Config.storeIdChildren.defaultQueueSize) | |
379 | Config.storeIdChildren.queue_size = 0; | |
380 | ||
a8a0b1c2 EC |
381 | storeIds->childs.updateLimits(Config.storeIdChildren); |
382 | ||
383 | storeIds->ipc_type = IPC_STREAM; | |
384 | ||
32fd6d8a CT |
385 | storeIds->retryBrokenHelper = true; // XXX: make this configurable ? |
386 | ||
bd71920d | 387 | storeIds->openSessions(); |
a8a0b1c2 | 388 | } |
62e76326 | 389 | |
b11724bb | 390 | if (Config.redirector_extras) { |
78338384 | 391 | delete redirectorExtrasFmt; |
fe7966ec | 392 | redirectorExtrasFmt = new ::Format::Format("url_rewrite_extras"); |
b11724bb CT |
393 | (void)redirectorExtrasFmt->parse(Config.redirector_extras); |
394 | } | |
395 | ||
396 | if (Config.storeId_extras) { | |
78338384 | 397 | delete storeIdExtrasFmt; |
fe7966ec | 398 | storeIdExtrasFmt = new ::Format::Format("store_id_extras"); |
b11724bb CT |
399 | (void)storeIdExtrasFmt->parse(Config.storeId_extras); |
400 | } | |
401 | ||
dd1efef8 | 402 | init = true; |
30a4f2a8 | 403 | } |
404 | ||
16d3fe0d | 405 | void |
74addf6c | 406 | redirectShutdown(void) |
30a4f2a8 | 407 | { |
9837567d | 408 | /** TODO: Temporary unified helpers Shutdown |
a8a0b1c2 EC |
409 | * When and if needed for more helpers a separated shutdown |
410 | * method will be added for each of them. | |
411 | */ | |
a8a0b1c2 EC |
412 | if (redirectors) |
413 | helperShutdown(redirectors); | |
414 | ||
415 | if (storeIds) | |
416 | helperShutdown(storeIds); | |
62e76326 | 417 | |
838b993c | 418 | if (!shutting_down) |
62e76326 | 419 | return; |
420 | ||
aee3523a | 421 | redirectors = nullptr; |
a8a0b1c2 | 422 | |
aee3523a | 423 | storeIds = nullptr; |
a8a0b1c2 | 424 | |
b11724bb | 425 | delete redirectorExtrasFmt; |
aee3523a | 426 | redirectorExtrasFmt = nullptr; |
b11724bb CT |
427 | |
428 | delete storeIdExtrasFmt; | |
aee3523a | 429 | storeIdExtrasFmt = nullptr; |
30a4f2a8 | 430 | } |
f53969cc | 431 | |
23da195f CT |
432 | void |
433 | redirectReconfigure() | |
434 | { | |
435 | redirectShutdown(); | |
436 | redirectInit(); | |
437 | } | |
45e49ce3 | 438 |