#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;
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
* 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
", 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;
/* 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)
* - 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 */
// 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;
}
}
#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;
}
// 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()) {
context->registerWithConn();
+#if USE_OPENSSL
+ if (switchedToHttps())
+ parsedBumpedRequestCount++;
+#endif
+
processParsedRequest(context);
parsed_req = true; // XXX: do we really need to parse everything right NOW ?
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
#if USE_OPENSSL
switchedToHttps_(false),
parsingTlsHandshake(false),
+ parsedBumpedRequestCount(0),
tlsConnectPort(0),
sslServerBump(NULL),
signAlgorithm(Ssl::algSignTrusted),
} else
whenClientIpKnown();
+ // requires needProxyProtocolHeader_ which is initialized above
+ preservingClientData_ = shouldPreserveClientData();
}
void
}
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();
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) {
// 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)
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.
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
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();
}
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();
}
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;
clientReplyStatus, newServer, clientSocketRecipient,
clientSocketDetach, newClient, tempBuffer);
- http->uri = SBufToCstring(useHost);
stream->flags.parsed_ok = 1; // Do we need it?
stream->mayUseConnection(true);
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);
}
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