]> git.ipfire.org Git - thirdparty/squid.git/blob - src/redirect.cc
Add url_rewrite_extras and store_id_extras for redirector and store_id helpers
[thirdparty/squid.git] / src / redirect.cc
1 /*
2 * DEBUG: section 61 Redirector
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 "acl/Checklist.h"
35 #include "client_side.h"
36 #include "client_side_reply.h"
37 #include "client_side_request.h"
38 #include "comm/Connection.h"
39 #include "fde.h"
40 #include "fqdncache.h"
41 #include "format/Format.h"
42 #include "globals.h"
43 #include "HttpRequest.h"
44 #include "mgr/Registration.h"
45 #include "redirect.h"
46 #include "rfc1738.h"
47 #include "SBuf.h"
48 #include "SquidConfig.h"
49 #include "Store.h"
50 #if USE_AUTH
51 #include "auth/UserRequest.h"
52 #endif
53 #if USE_SSL
54 #include "ssl/support.h"
55 #endif
56
57 /// url maximum lengh + extra informations passed to redirector
58 #define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024)
59
60 class RedirectStateData
61 {
62 public:
63 explicit RedirectStateData(const char *url);
64 ~RedirectStateData();
65
66 void *data;
67 SBuf orig_url;
68
69 Ip::Address client_addr;
70 const char *client_ident;
71 const char *method_s;
72 HLPCB *handler;
73
74 private:
75 CBDATA_CLASS2(RedirectStateData);
76 };
77
78 static HLPCB redirectHandleReply;
79 static HLPCB storeIdHandleReply;
80 static helper *redirectors = NULL;
81 static helper *storeIds = NULL;
82 static OBJH redirectStats;
83 static OBJH storeIdStats;
84 static int redirectorBypassed = 0;
85 static int storeIdBypassed = 0;
86 static Format::Format *redirectorExtrasFmt = NULL;
87 static Format::Format *storeIdExtrasFmt = NULL;
88
89 CBDATA_CLASS_INIT(RedirectStateData);
90
91 RedirectStateData::RedirectStateData(const char *url) :
92 data(NULL),
93 orig_url(url),
94 client_addr(),
95 client_ident(NULL),
96 method_s(NULL),
97 handler(NULL)
98 {
99 }
100
101 RedirectStateData::~RedirectStateData()
102 {
103 }
104
105 static void
106 redirectHandleReply(void *data, const HelperReply &reply)
107 {
108 RedirectStateData *r = static_cast<RedirectStateData *>(data);
109 debugs(61, 5, HERE << "reply=" << reply);
110
111 // XXX: This function is now kept only to check for and display the garbage use-case
112 // and to map the old helper response format(s) into new format result code and key=value pairs
113 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
114
115 if (reply.result == HelperReply::Unknown) {
116 // BACKWARD COMPATIBILITY 2012-06-15:
117 // Some nasty old helpers send back the entire input line including extra format keys.
118 // This is especially bad for simple perl search-replace filter scripts.
119 //
120 // * trim all but the first word off the response.
121 // * warn once every 50 responses that this will stop being fixed-up soon.
122 //
123 if (const char * res = reply.other().content()) {
124 if (const char *t = strchr(res, ' ')) {
125 static int warn = 0;
126 debugs(61, (!(warn++%50)? DBG_CRITICAL:2), "UPGRADE WARNING: URL rewriter reponded with garbage '" << t <<
127 "'. Future Squid will treat this as part of the URL.");
128 const mb_size_t garbageLength = reply.other().contentSize() - (t-res);
129 reply.modifiableOther().truncate(garbageLength);
130 }
131 if (reply.other().hasContent() && *res == '\0')
132 reply.modifiableOther().clean(); // drop the whole buffer of garbage.
133
134 // if we still have anything in other() after all that
135 // parse it into status=, url= and rewrite-url= keys
136 if (reply.other().hasContent()) {
137 /* 2012-06-28: This cast is due to urlParse() truncating too-long URLs itself.
138 * At this point altering the helper buffer in that way is not harmful, but annoying.
139 * When Bug 1961 is resolved and urlParse has a const API, this needs to die.
140 */
141 const char * result = reply.other().content();
142 const Http::StatusCode status = static_cast<Http::StatusCode>(atoi(result));
143
144 HelperReply newReply;
145 // BACKWARD COMPATIBILITY 2012-06-15:
146 // We got HelperReply::Unknown reply result but new
147 // RedirectStateData handlers require HelperReply::Okay,
148 // else will drop the helper reply
149 newReply.result = HelperReply::Okay;
150 newReply.notes.append(&reply.notes);
151
152 if (status == Http::scMovedPermanently
153 || status == Http::scMovedTemporarily
154 || status == Http::scSeeOther
155 || status == Http::scPermanentRedirect
156 || status == Http::scTemporaryRedirect) {
157
158 if (const char *t = strchr(result, ':')) {
159 char statusBuf[4];
160 snprintf(statusBuf, sizeof(statusBuf),"%3u",status);
161 newReply.notes.add("status", statusBuf);
162 ++t;
163 // TODO: validate the URL produced here is RFC 2616 compliant URI
164 newReply.notes.add("url", t);
165 } else {
166 debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid " << status << " redirect Location: " << result);
167 }
168 } else {
169 // status code is not a redirect code (or does not exist)
170 // treat as a re-write URL request
171 // TODO: validate the URL produced here is RFC 2616 compliant URI
172 newReply.notes.add("rewrite-url", reply.other().content());
173 }
174
175 void *cbdata;
176 if (cbdataReferenceValidDone(r->data, &cbdata))
177 r->handler(cbdata, newReply);
178
179 delete r;
180 return;
181 }
182 }
183 }
184
185 void *cbdata;
186 if (cbdataReferenceValidDone(r->data, &cbdata))
187 r->handler(cbdata, reply);
188
189 delete r;
190 }
191
192 static void
193 storeIdHandleReply(void *data, const HelperReply &reply)
194 {
195 RedirectStateData *r = static_cast<RedirectStateData *>(data);
196 debugs(61, 5,"StoreId helper: reply=" << reply);
197
198 // XXX: This function is now kept only to check for and display the garbage use-case
199 // and to map the old helper response format(s) into new format result code and key=value pairs
200 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
201 void *cbdata;
202 if (cbdataReferenceValidDone(r->data, &cbdata))
203 r->handler(cbdata, reply);
204
205 delete r;
206 }
207
208 static void
209 redirectStats(StoreEntry * sentry)
210 {
211 if (redirectors == NULL) {
212 storeAppendPrintf(sentry, "No redirectors defined\n");
213 return;
214 }
215
216 helperStats(sentry, redirectors, "Redirector Statistics");
217
218 if (Config.onoff.redirector_bypass)
219 storeAppendPrintf(sentry, "\nNumber of requests bypassed "
220 "because all redirectors were busy: %d\n", redirectorBypassed);
221 }
222
223 static void
224 storeIdStats(StoreEntry * sentry)
225 {
226 if (storeIds == NULL) {
227 storeAppendPrintf(sentry, "No StoreId helpers defined\n");
228 return;
229 }
230
231 helperStats(sentry, storeIds, "StoreId helper Statistics");
232
233 if (Config.onoff.store_id_bypass)
234 storeAppendPrintf(sentry, "\nNumber of requests bypassed "
235 "because all StoreId helpers were busy: %d\n", storeIdBypassed);
236 }
237
238 static void
239 constructHelperQuery(const char *name, helper *hlp, HLPCB *replyHandler, ClientHttpRequest * http, HLPCB *handler, void *data, Format::Format *requestExtrasFmt)
240 {
241 ConnStateData * conn = http->getConn();
242 const char *fqdn;
243 char buf[MAX_REDIRECTOR_REQUEST_STRLEN];
244 int sz;
245 Http::StatusCode status;
246
247 /** TODO: create a standalone method to initialize
248 * the RedirectStateData for all the helpers.
249 */
250 RedirectStateData *r = new RedirectStateData(http->uri);
251 if (conn != NULL)
252 r->client_addr = conn->log_addr;
253 else
254 r->client_addr.setNoAddr();
255 r->client_ident = NULL;
256 #if USE_AUTH
257 if (http->request->auth_user_request != NULL) {
258 r->client_ident = http->request->auth_user_request->username();
259 debugs(61, 5, HERE << "auth-user=" << (r->client_ident?r->client_ident:"NULL"));
260 }
261 #endif
262
263 if (!r->client_ident && http->request->extacl_user.size() > 0) {
264 r->client_ident = http->request->extacl_user.termedBuf();
265 debugs(61, 5, HERE << "acl-user=" << (r->client_ident?r->client_ident:"NULL"));
266 }
267
268 if (!r->client_ident && conn != NULL && conn->clientConnection != NULL && conn->clientConnection->rfc931[0]) {
269 r->client_ident = conn->clientConnection->rfc931;
270 debugs(61, 5, HERE << "ident-user=" << (r->client_ident?r->client_ident:"NULL"));
271 }
272
273 #if USE_SSL
274
275 if (!r->client_ident && conn != NULL && Comm::IsConnOpen(conn->clientConnection)) {
276 r->client_ident = sslGetUserEmail(fd_table[conn->clientConnection->fd].ssl);
277 debugs(61, 5, HERE << "ssl-user=" << (r->client_ident?r->client_ident:"NULL"));
278 }
279 #endif
280
281 if (!r->client_ident)
282 r->client_ident = dash_str;
283
284 r->method_s = RequestMethodStr(http->request->method);
285
286 r->handler = handler;
287
288 r->data = cbdataReference(data);
289
290 if ((fqdn = fqdncache_gethostbyaddr(r->client_addr, 0)) == NULL)
291 fqdn = dash_str;
292
293 static MemBuf requestExtras;
294 requestExtras.reset();
295 if (requestExtrasFmt)
296 requestExtrasFmt->assemble(requestExtras, http->al, 0);
297
298 sz = snprintf(buf, MAX_REDIRECTOR_REQUEST_STRLEN, "%s%s%s\n",
299 r->orig_url.c_str(),
300 requestExtras.hasContent() ? " " : "",
301 requestExtras.hasContent() ? requestExtras.content() : "");
302
303 if ((sz<=0) || (sz>=MAX_REDIRECTOR_REQUEST_STRLEN)) {
304 if (sz<=0) {
305 status = Http::scInternalServerError;
306 debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Can not build request to be passed to " << name << ". Request ABORTED.");
307 } else {
308 status = Http::scRequestUriTooLarge;
309 debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Request passed to " << name << " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN << "). Request ABORTED.");
310 }
311
312 clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data;
313 clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
314 assert (repContext);
315 Ip::Address tmpnoaddr;
316 tmpnoaddr.setNoAddr();
317 repContext->setReplyToError(ERR_GATEWAY_FAILURE, status,
318 http->request->method, NULL,
319 http->getConn() != NULL && http->getConn()->clientConnection != NULL ?
320 http->getConn()->clientConnection->remote : tmpnoaddr,
321 http->request,
322 NULL,
323 #if USE_AUTH
324 http->getConn() != NULL && http->getConn()->getAuth() != NULL ?
325 http->getConn()->getAuth() : http->request->auth_user_request);
326 #else
327 NULL);
328 #endif
329
330 node = (clientStreamNode *)http->client_stream.tail->data;
331 clientStreamRead(node, http, node->readBuffer);
332 return;
333 }
334
335 debugs(61,6, HERE << "sending '" << buf << "' to the " << name << " helper");
336 helperSubmit(hlp, buf, replyHandler, r);
337 }
338
339 /**** PUBLIC FUNCTIONS ****/
340
341 void
342 redirectStart(ClientHttpRequest * http, HLPCB * handler, void *data)
343 {
344 assert(http);
345 assert(handler);
346 debugs(61, 5, "redirectStart: '" << http->uri << "'");
347
348 if (Config.onoff.redirector_bypass && redirectors->stats.queue_size) {
349 /* Skip redirector if there is one request queued */
350 ++redirectorBypassed;
351 HelperReply bypassReply;
352 bypassReply.result = HelperReply::Okay;
353 bypassReply.notes.add("message","URL rewrite/redirect queue too long. Bypassed.");
354 handler(data, bypassReply);
355 return;
356 }
357
358 constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data, redirectorExtrasFmt);
359 }
360
361 /**
362 * Handles the StoreID feature helper starting.
363 * For now it cannot be done using the redirectStart method.
364 */
365 void
366 storeIdStart(ClientHttpRequest * http, HLPCB * handler, void *data)
367 {
368 assert(http);
369 assert(handler);
370 debugs(61, 5, "storeIdStart: '" << http->uri << "'");
371
372 if (Config.onoff.store_id_bypass && storeIds->stats.queue_size) {
373 /* Skip StoreID Helper if there is one request queued */
374 ++storeIdBypassed;
375 HelperReply bypassReply;
376
377 bypassReply.result = HelperReply::Okay;
378
379 bypassReply.notes.add("message","StoreId helper queue too long. Bypassed.");
380 handler(data, bypassReply);
381 return;
382 }
383
384 constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data, storeIdExtrasFmt);
385 }
386
387 void
388 redirectInit(void)
389 {
390 static bool init = false;
391
392 if (!init) {
393 Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats, 0, 1);
394 Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats, 0, 1);
395 }
396
397 if (Config.Program.redirect) {
398
399 if (redirectors == NULL)
400 redirectors = new helper("redirector");
401
402 redirectors->cmdline = Config.Program.redirect;
403
404 redirectors->childs.updateLimits(Config.redirectChildren);
405
406 redirectors->ipc_type = IPC_STREAM;
407
408 helperOpenServers(redirectors);
409 }
410
411 if (Config.Program.store_id) {
412
413 if (storeIds == NULL)
414 storeIds = new helper("store_id");
415
416 storeIds->cmdline = Config.Program.store_id;
417
418 storeIds->childs.updateLimits(Config.storeIdChildren);
419
420 storeIds->ipc_type = IPC_STREAM;
421
422 helperOpenServers(storeIds);
423 }
424
425 if (Config.redirector_extras) {
426 redirectorExtrasFmt = new ::Format::Format("redirecor_extras");
427 (void)redirectorExtrasFmt->parse(Config.redirector_extras);
428 }
429
430 if (Config.storeId_extras) {
431 storeIdExtrasFmt = new ::Format::Format("storeId_extras");
432 (void)storeIdExtrasFmt->parse(Config.storeId_extras);
433 }
434
435 init = true;
436 }
437
438 void
439 redirectShutdown(void)
440 {
441 /** FIXME: Temporary unified helpers Shutdown
442 * When and if needed for more helpers a separated shutdown
443 * method will be added for each of them.
444 */
445 if (!storeIds && !redirectors)
446 return;
447
448 if (redirectors)
449 helperShutdown(redirectors);
450
451 if (storeIds)
452 helperShutdown(storeIds);
453
454 if (!shutting_down)
455 return;
456
457 delete redirectors;
458 redirectors = NULL;
459
460 delete storeIds;
461 storeIds = NULL;
462
463 delete redirectorExtrasFmt;
464 redirectorExtrasFmt = NULL;
465
466 delete storeIdExtrasFmt;
467 storeIdExtrasFmt = NULL;
468 }