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