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