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