]>
Commit | Line | Data |
---|---|---|
30a4f2a8 | 1 | /* |
bbc27441 | 2 | * Copyright (C) 1996-2014 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" | |
582c2af2 | 23 | #include "HttpRequest.h" |
582c2af2 | 24 | #include "mgr/Registration.h" |
c548327a | 25 | #include "redirect.h" |
1fa9b1a7 | 26 | #include "rfc1738.h" |
dd1efef8 | 27 | #include "SBuf.h" |
4d5904f7 | 28 | #include "SquidConfig.h" |
582c2af2 FC |
29 | #include "Store.h" |
30 | #if USE_AUTH | |
31 | #include "auth/UserRequest.h" | |
32 | #endif | |
cb4f4424 | 33 | #if USE_OPENSSL |
4db984be CT |
34 | #include "ssl/support.h" |
35 | #endif | |
30a4f2a8 | 36 | |
7561850e AJ |
37 | /// url maximum lengh + extra informations passed to redirector |
38 | #define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024) | |
39 | ||
dd1efef8 AJ |
40 | class RedirectStateData |
41 | { | |
5c2f68b7 AJ |
42 | CBDATA_CLASS(RedirectStateData); |
43 | ||
dd1efef8 AJ |
44 | public: |
45 | explicit RedirectStateData(const char *url); | |
46 | ~RedirectStateData(); | |
47 | ||
d2af9477 | 48 | void *data; |
dd1efef8 | 49 | SBuf orig_url; |
62e76326 | 50 | |
e166785a | 51 | HLPCB *handler; |
dd1efef8 | 52 | }; |
30a4f2a8 | 53 | |
74addf6c | 54 | static HLPCB redirectHandleReply; |
a8a0b1c2 | 55 | static HLPCB storeIdHandleReply; |
74addf6c | 56 | static helper *redirectors = NULL; |
a8a0b1c2 | 57 | static helper *storeIds = NULL; |
74addf6c | 58 | static OBJH redirectStats; |
a8a0b1c2 EC |
59 | static OBJH storeIdStats; |
60 | static int redirectorBypassed = 0; | |
61 | static int storeIdBypassed = 0; | |
b11724bb CT |
62 | static Format::Format *redirectorExtrasFmt = NULL; |
63 | static Format::Format *storeIdExtrasFmt = NULL; | |
dd1efef8 AJ |
64 | |
65 | CBDATA_CLASS_INIT(RedirectStateData); | |
66 | ||
67 | RedirectStateData::RedirectStateData(const char *url) : | |
f53969cc SM |
68 | data(NULL), |
69 | orig_url(url), | |
70 | handler(NULL) | |
dd1efef8 AJ |
71 | { |
72 | } | |
73 | ||
74 | RedirectStateData::~RedirectStateData() | |
75 | { | |
76 | } | |
30a4f2a8 | 77 | |
582b6456 | 78 | static void |
24438ec5 | 79 | redirectHandleReply(void *data, const Helper::Reply &reply) |
30a4f2a8 | 80 | { |
dd1efef8 | 81 | RedirectStateData *r = static_cast<RedirectStateData *>(data); |
e166785a AJ |
82 | debugs(61, 5, HERE << "reply=" << reply); |
83 | ||
d06e17ea AJ |
84 | // XXX: This function is now kept only to check for and display the garbage use-case |
85 | // and to map the old helper response format(s) into new format result code and key=value pairs | |
fd7f26ea | 86 | // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format |
e166785a | 87 | |
2428ce02 | 88 | if (reply.result == Helper::Unknown) { |
e166785a AJ |
89 | // BACKWARD COMPATIBILITY 2012-06-15: |
90 | // Some nasty old helpers send back the entire input line including extra format keys. | |
91 | // This is especially bad for simple perl search-replace filter scripts. | |
92 | // | |
93 | // * trim all but the first word off the response. | |
94 | // * warn once every 50 responses that this will stop being fixed-up soon. | |
95 | // | |
96 | if (const char * res = reply.other().content()) { | |
97 | if (const char *t = strchr(res, ' ')) { | |
98 | static int warn = 0; | |
99 | debugs(61, (!(warn++%50)? DBG_CRITICAL:2), "UPGRADE WARNING: URL rewriter reponded with garbage '" << t << | |
dacb64b9 | 100 | "'. Future Squid will treat this as part of the URL."); |
e166785a AJ |
101 | const mb_size_t garbageLength = reply.other().contentSize() - (t-res); |
102 | reply.modifiableOther().truncate(garbageLength); | |
103 | } | |
104 | if (reply.other().hasContent() && *res == '\0') | |
105 | reply.modifiableOther().clean(); // drop the whole buffer of garbage. | |
d06e17ea AJ |
106 | |
107 | // if we still have anything in other() after all that | |
108 | // parse it into status=, url= and rewrite-url= keys | |
109 | if (reply.other().hasContent()) { | |
110 | /* 2012-06-28: This cast is due to urlParse() truncating too-long URLs itself. | |
111 | * At this point altering the helper buffer in that way is not harmful, but annoying. | |
112 | * When Bug 1961 is resolved and urlParse has a const API, this needs to die. | |
113 | */ | |
d074f918 | 114 | char * result = reply.modifiableOther().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++) | |
128 | debugs(85, DBG_IMPORTANT, "UPGRADE WARNING: URL rewriter using obsolete Squid-2 urlgroup feature needs updating."); | |
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 | { |
587609e9 | 198 | if (redirectors == NULL) { |
199 | storeAppendPrintf(sentry, "No redirectors defined\n"); | |
200 | return; | |
201 | } | |
202 | ||
9522b380 | 203 | helperStats(sentry, redirectors, "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 | { | |
213 | if (storeIds == NULL) { | |
214 | storeAppendPrintf(sentry, "No StoreId helpers defined\n"); | |
215 | return; | |
216 | } | |
d2af9477 | 217 | |
a8a0b1c2 EC |
218 | helperStats(sentry, storeIds, "StoreId helper Statistics"); |
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 | |
b11724bb | 226 | constructHelperQuery(const char *name, helper *hlp, HLPCB *replyHandler, ClientHttpRequest * http, HLPCB *handler, void *data, Format::Format *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); | |
261 | Ip::Address tmpnoaddr; | |
4dd643d5 | 262 | tmpnoaddr.setNoAddr(); |
e9f8ec2d A |
263 | repContext->setReplyToError(ERR_GATEWAY_FAILURE, status, |
264 | http->request->method, NULL, | |
1b76e6c1 AJ |
265 | http->getConn() != NULL && http->getConn()->clientConnection != NULL ? |
266 | http->getConn()->clientConnection->remote : tmpnoaddr, | |
e9f8ec2d A |
267 | http->request, |
268 | NULL, | |
2f1431ea | 269 | #if USE_AUTH |
cc1e110a AJ |
270 | http->getConn() != NULL && http->getConn()->getAuth() != NULL ? |
271 | http->getConn()->getAuth() : http->request->auth_user_request); | |
2f1431ea AJ |
272 | #else |
273 | NULL); | |
274 | #endif | |
e9f8ec2d A |
275 | |
276 | node = (clientStreamNode *)http->client_stream.tail->data; | |
277 | clientStreamRead(node, http, node->readBuffer); | |
278 | return; | |
7561850e | 279 | } |
62e76326 | 280 | |
a8a0b1c2 EC |
281 | debugs(61,6, HERE << "sending '" << buf << "' to the " << name << " helper"); |
282 | helperSubmit(hlp, buf, replyHandler, r); | |
283 | } | |
284 | ||
285 | /**** PUBLIC FUNCTIONS ****/ | |
286 | ||
287 | void | |
288 | redirectStart(ClientHttpRequest * http, HLPCB * handler, void *data) | |
289 | { | |
290 | assert(http); | |
291 | assert(handler); | |
292 | debugs(61, 5, "redirectStart: '" << http->uri << "'"); | |
293 | ||
6825b101 CT |
294 | if (Config.onoff.redirector_bypass && redirectors->queueFull()) { |
295 | /* Skip redirector if the queue is full */ | |
a8a0b1c2 | 296 | ++redirectorBypassed; |
24438ec5 | 297 | Helper::Reply bypassReply; |
2428ce02 | 298 | bypassReply.result = Helper::Okay; |
a8a0b1c2 EC |
299 | bypassReply.notes.add("message","URL rewrite/redirect queue too long. Bypassed."); |
300 | handler(data, bypassReply); | |
301 | return; | |
302 | } | |
303 | ||
b11724bb | 304 | constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data, redirectorExtrasFmt); |
a8a0b1c2 EC |
305 | } |
306 | ||
307 | /** | |
308 | * Handles the StoreID feature helper starting. | |
309 | * For now it cannot be done using the redirectStart method. | |
310 | */ | |
311 | void | |
312 | storeIdStart(ClientHttpRequest * http, HLPCB * handler, void *data) | |
313 | { | |
314 | assert(http); | |
315 | assert(handler); | |
316 | debugs(61, 5, "storeIdStart: '" << http->uri << "'"); | |
317 | ||
6825b101 CT |
318 | if (Config.onoff.store_id_bypass && storeIds->queueFull()) { |
319 | /* Skip StoreID Helper if the queue is full */ | |
a8a0b1c2 | 320 | ++storeIdBypassed; |
24438ec5 | 321 | Helper::Reply bypassReply; |
a8a0b1c2 | 322 | |
2428ce02 | 323 | bypassReply.result = Helper::Okay; |
a8a0b1c2 EC |
324 | |
325 | bypassReply.notes.add("message","StoreId helper queue too long. Bypassed."); | |
326 | handler(data, bypassReply); | |
327 | return; | |
328 | } | |
329 | ||
b11724bb | 330 | constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data, storeIdExtrasFmt); |
0a21bd84 | 331 | } |
332 | ||
b8d8561b | 333 | void |
74addf6c | 334 | redirectInit(void) |
30a4f2a8 | 335 | { |
dd1efef8 | 336 | static bool init = false; |
d120ed12 | 337 | |
dd1efef8 AJ |
338 | if (!init) { |
339 | Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats, 0, 1); | |
340 | Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats, 0, 1); | |
341 | } | |
62e76326 | 342 | |
a8a0b1c2 | 343 | if (Config.Program.redirect) { |
62e76326 | 344 | |
a8a0b1c2 EC |
345 | if (redirectors == NULL) |
346 | redirectors = new helper("redirector"); | |
62e76326 | 347 | |
a8a0b1c2 | 348 | redirectors->cmdline = Config.Program.redirect; |
07eca7e0 | 349 | |
6825b101 CT |
350 | // BACKWARD COMPATIBILITY: |
351 | // if redirectot_bypass is set then use queue_size=0 as default size | |
352 | if (Config.onoff.redirector_bypass && Config.redirectChildren.defaultQueueSize) | |
353 | Config.redirectChildren.queue_size = 0; | |
354 | ||
a8a0b1c2 | 355 | redirectors->childs.updateLimits(Config.redirectChildren); |
62e76326 | 356 | |
a8a0b1c2 EC |
357 | redirectors->ipc_type = IPC_STREAM; |
358 | ||
32fd6d8a CT |
359 | redirectors->timeout = Config.Timeout.urlRewrite; |
360 | ||
361 | redirectors->retryTimedOut = (Config.onUrlRewriteTimeout.action == toutActRetry); | |
362 | redirectors->retryBrokenHelper = true; // XXX: make this configurable ? | |
363 | redirectors->onTimedOutResponse.clear(); | |
364 | if (Config.onUrlRewriteTimeout.action == toutActUseConfiguredResponse) | |
365 | redirectors->onTimedOutResponse.assign(Config.onUrlRewriteTimeout.response); | |
366 | ||
a8a0b1c2 EC |
367 | helperOpenServers(redirectors); |
368 | } | |
369 | ||
370 | if (Config.Program.store_id) { | |
371 | ||
372 | if (storeIds == NULL) | |
373 | storeIds = new helper("store_id"); | |
374 | ||
375 | storeIds->cmdline = Config.Program.store_id; | |
376 | ||
6825b101 CT |
377 | // BACKWARD COMPATIBILITY: |
378 | // if store_id_bypass is set then use queue_size=0 as default size | |
379 | if (Config.onoff.store_id_bypass && Config.storeIdChildren.defaultQueueSize) | |
380 | Config.storeIdChildren.queue_size = 0; | |
381 | ||
a8a0b1c2 EC |
382 | storeIds->childs.updateLimits(Config.storeIdChildren); |
383 | ||
384 | storeIds->ipc_type = IPC_STREAM; | |
385 | ||
32fd6d8a CT |
386 | storeIds->retryBrokenHelper = true; // XXX: make this configurable ? |
387 | ||
a8a0b1c2 EC |
388 | helperOpenServers(storeIds); |
389 | } | |
62e76326 | 390 | |
b11724bb | 391 | if (Config.redirector_extras) { |
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) { | |
fe7966ec | 397 | storeIdExtrasFmt = new ::Format::Format("store_id_extras"); |
b11724bb CT |
398 | (void)storeIdExtrasFmt->parse(Config.storeId_extras); |
399 | } | |
400 | ||
dd1efef8 | 401 | init = true; |
30a4f2a8 | 402 | } |
403 | ||
16d3fe0d | 404 | void |
74addf6c | 405 | redirectShutdown(void) |
30a4f2a8 | 406 | { |
a8a0b1c2 EC |
407 | /** FIXME: Temporary unified helpers Shutdown |
408 | * When and if needed for more helpers a separated shutdown | |
409 | * method will be added for each of them. | |
410 | */ | |
411 | if (!storeIds && !redirectors) | |
62e76326 | 412 | return; |
413 | ||
a8a0b1c2 EC |
414 | if (redirectors) |
415 | helperShutdown(redirectors); | |
416 | ||
417 | if (storeIds) | |
418 | helperShutdown(storeIds); | |
62e76326 | 419 | |
838b993c | 420 | if (!shutting_down) |
62e76326 | 421 | return; |
422 | ||
48d54e4d | 423 | delete redirectors; |
1f5f60dd | 424 | redirectors = NULL; |
a8a0b1c2 EC |
425 | |
426 | delete storeIds; | |
427 | storeIds = NULL; | |
428 | ||
b11724bb CT |
429 | delete redirectorExtrasFmt; |
430 | redirectorExtrasFmt = NULL; | |
431 | ||
432 | delete storeIdExtrasFmt; | |
433 | storeIdExtrasFmt = NULL; | |
30a4f2a8 | 434 | } |
f53969cc | 435 |