]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Do not blindly forward cache peer CONNECT responses.
authorAlex Rousskov <rousskov@measurement-factory.com>
Wed, 1 Jul 2015 06:26:38 +0000 (23:26 -0700)
committerAmos Jeffries <squid3@treenet.co.nz>
Wed, 1 Jul 2015 06:26:38 +0000 (23:26 -0700)
Squid blindly forwards cache peer CONNECT responses to clients. This
may break things if the peer responds with something like HTTP 403
(Forbidden) and keeps the connection with Squid open:
  -  The client application issues a CONNECT request.
  -  Squid forwards this request to a cache peer.
  -  Cache peer correctly responds back with a "403 Forbidden".
  -  Squid does not parse cache peer response and
     just forwards it as if it was a Squid response to the client.
  -  The TCP connections are not closed.

At this stage, Squid is unaware that the CONNECT request has failed. All
subsequent requests on the user agent TCP connection are treated as
tunnelled traffic. Squid is forwarding these requests to the peer on the
TCP connection previously used for the 403-ed CONNECT request, without
proper processing. The additional headers which should have been applied
by Squid to these requests are not applied, and the requests are being
forwarded to the cache peer even though the Squid configuration may
state that these requests must go directly to the origin server.

This fixes Squid to parse cache peer responses, and if an error response
found, respond with "502 Bad Gateway" to the client and close the
connections.

src/tunnel.cc

index 0262363d93d1a6f3db5a6c75daaf3fc0272ad646..5684e10c1fafac34a66d01d54686823ffe85116d 100644 (file)
@@ -110,6 +110,10 @@ public:
                  (request->flags.interceptTproxy || request->flags.intercepted));
     }
 
+    /// Sends "502 Bad Gateway" error response to the client,
+    /// if it is waiting for Squid CONNECT response, closing connections.
+    void informUserOfPeerError(const char *errMsg);
+
     class Connection
     {
 
@@ -128,12 +132,13 @@ public:
 
         void error(int const xerrno);
         int debugLevelForError(int const xerrno) const;
-        /// handles a non-I/O error associated with this Connection
-        void logicError(const char *errMsg);
         void closeIfOpen();
         void dataSent (size_t amount);
+        /// writes 'b' buffer, setting the 'writer' member to 'callback'.
+        void write(const char *b, int size, AsyncCall::Pointer &callback, FREE * free_func);
         int len;
         char *buf;
+        AsyncCall::Pointer writer; ///< pending Comm::Write callback
         int64_t *size_ptr;      /* pointer to size in an ConnStateData for logging */
 
         Comm::ConnectionPointer conn;    ///< The currently connected connection.
@@ -224,6 +229,7 @@ tunnelServerClosed(const CommCloseCbParams &params)
     TunnelStateData *tunnelState = (TunnelStateData *)params.data;
     debugs(26, 3, HERE << tunnelState->server.conn);
     tunnelState->server.conn = NULL;
+    tunnelState->server.writer = NULL;
 
     if (tunnelState->request != NULL)
         tunnelState->request->hier.stopPeerClock(false);
@@ -233,7 +239,7 @@ tunnelServerClosed(const CommCloseCbParams &params)
         return;
     }
 
