2 * DEBUG: section 61 Redirector
3 * AUTHOR: Duane Wessels
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
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.
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.
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.
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.
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"
40 #include "fqdncache.h"
42 #include "HttpRequest.h"
43 #include "mgr/Registration.h"
46 #include "SquidConfig.h"
49 #include "auth/UserRequest.h"
52 #include "ssl/support.h"
55 /// url maximum lengh + extra informations passed to redirector
56 #define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024)
62 Ip::Address client_addr
;
63 const char *client_ident
;
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
);
80 redirectHandleReply(void *data
, const HelperReply
&reply
)
82 redirectStateData
*r
= static_cast<redirectStateData
*>(data
);
83 debugs(61, 5, HERE
<< "reply=" << reply
);
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
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.
94 // * trim all but the first word off the response.
95 // * warn once every 50 responses that this will stop being fixed-up soon.
97 if (const char * res
= reply
.other().content()) {
98 if (const char *t
= strchr(res
, ' ')) {
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
);
105 if (reply
.other().hasContent() && *res
== '\0')
106 reply
.modifiableOther().clean(); // drop the whole buffer of garbage.
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.
115 const char * result
= reply
.other().content();
116 const http_status status
= (http_status
) atoi(result
);
118 HelperReply newReply
;
119 newReply
.result
= reply
.result
;
120 newReply
.notes
= reply
.notes
;
122 if (status
== HTTP_MOVED_PERMANENTLY
123 || status
== HTTP_MOVED_TEMPORARILY
124 || status
== HTTP_SEE_OTHER
125 || status
== HTTP_PERMANENT_REDIRECT
126 || status
== HTTP_TEMPORARY_REDIRECT
) {
128 if (const char *t
= strchr(result
, ':')) {
130 snprintf(statusBuf
, sizeof(statusBuf
),"%3u",status
);
131 newReply
.notes
.add("status", statusBuf
);
133 // TODO: validate the URL produced here is RFC 2616 compliant URI
134 newReply
.notes
.add("url", t
);
136 debugs(85, DBG_CRITICAL
, "ERROR: URL-rewrite produces invalid " << status
<< " redirect Location: " << result
);
139 // status code is not a redirect code (or does not exist)
140 // treat as a re-write URL request
141 // TODO: validate the URL produced here is RFC 2616 compliant URI
142 newReply
.notes
.add("rewrite-url", reply
.other().content());
146 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
147 r
->handler(cbdata
, newReply
);
149 redirectStateFree(r
);
156 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
157 r
->handler(cbdata
, reply
);
159 redirectStateFree(r
);
163 storeIdHandleReply(void *data
, const HelperReply
&reply
)
165 redirectStateData
*r
= static_cast<redirectStateData
*>(data
);
166 debugs(61, 5,"StoreId helper: reply=" << reply
);
168 // XXX: This function is now kept only to check for and display the garbage use-case
169 // and to map the old helper response format(s) into new format result code and key=value pairs
170 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
172 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
173 r
->handler(cbdata
, reply
);
175 redirectStateFree(r
);
179 redirectStateFree(redirectStateData
* r
)
181 safe_free(r
->orig_url
);
186 redirectStats(StoreEntry
* sentry
)
188 if (redirectors
== NULL
) {
189 storeAppendPrintf(sentry
, "No redirectors defined\n");
193 helperStats(sentry
, redirectors
, "Redirector Statistics");
195 if (Config
.onoff
.redirector_bypass
)
196 storeAppendPrintf(sentry
, "\nNumber of requests bypassed "
197 "because all redirectors were busy: %d\n", redirectorBypassed
);
201 storeIdStats(StoreEntry
* sentry
)
203 if (storeIds
== NULL
) {
204 storeAppendPrintf(sentry
, "No StoreId helpers defined\n");
208 helperStats(sentry
, storeIds
, "StoreId helper Statistics");
210 if (Config
.onoff
.store_id_bypass
)
211 storeAppendPrintf(sentry
, "\nNumber of requests bypassed "
212 "because all StoreId helpers were busy: %d\n", storeIdBypassed
);
216 constructHelperQuery(const char *name
, struct helper
*hlp
, HLPCB
*replyHandler
, ClientHttpRequest
* http
, HLPCB
*handler
, void *data
)
218 ConnStateData
* conn
= http
->getConn();
220 char buf
[MAX_REDIRECTOR_REQUEST_STRLEN
];
223 char claddr
[MAX_IPSTRLEN
];
224 char myaddr
[MAX_IPSTRLEN
];
226 /** TODO: create a standalone method to initialize
227 * the cbdata\redirectStateData for all the helpers.
229 redirectStateData
*r
= cbdataAlloc(redirectStateData
);
230 r
->orig_url
= xstrdup(http
->uri
);
232 r
->client_addr
= conn
->log_addr
;
234 r
->client_addr
.SetNoAddr();
235 r
->client_ident
= NULL
;
237 if (http
->request
->auth_user_request
!= NULL
) {
238 r
->client_ident
= http
->request
->auth_user_request
->username();
239 debugs(61, 5, HERE
<< "auth-user=" << (r
->client_ident
?r
->client_ident
:"NULL"));
243 // HttpRequest initializes with null_string. So we must check both defined() and size()
244 if (!r
->client_ident
&& http
->request
->extacl_user
.defined() && http
->request
->extacl_user
.size()) {
245 r
->client_ident
= http
->request
->extacl_user
.termedBuf();
246 debugs(61, 5, HERE
<< "acl-user=" << (r
->client_ident
?r
->client_ident
:"NULL"));
249 if (!r
->client_ident
&& conn
!= NULL
&& conn
->clientConnection
!= NULL
&& conn
->clientConnection
->rfc931
[0]) {
250 r
->client_ident
= conn
->clientConnection
->rfc931
;
251 debugs(61, 5, HERE
<< "ident-user=" << (r
->client_ident
?r
->client_ident
:"NULL"));
256 if (!r
->client_ident
&& conn
!= NULL
&& Comm::IsConnOpen(conn
->clientConnection
)) {
257 r
->client_ident
= sslGetUserEmail(fd_table
[conn
->clientConnection
->fd
].ssl
);
258 debugs(61, 5, HERE
<< "ssl-user=" << (r
->client_ident
?r
->client_ident
:"NULL"));
262 if (!r
->client_ident
)
263 r
->client_ident
= dash_str
;
265 r
->method_s
= RequestMethodStr(http
->request
->method
);
267 r
->handler
= handler
;
269 r
->data
= cbdataReference(data
);
271 if ((fqdn
= fqdncache_gethostbyaddr(r
->client_addr
, 0)) == NULL
)
274 sz
= snprintf(buf
, MAX_REDIRECTOR_REQUEST_STRLEN
, "%s %s/%s %s %s myip=%s myport=%d\n",
276 r
->client_addr
.NtoA(claddr
,MAX_IPSTRLEN
),
278 r
->client_ident
[0] ? rfc1738_escape(r
->client_ident
) : dash_str
,
280 http
->request
->my_addr
.NtoA(myaddr
,MAX_IPSTRLEN
),
281 http
->request
->my_addr
.GetPort());
283 if ((sz
<=0) || (sz
>=MAX_REDIRECTOR_REQUEST_STRLEN
)) {
285 status
= HTTP_INTERNAL_SERVER_ERROR
;
286 debugs(61, DBG_CRITICAL
, "ERROR: Gateway Failure. Can not build request to be passed to " << name
<< ". Request ABORTED.");
288 status
= HTTP_REQUEST_URI_TOO_LARGE
;
289 debugs(61, DBG_CRITICAL
, "ERROR: Gateway Failure. Request passed to " << name
<< " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN
<< "). Request ABORTED.");
292 clientStreamNode
*node
= (clientStreamNode
*)http
->client_stream
.tail
->prev
->data
;
293 clientReplyContext
*repContext
= dynamic_cast<clientReplyContext
*>(node
->data
.getRaw());
295 Ip::Address tmpnoaddr
;
296 tmpnoaddr
.SetNoAddr();
297 repContext
->setReplyToError(ERR_GATEWAY_FAILURE
, status
,
298 http
->request
->method
, NULL
,
299 http
->getConn() != NULL
&& http
->getConn()->clientConnection
!= NULL
?
300 http
->getConn()->clientConnection
->remote
: tmpnoaddr
,
304 http
->getConn() != NULL
&& http
->getConn()->auth_user_request
!= NULL
?
305 http
->getConn()->auth_user_request
: http
->request
->auth_user_request
);
310 node
= (clientStreamNode
*)http
->client_stream
.tail
->data
;
311 clientStreamRead(node
, http
, node
->readBuffer
);
315 debugs(61,6, HERE
<< "sending '" << buf
<< "' to the " << name
<< " helper");
316 helperSubmit(hlp
, buf
, replyHandler
, r
);
319 /**** PUBLIC FUNCTIONS ****/
322 redirectStart(ClientHttpRequest
* http
, HLPCB
* handler
, void *data
)
326 debugs(61, 5, "redirectStart: '" << http
->uri
<< "'");
328 if (Config
.onoff
.redirector_bypass
&& redirectors
->stats
.queue_size
) {
329 /* Skip redirector if there is one request queued */
330 ++redirectorBypassed
;
331 HelperReply bypassReply
;
332 bypassReply
.result
= HelperReply::Okay
;
333 bypassReply
.notes
.add("message","URL rewrite/redirect queue too long. Bypassed.");
334 handler(data
, bypassReply
);
338 constructHelperQuery("redirector", redirectors
, redirectHandleReply
, http
, handler
, data
);
342 * Handles the StoreID feature helper starting.
343 * For now it cannot be done using the redirectStart method.
346 storeIdStart(ClientHttpRequest
* http
, HLPCB
* handler
, void *data
)
350 debugs(61, 5, "storeIdStart: '" << http
->uri
<< "'");
352 if (Config
.onoff
.store_id_bypass
&& storeIds
->stats
.queue_size
) {
353 /* Skip StoreID Helper if there is one request queued */
355 HelperReply bypassReply
;
357 bypassReply
.result
= HelperReply::Okay
;
359 bypassReply
.notes
.add("message","StoreId helper queue too long. Bypassed.");
360 handler(data
, bypassReply
);
364 constructHelperQuery("storeId helper", storeIds
, storeIdHandleReply
, http
, handler
, data
);
368 redirectRegisterWithCacheManager(void)
370 Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats
, 0, 1);
371 Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats
, 0, 1); /* registering the new StoreID statistics in Mgr*/
379 redirectRegisterWithCacheManager();
381 /** FIXME: Temporary unified helpers startup
382 * When and if needed for more helpers a separated startup
383 * method will be added for each of them.
385 if (!Config
.Program
.redirect
&& !Config
.Program
.store_id
)
388 if (Config
.Program
.redirect
) {
390 if (redirectors
== NULL
)
391 redirectors
= new helper("redirector");
393 redirectors
->cmdline
= Config
.Program
.redirect
;
395 redirectors
->childs
.updateLimits(Config
.redirectChildren
);
397 redirectors
->ipc_type
= IPC_STREAM
;
399 helperOpenServers(redirectors
);
402 if (Config
.Program
.store_id
) {
404 if (storeIds
== NULL
)
405 storeIds
= new helper("store_id");
407 storeIds
->cmdline
= Config
.Program
.store_id
;
409 storeIds
->childs
.updateLimits(Config
.storeIdChildren
);
411 storeIds
->ipc_type
= IPC_STREAM
;
413 helperOpenServers(storeIds
);
418 CBDATA_INIT_TYPE(redirectStateData
);
423 redirectShutdown(void)
425 /** FIXME: Temporary unified helpers Shutdown
426 * When and if needed for more helpers a separated shutdown
427 * method will be added for each of them.
429 if (!storeIds
&& !redirectors
)
433 helperShutdown(redirectors
);
436 helperShutdown(storeIds
);