]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Fixed on_unsupported_protocol tunnel action (#339)
authorChristos Tsantilas <christos@chtsanti.net>
Mon, 12 Aug 2019 15:48:10 +0000 (15:48 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Tue, 13 Aug 2019 03:32:55 +0000 (03:32 +0000)
Instead of tunneling traffic, a matching on_unsupported_protocol
"tunnel" action resulted in a Squid error response sent to the client
(or, where an error response was not possible, in a connection closure).
The following three cases were fixed:

    * port: http_port (real CONNECT)
    * ssl_bump action: client-first or step1 bump
    * handling phase: parsing TLS client handshake
    * expected data: TLS Client Hello

    * port: http_port (real CONNECT)
    * ssl_bump action: client-first or step1 bump
    * handling phase: parsing the first bumped HTTP request
    * expected data: HTTP request header

    * port: https_port (fake CONNECT)
    * ssl_bump action: any action except terminate
    * handling phase: parsing TLS client handshake
    * expected data: TLS Client Hello

Also, when on_unsupported_protocol was configured, Squid wasted RAM and
CPU cycles to buffer client HTTP requests beyond the point of no return
(i.e., roughly, beyond the first HTTP request on a connection or in a
tunnel), when on_unsupported_protocol settings no longer apply.

Client handshake accumulation is now driven by preservingClientData_. We
set that data member when the connection is accepted (because we may
decide to start preserving bytes right away) and reset it whenever that
decision may change, including when switching to a new protocol inside
CONNECT tunnel and confirming the expected/supported protocol by
successfully parsing its handshake.

Squid does not stop handshake preservation when on_unsupported_protocol
gets disabled during reconfiguration, but Squid will not tunnel
preserved bytes if that happens (and will not tunnel a partial handshake
if on_unsupported_protocol configuration keeps changing).

Also changed how IPv6-based certificates are generated. Their CN field
value is no longer surrounded by [square brackets]. This change was done
to improve Squid code that had to be modified to fix
on_unsupported_protocol. It affects certificate cache key so old
IPv6-based certificates will never be found (and will eventually be
purged) while new ones will be generated and cached instead. We believe
these IPv6-based certificates are rare and untrusted by browsers so the
change in their CN should not have a significant affect on users.

This is a Measurement Factory project.

src/anyp/Uri.cc
src/anyp/Uri.h
src/client_side.cc
src/client_side.h
src/err_type.h
src/servers/Http1Server.cc

index 36d3fe2ac448d4316fcb98e4ab013a364d9880ed..065aea5803c2edd2e1eb5ebc35b868cab3208c2d 100644 (file)
@@ -59,6 +59,16 @@ AnyP::Uri::host(const char *src)
     touch();
 }
 
+SBuf
+AnyP::Uri::hostOrIp() const
+{
+    static char ip[MAX_IPSTRLEN];
+    if (hostIsNumeric())
+        return SBuf(hostIP().toStr(ip, sizeof(ip)));
+    else
+        return SBuf(host());
+}
+
 const SBuf &
 AnyP::Uri::path() const
 {
index 9d28bb001377a7fc3ec727766bf4d66ca340889f..86071852403f3d3a91e7cf6eb42cbb9292917c08 100644 (file)
@@ -80,6 +80,11 @@ public:
     int hostIsNumeric(void) const {return hostIsNumeric_;}
     Ip::Address const & hostIP(void) const {return hostAddr_;}
 
+    /// \returns the host subcomponent of the authority component
+    /// If the host is an IPv6 address, returns that IP address without
+    /// [brackets]! See RFC 3986 Section 3.2.2.
+    SBuf hostOrIp() const;
+
     void port(unsigned short p) {port_=p; touch();}
     unsigned short port() const {return port_;}
     /// reset the port to the default port number for the current scheme
index 4c7ee1e6a0c0ab72f262cab927e60ec3965a15af..818d5b4643375b11c41cb32247c8b55a7d3198dc 100644 (file)
@@ -1225,12 +1225,12 @@ ConnStateData::prepareTlsSwitchingURL(const Http1::RequestParserPointer &hp)
 #if USE_OPENSSL
     if (!uri) {
         Must(tlsConnectPort);
-        Must(sslConnectHostOrIp.size());
+        Must(!tlsConnectHostOrIp.isEmpty());
         SBuf useHost;
         if (!tlsClientSni().isEmpty())
             useHost = tlsClientSni();
         else
-            useHost.assign(sslConnectHostOrIp.rawBuf(), sslConnectHostOrIp.size());
+            useHost = tlsConnectHostOrIp;
 
         const SBuf &scheme = AnyP::UriScheme(transferProtocol.protocol).image();
         const int url_sz = scheme.length() + useHost.length() + hp->requestUri().length() + 32;
@@ -1272,65 +1272,52 @@ prepareTransparentURL(ConnStateData * conn, const Http1::RequestParserPointer &h
     return uri;
 }
 
-/** Parse an HTTP request
- *
- *  \note Sets result->flags.parsed_ok to 0 if failed to parse the request,
- *          to 1 if the request was correctly parsed.
- *  \param[in] csd a ConnStateData. The caller must make sure it is not null
- *  \param[in] hp an Http1::RequestParser
- *  \param[out] mehtod_p will be set as a side-effect of the parsing.
- *          Pointed-to value will be set to Http::METHOD_NONE in case of
- *          parsing failure
- *  \param[out] http_ver will be set as a side-effect of the parsing
- *  \return NULL on incomplete requests,
- *          a Http::Stream on success or failure.
- */
 Http::Stream *
-parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
+ConnStateData::parseHttpRequest(const Http1::RequestParserPointer &hp)
 {
     /* Attempt to parse the first line; this will define where the method, url, version and header begin */
     {
-        const bool parsedOk = hp->parse(csd->inBuf);
+        Must(hp);
+
+        if (preservingClientData_)
+            preservedClientData = inBuf;
+
+        const bool parsedOk = hp->parse(inBuf);
 
         // sync the buffers after parsing.
-        csd->inBuf = hp->remaining();
+        inBuf = hp->remaining();
 
         if (hp->needsMoreData()) {
             debugs(33, 5, "Incomplete request, waiting for end of request line");
             return NULL;
         }
 
-        if (csd->mayTunnelUnsupportedProto()) {
-            csd->preservedClientData = hp->parsed();
-            csd->preservedClientData.append(csd->inBuf);
-        }
-
         if (!parsedOk) {
             const bool tooBig =
                 hp->parseStatusCode == Http::scRequestHeaderFieldsTooLarge ||
                 hp->parseStatusCode == Http::scUriTooLong;
-            auto result = csd->abortRequestParsing(
-                              tooBig ? "error:request-too-large" : "error:invalid-request");
+            auto result = abortRequestParsing(
+                          tooBig ? "error:request-too-large" : "error:invalid-request");
             // assume that remaining leftovers belong to this bad request
-            if (!csd->inBuf.isEmpty())
-                csd->consumeInput(csd->inBuf.length());
+            if (!inBuf.isEmpty())
+                consumeInput(inBuf.length());
             return result;
         }
     }
 
     /* We know the whole request is in parser now */
-    debugs(11, 2, "HTTP Client " << csd->clientConnection);
+    debugs(11, 2, "HTTP Client " << clientConnection);
     debugs(11, 2, "HTTP Client REQUEST:\n---------\n" <<
            hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol() << "\n" <<
            hp->mimeHeader() <<
            "\n----------");
 
     /* deny CONNECT via accelerated ports */
-    if (hp->method() == Http::METHOD_CONNECT && csd->port != NULL && csd->port->flags.accelSurrogate) {
-        debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << csd->transferProtocol << " Accelerator port " << csd->port->s.port());
+    if (hp->method() == Http::METHOD_CONNECT && port != NULL && port->flags.accelSurrogate) {
+        debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << transferProtocol << " Accelerator port " << port->s.port());
         debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
         hp->parseStatusCode = Http::scMethodNotAllowed;
-        return csd->abortRequestParsing("error:method-not-allowed");
+        return abortRequestParsing("error:method-not-allowed");
     }
 
     /* RFC 7540 section 11.6 registers the method PRI as HTTP/2 specific
@@ -1338,16 +1325,16 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
      * If seen it signals a broken client or proxy has corrupted the traffic.
      */
     if (hp->method() == Http::METHOD_PRI && hp->messageProtocol() < Http::ProtocolVersion(2,0)) {
-        debugs(33, DBG_IMPORTANT, "WARNING: PRI method received on " << csd->transferProtocol << " port " << csd->port->s.port());
+        debugs(33, DBG_IMPORTANT, "WARNING: PRI method received on " << transferProtocol << " port " << port->s.port());
         debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
         hp->parseStatusCode = Http::scMethodNotAllowed;
-        return csd->abortRequestParsing("error:method-not-allowed");
+        return abortRequestParsing("error:method-not-allowed");
     }
 
     if (hp->method() == Http::METHOD_NONE) {
         debugs(33, DBG_IMPORTANT, "WARNING: Unsupported method: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
         hp->parseStatusCode = Http::scMethodNotAllowed;
-        return csd->abortRequestParsing("error:unsupported-request-method");
+        return abortRequestParsing("error:unsupported-request-method");
     }
 
     // Process headers after request line
@@ -1358,10 +1345,10 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
            ", mime header block:\n" << hp->mimeHeader() << "\n----------");
 
     /* Ok, all headers are received */
-    ClientHttpRequest *http = new ClientHttpRequest(csd);
+    ClientHttpRequest *http = new ClientHttpRequest(this);
 
     http->req_sz = hp->messageHeaderSize();
-    Http::Stream *result = new Http::Stream(csd->clientConnection, http);
+    Http::Stream *result = new Http::Stream(clientConnection, http);
 
     StoreIOBuffer tempBuffer;
     tempBuffer.data = result->reqbuf;
@@ -1375,7 +1362,7 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
 
     /* set url */
     debugs(33,5, "Prepare absolute URL from " <<
-           (csd->transparent()?"intercept":(csd->port->flags.accelSurrogate ? "accel":"")));
+           (transparent()?"intercept":(port->flags.accelSurrogate ? "accel":"")));
     /* Rewrite the URL in transparent or accelerator mode */
     /* NP: there are several cases to traverse here:
      *  - standard mode (forward proxy)
@@ -1389,11 +1376,11 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
      *  - remote interception with PROXY protocol
      *  - remote reverse-proxy with PROXY protocol
      */
-    if (csd->switchedToHttps()) {
-        http->uri = csd->prepareTlsSwitchingURL(hp);
-    } else if (csd->transparent()) {
+    if (switchedToHttps()) {
+        http->uri = prepareTlsSwitchingURL(hp);
+    } else if (transparent()) {
         /* intercept or transparent mode, properly working with no failures */
-        http->uri = prepareTransparentURL(csd, hp);
+        http->uri = prepareTransparentURL(this, hp);
 
     } else if (internalCheck(hp->requestUri())) { // NP: only matches relative-URI
         /* internal URL mode */
@@ -1403,9 +1390,9 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
         //  But have not parsed there yet!! flag for local-only handling.
         http->flags.internal = true;
 
-    } else if (csd->port->flags.accelSurrogate) {
+    } else if (port->flags.accelSurrogate) {
         /* accelerator mode */
-        http->uri = prepareAcceleratedURL(csd, hp);
+        http->uri = prepareAcceleratedURL(this, hp);
         http->flags.accel = true;
     }
 
@@ -1549,37 +1536,50 @@ bool ConnStateData::serveDelayedError(Http::Stream *context)
 }
 #endif // USE_OPENSSL
 
-/**
- * Check on_unsupported_protocol checklist and return true if tunnel mode selected
- * or false otherwise
- */
+/// ConnStateData::tunnelOnError() wrapper. Reduces code changes. TODO: Remove.
 bool
 clientTunnelOnError(ConnStateData *conn, Http::StreamPointer &context, HttpRequest::Pointer &request, const HttpRequestMethod& method, err_type requestError)
 {
-    if (conn->mayTunnelUnsupportedProto()) {
-        ACLFilledChecklist checklist(Config.accessList.on_unsupported_protocol, request.getRaw(), nullptr);
-        checklist.al = (context && context->http) ? context->http->al : nullptr;
-        checklist.requestErrorType = requestError;
-        checklist.src_addr = conn->clientConnection->remote;
-        checklist.my_addr = conn->clientConnection->local;
-        checklist.conn(conn);
-        ClientHttpRequest *http = context ? context->http : nullptr;
-        const char *log_uri = http ? http->log_uri : nullptr;
-        checklist.syncAle(request.getRaw(), log_uri);
-        auto answer = checklist.fastCheck();
-        if (answer.allowed() && answer.kind == 1) {
-            debugs(33, 3, "Request will be tunneled to server");
-            if (context) {
-                assert(conn->pipeline.front() == context); // XXX: still assumes HTTP/1 semantics
-                context->finished(); // Will remove from conn->pipeline queue
-            }
-            Comm::SetSelect(conn->clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
-            return conn->initiateTunneledRequest(request, Http::METHOD_NONE, "unknown-protocol", conn->preservedClientData);
-        } else {
-            debugs(33, 3, "Continue with returning the error: " << requestError);
-        }
+    assert(conn);
+    assert(conn->pipeline.front() == context);
+    return conn->tunnelOnError(method, requestError);
+}
+
+/// initiate tunneling if possible or return false otherwise
+bool
+ConnStateData::tunnelOnError(const HttpRequestMethod &method, const err_type requestError)
+{
+    if (!Config.accessList.on_unsupported_protocol) {
+        debugs(33, 5, "disabled; send error: " << requestError);
+        return false;
     }
 
+    if (!preservingClientData_) {
+        debugs(33, 3, "may have forgotten client data; send error: " << requestError);
+        return false;
+    }
+
+    const auto context = pipeline.front();
+    const auto http = context ? context->http : nullptr;
+    const auto request = http ? http->request : nullptr;
+
+    ACLFilledChecklist checklist(Config.accessList.on_unsupported_protocol, request, nullptr);
+    checklist.al = http ? http->al : nullptr;
+    checklist.requestErrorType = requestError;
+    checklist.src_addr = clientConnection->remote;
+    checklist.my_addr = clientConnection->local;
+    checklist.conn(this);
+    const char *log_uri = http ? http->log_uri : nullptr;
+    checklist.syncAle(request, log_uri);
+    auto answer = checklist.fastCheck();
+    if (answer.allowed() && answer.kind == 1) {
+        debugs(33, 3, "Request will be tunneled to server");
+        if (context)
+            context->finished(); // Will remove from pipeline queue
+        Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
+        return initiateTunneledRequest(request, Http::METHOD_NONE, "unknown-protocol", preservedClientData);
+    }
+    debugs(33, 3, "denied; send error: " << requestError);
     return false;
 }
 
@@ -1918,6 +1918,11 @@ ConnStateData::clientParseRequests()
             // we have been waiting for PROXY to provide client-IP
             // for some lookups, ie rDNS and IDENT.
             whenClientIpKnown();
+
+            // Done with PROXY protocol which has cleared preservingClientData_.
+            // If the next protocol supports on_unsupported_protocol, then its
+            // parseOneRequest() must reset preservingClientData_.
+            assert(!preservingClientData_);
         }
 
         if (Http::StreamPointer context = parseOneRequest()) {
@@ -1929,6 +1934,11 @@ ConnStateData::clientParseRequests()
 
             context->registerWithConn();
 
+#if USE_OPENSSL
+            if (switchedToHttps())
+                parsedBumpedRequestCount++;
+#endif
+
             processParsedRequest(context);
 
             parsed_req = true; // XXX: do we really need to parse everything right NOW ?
@@ -2140,13 +2150,10 @@ ConnStateData::requestTimeout(const CommTimeoutCbParams &io)
     if (!Comm::IsConnOpen(io.conn))
         return;
 
-    if (mayTunnelUnsupportedProto() && !receivedFirstByte_) {
-        Http::StreamPointer context = pipeline.front();
-        Must(context && context->http);
-        HttpRequest::Pointer request = context->http->request;
-        if (clientTunnelOnError(this, context, request, HttpRequestMethod(), ERR_REQUEST_START_TIMEOUT))
-            return;
-    }
+    const err_type error = receivedFirstByte_ ? ERR_REQUEST_PARSE_TIMEOUT : ERR_REQUEST_START_TIMEOUT;
+    if (tunnelOnError(HttpRequestMethod(), error))
+        return;
+
     /*
     * Just close the connection to not confuse browsers
     * using persistent connections. Some browsers open
@@ -2181,6 +2188,7 @@ ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
 #if USE_OPENSSL
     switchedToHttps_(false),
     parsingTlsHandshake(false),
+    parsedBumpedRequestCount(0),
     tlsConnectPort(0),
     sslServerBump(NULL),
     signAlgorithm(Ssl::algSignTrusted),
@@ -2242,6 +2250,8 @@ ConnStateData::start()
     } else
         whenClientIpKnown();
 
+    // requires needProxyProtocolHeader_ which is initialized above
+    preservingClientData_ = shouldPreserveClientData();
 }
 
 void
@@ -2665,18 +2675,18 @@ ConnStateData::sslCrtdHandleReply(const Helper::Reply &reply)
     }
 
     if (reply.result == Helper::BrokenHelper) {
-        debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply);
+        debugs(33, 5, "Certificate for " << tlsConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply);
     } else if (!reply.other().hasContent()) {
         debugs(1, DBG_IMPORTANT, HERE << "\"ssl_crtd\" helper returned <NULL> reply.");
     } else {
         Ssl::CrtdMessage reply_message(Ssl::CrtdMessage::REPLY);
         if (reply_message.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK) {
-            debugs(33, 5, HERE << "Reply from ssl_crtd for " << sslConnectHostOrIp << " is incorrect");
+            debugs(33, 5, "Reply from ssl_crtd for " << tlsConnectHostOrIp << " is incorrect");
         } else {
             if (reply.result != Helper::Okay) {
-                debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply_message.getBody());
+                debugs(33, 5, "Certificate for " << tlsConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply_message.getBody());
             } else {
-                debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd");
+                debugs(33, 5, "Certificate for " << tlsConnectHostOrIp << " was successfully recieved from ssl_crtd");
                 if (sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare)) {
                     doPeekAndSpliceStep();
                     auto ssl = fd_table[clientConnection->fd].ssl.get();
@@ -2702,7 +2712,7 @@ ConnStateData::sslCrtdHandleReply(const Helper::Reply &reply)
 
 void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties)
 {
-    certProperties.commonName =  sslCommonName_.isEmpty() ? sslConnectHostOrIp.termedBuf() : sslCommonName_.c_str();
+    certProperties.commonName = sslCommonName_.isEmpty() ? tlsConnectHostOrIp.c_str() : sslCommonName_.c_str();
 
     const bool connectedOk = sslServerBump && sslServerBump->connectedOk();
     if (connectedOk) {
@@ -2728,7 +2738,7 @@ void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &cer
                 // CONNECT request.
                 if (ca->alg == Ssl::algSetCommonName) {
                     if (!param)
-                        param = sslConnectHostOrIp.termedBuf();
+                        param = tlsConnectHostOrIp.c_str();
                     certProperties.commonName = param;
                     certProperties.setCommonName = true;
                 } else if (ca->alg == Ssl::algSetValidAfter)
@@ -2877,7 +2887,7 @@ void
 ConnStateData::getSslContextDone(Security::ContextPointer &ctx)
 {
     if (port->secure.generateHostCertificates && !ctx) {
-        debugs(33, 2, "Failed to generate TLS context for " << sslConnectHostOrIp);
+        debugs(33, 2, "Failed to generate TLS context for " << tlsConnectHostOrIp);
     }
 
     // If generated ssl context = NULL, try to use static ssl context.
@@ -2920,13 +2930,18 @@ ConnStateData::switchToHttps(ClientHttpRequest *http, Ssl::BumpMode bumpServerMo
     Must(http->request);
     auto &request = http->request;
 
-    sslConnectHostOrIp = request->url.host();
+    // Depending on receivedFirstByte_, we are at the start of either an
+    // established CONNECT tunnel with the client or an intercepted TCP (and
+    // presumably TLS) connection from the client. Expect TLS Client Hello.
+    const auto insideConnectTunnel = receivedFirstByte_;
+    debugs(33, 5, (insideConnectTunnel ? "post-CONNECT " : "raw TLS ") << clientConnection);
+
+    tlsConnectHostOrIp = request->url.hostOrIp();
     tlsConnectPort = request->url.port();
     resetSslCommonName(request->url.host());
 
     // We are going to read new request
     flags.readMore = true;
-    debugs(33, 5, HERE << "converting " << clientConnection << " to SSL");
 
     // keep version major.minor details the same.
     // but we are now performing the HTTPS handshake traffic
@@ -2954,6 +2969,13 @@ ConnStateData::switchToHttps(ClientHttpRequest *http, Ssl::BumpMode bumpServerMo
     receivedFirstByte_ = false;
     // Get more data to peek at Tls
     parsingTlsHandshake = true;
+
+    // If the protocol has changed, then reset preservingClientData_.
+    // Otherwise, its value initially set in start() is still valid/fresh.
+    // shouldPreserveClientData() uses parsingTlsHandshake which is reset above.
+    if (insideConnectTunnel)
+        preservingClientData_ = shouldPreserveClientData();
+
     readSomeData();
 }
 
@@ -3171,9 +3193,9 @@ ConnStateData::httpsPeeked(PinnedIdleContext pic)
 
     if (Comm::IsConnOpen(pic.connection)) {
         notePinnedConnectionBecameIdle(pic);
-        debugs(33, 5, HERE << "bumped HTTPS server: " << sslConnectHostOrIp);
+        debugs(33, 5, "bumped HTTPS server: " << tlsConnectHostOrIp);
     } else
-        debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp);
+        debugs(33, 5, "Error while bumping: " << tlsConnectHostOrIp);
 
     getSslContextStart();
 }
@@ -3189,13 +3211,20 @@ ConnStateData::initiateTunneledRequest(HttpRequest::Pointer const &cause, Http::
 
     if (pinning.serverConnection != nullptr) {
         static char ip[MAX_IPSTRLEN];
-        pinning.serverConnection->remote.toHostStr(ip, sizeof(ip));
-        connectHost.assign(ip);
+        connectHost = pinning.serverConnection->remote.toStr(ip, sizeof(ip));
         connectPort = pinning.serverConnection->remote.port();
-    } else if (cause && cause->method == Http::METHOD_CONNECT) {
-        // We are inside a (not fully established) CONNECT request
-        connectHost = cause->url.host();
+    } else if (cause) {
+        connectHost = cause->url.hostOrIp();
         connectPort = cause->url.port();
+#if USE_OPENSSL
+    } else if (!tlsConnectHostOrIp.isEmpty()) {
+        connectHost = tlsConnectHostOrIp;
+        connectPort = tlsConnectPort;
+#endif
+    } else if (transparent()) {
+        static char ip[MAX_IPSTRLEN];
+        connectHost = clientConnection->local.toStr(ip, sizeof(ip));
+        connectPort = clientConnection->local.port();
     } else {
         debugs(33, 2, "Not able to compute URL, abort request tunneling for " << reason);
         return false;
@@ -3256,7 +3285,6 @@ ConnStateData::buildFakeRequest(Http::MethodType const method, SBuf &useHost, un
                      clientReplyStatus, newServer, clientSocketRecipient,
                      clientSocketDetach, newClient, tempBuffer);
 
-    http->uri = SBufToCstring(useHost);
     stream->flags.parsed_ok = 1; // Do we need it?
     stream->mayUseConnection(true);
 
@@ -3276,6 +3304,8 @@ ConnStateData::buildFakeRequest(Http::MethodType const method, SBuf &useHost, un
     request->method = method;
     request->url.host(useHost.c_str());
     request->url.port(usePort);
+
+    http->uri = SBufToCstring(request->effectiveRequestUri());
     http->initRequest(request.getRaw());
 
     request->manager(this, http->al);
@@ -3971,15 +4001,35 @@ ConnStateData::checkLogging()
 }
 
 bool
-ConnStateData::mayTunnelUnsupportedProto()
+ConnStateData::shouldPreserveClientData() const
 {
-    return Config.accessList.on_unsupported_protocol
+    // PROXY protocol bytes are meant for us and, hence, cannot be tunneled
+    if (needProxyProtocolHeader_)
+        return false;
+
+    // If our decision here is negative, configuration changes are irrelevant.
+    // Otherwise, clientTunnelOnError() rechecks configuration before tunneling.
+    if (!Config.accessList.on_unsupported_protocol)
+        return false;
+
+    // TODO: Figure out whether/how we can support FTP tunneling.
+    if (port->transport.protocol == AnyP::PROTO_FTP)
+        return false;
+
 #if USE_OPENSSL
-           &&
-           ((port->flags.isIntercepted() && port->flags.tunnelSslBumping)
-            || (serverBump() && pinning.serverConnection))
+    if (parsingTlsHandshake)
+        return true;
+
+    // the 1st HTTP request on a bumped connection
+    if (!parsedBumpedRequestCount && switchedToHttps())
+        return true;
 #endif
-           ;
+
+    // the 1st HTTP request on a connection to a plain intercepting port
+    if (!pipeline.nrequests && !port->secure.encryptTransport && transparent())
+        return true;
+
+    return false;
 }
 
 NotePairs::Pointer
index 318730dd36bf080954b8cef6c9f46933642f0c50..e1e760971adc538dadc5904bc7f76c25c78616c5 100644 (file)
@@ -301,14 +301,19 @@ public:
     /// generates and sends to tunnel.cc a fake request with a given payload
     bool initiateTunneledRequest(HttpRequest::Pointer const &cause, Http::MethodType const method, const char *reason, const SBuf &payload);
 
-    /// whether tunneling of unsupported protocol is allowed for this connection
-    bool mayTunnelUnsupportedProto();
+    /// whether we should start saving inBuf client bytes in anticipation of
+    /// tunneling them to the server later (on_unsupported_protocol)
+    bool shouldPreserveClientData() const;
+
+    // TODO: move to the protected section when removing clientTunnelOnError()
+    bool tunnelOnError(const HttpRequestMethod &, const err_type);
 
     /// build a fake http request
     ClientHttpRequest *buildFakeRequest(Http::MethodType const method, SBuf &useHost, unsigned short usePort, const SBuf &payload);
 
-    /// client data which may need to forward as-is to server after an
-    /// on_unsupported_protocol tunnel decision.
+    /// From-client handshake bytes (including bytes at the beginning of a
+    /// CONNECT tunnel) which we may need to forward as-is if their syntax does
+    /// not match the expected TLS or HTTP protocol (on_unsupported_protocol).
     SBuf preservedClientData;
 
     /* Registered Runner API */
@@ -337,6 +342,16 @@ protected:
     bool handleIdleClientPinnedTlsRead();
 #endif
 
+    /// Parse an HTTP request
+    /// \note Sets result->flags.parsed_ok to 0 if failed to parse the request,
+    ///       to 1 if the request was correctly parsed
+    /// \param[in] hp an Http1::RequestParser
+    /// \return NULL on incomplete requests,
+    ///         a Http::Stream on success or failure.
+    /// TODO: Move to HttpServer. Warning: Move requires large code nonchanges!
+    Http::Stream *parseHttpRequest(const Http1::RequestParserPointer &);
+
+
     /// parse input buffer prefix into a single transfer protocol request
     /// return NULL to request more header bytes (after checking any limits)
     /// use abortRequestParsing() to handle parsing errors w/o creating request
@@ -357,6 +372,9 @@ protected:
 
     BodyPipe::Pointer bodyPipe; ///< set when we are reading request body
 
+    /// whether preservedClientData is valid and should be kept up to date
+    bool preservingClientData_;
+
 private:
     /* ::Server API */
     virtual bool connFinishedWithConn(int size);
@@ -392,15 +410,14 @@ private:
     Auth::UserRequest::Pointer auth_;
 #endif
 
-    /// the parser state for current HTTP/1.x input buffer processing
-    Http1::RequestParserPointer parser_;
-
 #if USE_OPENSSL
     bool switchedToHttps_;
     bool parsingTlsHandshake; ///< whether we are getting/parsing TLS Hello bytes
+    /// The number of parsed HTTP requests headers on a bumped client connection
+    uint64_t parsedBumpedRequestCount;
 
-    /// The SSL server host name appears in CONNECT request or the server ip address for the intercepted requests
-    String sslConnectHostOrIp; ///< The SSL server host name as passed in the CONNECT request
+    /// The TLS server host name appears in CONNECT request or the server ip address for the intercepted requests
+    SBuf tlsConnectHostOrIp; ///< The TLS server host name as passed in the CONNECT request
     unsigned short tlsConnectPort; ///< The TLS server port number as passed in the CONNECT request
     SBuf sslCommonName_; ///< CN name for SSL certificate generation
 
@@ -450,8 +467,6 @@ SQUIDCEXTERN CSD clientReplyDetach;
 CSCB clientSocketRecipient;
 CSD clientSocketDetach;
 
-/* TODO: Move to HttpServer. Warning: Move requires large code nonchanges! */
-Http::Stream *parseHttpRequest(ConnStateData *, const Http1::RequestParserPointer &);
 void clientProcessRequest(ConnStateData *, const Http1::RequestParserPointer &, Http::Stream *);
 void clientPostHttpsAccept(ConnStateData *);
 
index c6aa94426b854bbac7d1ec79f59dfbd7e82b7a8a..e46aa945a78acaf62b64aa70b1d212b4df9dea20 100644 (file)
@@ -76,6 +76,7 @@ typedef enum {
 
     ERR_SECURE_ACCEPT_FAIL, // Rejects the SSL connection intead of error page
     ERR_REQUEST_START_TIMEOUT, // Aborts the connection instead of error page
+    ERR_REQUEST_PARSE_TIMEOUT, // Aborts the connection instead of error page
     ERR_RELAY_REMOTE, // Sends server reply instead of error page
 
     /* Cache Manager GUI can install a manager index/home page */
index a0ee371b815f82ea400805e96f67be9753d7ddbc..9737c17ab238966da611ab2cac63dd84e2a317ab 100644 (file)
@@ -74,14 +74,18 @@ Http::One::Server::parseOneRequest()
 {
     PROF_start(HttpServer_parseOneRequest);
 
+    // reset because the protocol may have changed if this is the first request
+    // and because we never bypass parsing failures of N+1st same-proto request
+    preservingClientData_ = shouldPreserveClientData();
+
     // parser is incremental. Generate new parser state if we,
     // a) do not have one already
     // b) have completed the previous request parsing already
     if (!parser_ || !parser_->needsMoreData())
-        parser_ = new Http1::RequestParser(mayTunnelUnsupportedProto());
+        parser_ = new Http1::RequestParser(preservingClientData_);
 
     /* Process request */
-    Http::Stream *context = parseHttpRequest(this, parser_);
+    Http::Stream *context = parseHttpRequest(parser_);
 
     PROF_stop(HttpServer_parseOneRequest);
     return context;