-    if (!tunnelState->server.len) {
+    if (!tunnelState->client.writer) {
         tunnelState->client.conn->close();
         return;
     }
@@ -245,13 +251,14 @@ tunnelClientClosed(const CommCloseCbParams &params)
     TunnelStateData *tunnelState = (TunnelStateData *)params.data;
     debugs(26, 3, HERE << tunnelState->client.conn);
     tunnelState->client.conn = NULL;
+    tunnelState->client.writer = NULL;
 
     if (tunnelState->noConnections()) {
         delete tunnelState;
         return;
     }
 
-    if (!tunnelState->client.len) {
+    if (!tunnelState->server.writer) {
         tunnelState->server.conn->close();
         return;
     }
@@ -382,6 +389,23 @@ TunnelStateData::readConnectResponseDone(char *buf, size_t len, Comm::Flag errco
         handleConnectResponse(len);
 }
 
+void
+TunnelStateData::informUserOfPeerError(const char *errMsg)
+{
+    server.len = 0;
+    if (!clientExpectsConnectResponse()) {
+        // closing the connection is the best we can do here
+        debugs(50, 3, server.conn << " closing on error: " << errMsg);
+        server.conn->close();
+        return;
+    }
+    ErrorState *err  = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw());
+    err->callback = tunnelErrorComplete;
+    err->callback_data = this;
+    *status_ptr = Http::scBadGateway;
+    errorSend(http->getConn()->clientConnection, err);
+}
+
 /* Read from client side and queue it for writing to the server */
 void
 TunnelStateData::ReadConnectResponseDone(const Comm::ConnectionPointer &, char *buf, size_t len, Comm::Flag errcode, int xerrno, void *data)
@@ -413,7 +437,7 @@ TunnelStateData::handleConnectResponse(const size_t chunkSize)
     const bool parsed = rep.parse(connectRespBuf, eof, &parseErr);
     if (!parsed) {
         if (parseErr > 0) { // unrecoverable parsing error
-            server.logicError("malformed CONNECT response from peer");
+            informUserOfPeerError("malformed CONNECT response from peer");
             return;
         }
 
@@ -422,7 +446,7 @@ TunnelStateData::handleConnectResponse(const size_t chunkSize)
         assert(!parseErr);
 
         if (!connectRespBuf->hasSpace()) {
-            server.logicError("huge CONNECT response from peer");
+            informUserOfPeerError("huge CONNECT response from peer");
             return;
         }
 
@@ -436,7 +460,8 @@ TunnelStateData::handleConnectResponse(const size_t chunkSize)
 
     // bail if we did not get an HTTP 200 (Connection Established) response
     if (rep.sline.status() != Http::scOkay) {
-        server.logicError("unsupported CONNECT response status code");
+        // if we ever decide to reuse the peer connection, we must extract the error response first
+        informUserOfPeerError("unsupported CONNECT response status code");
         return;
     }
 
@@ -454,13 +479,6 @@ TunnelStateData::handleConnectResponse(const size_t chunkSize)
     connectExchangeCheckpoint();
 }
 
-void
-TunnelStateData::Connection::logicError(const char *errMsg)
-{
-    debugs(50, 3, conn << " closing on error: " << errMsg);
-    conn->close();
-}
-
 void
 TunnelStateData::Connection::error(int const xerrno)
 {
@@ -557,7 +575,7 @@ TunnelStateData::copy(size_t len, Connection &from, Connection &to, IOCB *comple
     debugs(26, 3, HERE << "Schedule Write");
     AsyncCall::Pointer call = commCbCall(5,5, "TunnelBlindCopyWriteHandler",
                                          CommIoCbPtrFun(completion, this));
-    Comm::Write(to.conn, from.buf, len, call, NULL);
+    to.write(from.buf, len, call, NULL);
 }
 
 /* Writes data from the client buffer to the server side */
@@ -566,6 +584,7 @@ TunnelStateData::WriteServerDone(const Comm::ConnectionPointer &, char *buf, siz
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     assert (cbdataReferenceValid (tunnelState));
+    tunnelState->server.writer = NULL;
 
     tunnelState->writeServerDone(buf, len, flag, xerrno);
 }
@@ -615,6 +634,7 @@ TunnelStateData::WriteClientDone(const Comm::ConnectionPointer &, char *buf, siz
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     assert (cbdataReferenceValid (tunnelState));
+    tunnelState->client.writer = NULL;
 
     tunnelState->writeClientDone(buf, len, flag, xerrno);
 }
@@ -632,7 +652,14 @@ TunnelStateData::Connection::dataSent(size_t amount)
 }
 
 void
-TunnelStateData::writeClientDone(char *buf, size_t len, Comm::Flag flag, int xerrno)
+TunnelStateData::Connection::write(const char *b, int size, AsyncCall::Pointer &callback, FREE * free_func)
+{
+    writer = callback;
+    Comm::Write(conn, b, size, callback, free_func);
+}
+
+void
+TunnelStateData::writeClientDone(char *, size_t len, Comm::Flag flag, int xerrno)
 {
     debugs(26, 3, HERE << client.conn << ", " << len << " bytes written, flag=" << flag);
 
@@ -790,6 +817,7 @@ tunnelConnectedWriteDone(const Comm::ConnectionPointer &conn, char *buf, size_t
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     debugs(26, 3, HERE << conn << ", flag=" << flag);
+    tunnelState->client.writer = NULL;
 
     if (flag != Comm::OK) {
         *tunnelState->status_ptr = Http::scInternalServerError;
@@ -806,6 +834,7 @@ tunnelConnectReqWriteDone(const Comm::ConnectionPointer &conn, char *buf, size_t
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     debugs(26, 3, conn << ", flag=" << flag);
+    tunnelState->server.writer = NULL;
     assert(tunnelState->waitingForConnectRequest());
 
     if (flag != Comm::OK) {
@@ -846,7 +875,7 @@ tunnelConnected(const Comm::ConnectionPointer &server, void *data)
     else {
         AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
                                              CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
-        Comm::Write(tunnelState->client.conn, conn_established, strlen(conn_established), call, NULL);
+        tunnelState->client.write(conn_established, strlen(conn_established), call, NULL);
     }
 }
 
@@ -1073,29 +1102,21 @@ tunnelRelayConnectRequest(const Comm::ConnectionPointer &srv, void *data)
     debugs(11, 2, "Tunnel Server REQUEST: " << tunnelState->server.conn << ":\n----------\n" <<
            Raw("tunnelRelayConnectRequest", mb.content(), mb.contentSize()) << "\n----------");
 
-    if (tunnelState->clientExpectsConnectResponse()) {
-        // hack: blindly tunnel peer response (to our CONNECT request) to the client as ours.
-        AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectedWriteDone",
-                                       CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
-        Comm::Write(srv, &mb, writeCall);
-    } else {
-        // we have to eat the connect response from the peer (so that the client
-        // does not see it) and only then start shoveling data to the client
-        AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectReqWriteDone",
-                                       CommIoCbPtrFun(tunnelConnectReqWriteDone,
-                                               tunnelState));
-        Comm::Write(srv, &mb, writeCall);
-        tunnelState->connectReqWriting = true;
-
-        tunnelState->connectRespBuf = new MemBuf;
-        // SQUID_TCP_SO_RCVBUF: we should not accumulate more than regular I/O buffer
-        // can hold since any CONNECT response leftovers have to fit into server.buf.
-        // 2*SQUID_TCP_SO_RCVBUF: HttpMsg::parse() zero-terminates, which uses space.
-        tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
-        tunnelState->readConnectResponse();
-
-        assert(tunnelState->waitingForConnectExchange());
-    }
+    AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectReqWriteDone",
+                                   CommIoCbPtrFun(tunnelConnectReqWriteDone,
+                                           tunnelState));
+
+    tunnelState->server.write(mb.buf, mb.size, writeCall, mb.freeFunc());
+    tunnelState->connectReqWriting = true;
+
+    tunnelState->connectRespBuf = new MemBuf;
+    // SQUID_TCP_SO_RCVBUF: we should not accumulate more than regular I/O buffer
+    // can hold since any CONNECT response leftovers have to fit into server.buf.
+    // 2*SQUID_TCP_SO_RCVBUF: HttpMsg::parse() zero-terminates, which uses space.
+    tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
+    tunnelState->readConnectResponse();
+
+    assert(tunnelState->waitingForConnectExchange());
 
     AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
                                      CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
@@ -1228,7 +1249,7 @@ switchToTunnel(HttpRequest *request, Comm::ConnectionPointer &clientConn, Comm::
 
     AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
                                          CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
-    Comm::Write(tunnelState->client.conn, buf.content(), buf.contentSize(), call, NULL);
+    tunnelState->client.write(buf.content(), buf.contentSize(), call, NULL);
 }
 #endif //USE_OPENSSL