--- /dev/null
- if (ClientSocketContext *context = parseOneRequest()) {
+#include "squid.h"
+#include "client_side.h"
+#include "client_side_request.h"
+#include "client_side_reply.h"
+#include "Downloader.h"
+#include "http/one/RequestParser.h"
++#include "http/Stream.h"
+
+CBDATA_CLASS_INIT(Downloader);
+
+Downloader::Downloader(SBuf &url, const MasterXaction::Pointer &xact, AsyncCall::Pointer &aCallback, unsigned int level):
+ AsyncJob("Downloader"),
+ ConnStateData(xact),
+ url_(url),
+ callback(aCallback),
+ status(Http::scNone),
+ level_(level)
+{
+ transferProtocol = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1);
+}
+
+Downloader::~Downloader()
+{
+ debugs(33 , 2, "Downloader Finished");
+}
+
+void
+Downloader::callException(const std::exception &e)
+{
+ debugs(33 , 2, "Downloader caught:" << e.what());
+ AsyncJob::callException(e);
+}
+
+bool
+Downloader::doneAll() const
+{
+ return (!callback || callback->canceled()) && AsyncJob::doneAll();
+}
+
+void
+Downloader::start()
+{
+ BodyProducer::start();
+ HttpControlMsgSink::start();
- ClientSocketContext *
++ if (Http::Stream *context = parseOneRequest()) {
+ context->registerWithConn();
+ processParsedRequest(context);
+
+ /**/
+ if (context->flags.deferred) {
+ if (context != context->http->getConn()->pipeline.front().getRaw())
+ context->deferRecipientForLater(context->deferredparams.node, context->deferredparams.rep, context->deferredparams.queuedBuffer);
+ else
+ context->http->getConn()->handleReply(context->deferredparams.rep, context->deferredparams.queuedBuffer);
+ }
+ /**/
+
+ }
+
+}
+
+void
+Downloader::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
+{
+ // This method required only if we need to support uploading data to server
+ // Currently only GET requests are supported
+ assert(0);
+}
+
+void
+Downloader::noteBodyConsumerAborted(BodyPipe::Pointer)
+{
+ // This method required only if we need to support uploading data to server
+ // Currently only GET requests are supported
+ assert(0);
+}
+
- ClientSocketContext *const context = new ClientSocketContext(NULL, http);
++Http::Stream *
+Downloader::parseOneRequest()
+{
+ const HttpRequestMethod method = Http::METHOD_GET;
+
+ char *uri = strdup(url_.c_str());
+ HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(uri, method);
+ if (!request) {
+ debugs(33, 5, "Invalid FTP URL: " << uri);
+ safe_free(uri);
+ return NULL; //earlyError(...)
+ }
+ request->http_ver = Http::ProtocolVersion();
+ request->header.putStr(Http::HdrType::HOST, request->url.host());
+ request->header.putTime(Http::HdrType::DATE, squid_curtime);
+
+ ClientHttpRequest *const http = new ClientHttpRequest(this);
+ http->request = request;
+ HTTPMSGLOCK(http->request);
+ http->req_sz = 0;
+ http->uri = uri;
+
- Downloader::processParsedRequest(ClientSocketContext *context)
++ Http::Stream *const context = new Http::Stream(NULL, http);
+ StoreIOBuffer tempBuffer;
+ tempBuffer.data = context->reqbuf;
+ tempBuffer.length = HTTP_REQBUF_SZ;
+
+ ClientStreamData newServer = new clientReplyContext(http);
+ ClientStreamData newClient = context;
+ clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
+ clientReplyStatus, newServer, clientSocketRecipient,
+ clientSocketDetach, newClient, tempBuffer);
+
+ context->flags.parsed_ok = 1;
+ return context;
+}
+
+void
- ClientSocketContext::Pointer context = pipeline.front();
++Downloader::processParsedRequest(Http::Stream *context)
+{
+ Must(context != NULL);
+ Must(pipeline.nrequests == 1);
+
+ ClientHttpRequest *const http = context->http;
+ assert(http != NULL);
+
+ debugs(33, 4, "forwarding request to server side");
+ assert(http->storeEntry() == NULL);
+ clientProcessRequest(this, Http1::RequestParserPointer(), context);
+}
+
+time_t
+Downloader::idleTimeout() const
+{
+ // No need to be implemented for connection-less ConnStateData object.
+ assert(0);
+ return 0;
+}
+
+void
+Downloader::writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call)
+{
+}
+
+void
+Downloader::handleReply(HttpReply *reply, StoreIOBuffer receivedData)
+{
++ Http::StreamPointer context = pipeline.front();
+ bool existingContent = reply ? reply->content_length : 0;
+ bool exceedSize = (context->startOfOutput() && existingContent > -1 && (size_t)existingContent > MaxObjectSize) ||
+ ((object.length() + receivedData.length) > MaxObjectSize);
+
+ if (exceedSize) {
+ status = Http::scInternalServerError;
+ callBack();
+ return;
+ }
+
+ debugs(33, 4, "Received " << receivedData.length <<
+ " object data, offset: " << receivedData.offset <<
+ " error flag:" << receivedData.flags.error);
+
+ if (receivedData.length > 0) {
+ object.append(receivedData.data, receivedData.length);
+ context->http->out.size += receivedData.length;
+ context->noteSentBodyBytes(receivedData.length);
+ }
+
+ switch (context->socketState()) {
+ case STREAM_NONE:
+ debugs(33, 3, "Get more data");
+ context->pullData();
+ break;
+ case STREAM_COMPLETE:
+ debugs(33, 3, "Object data transfer successfully complete");
+ status = Http::scOkay;
+ callBack();
+ break;
+ case STREAM_UNPLANNED_COMPLETE:
+ debugs(33, 3, "Object data transfer failed: STREAM_UNPLANNED_COMPLETE");
+ status = Http::scInternalServerError;
+ callBack();
+ break;
+ case STREAM_FAILED:
+ debugs(33, 3, "Object data transfer failed: STREAM_FAILED");
+ status = Http::scInternalServerError;
+ callBack();
+ break;
+ default:
+ fatal("unreachable code");
+ }
+}
+
+void
+Downloader::downloadFinished()
+{
+ debugs(33, 3, "fake call, to just delete the Downloader");
+
+ // Not really needed. Squid will delete this object because "doneAll" is true.
+ //deleteThis("completed");
+}
+
+void
+Downloader::callBack()
+{
+ CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer());
+ Must(dialer);
+ dialer->status = status;
+ if (status == Http::scOkay)
+ dialer->object = object;
+ ScheduleCallHere(callback);
+ callback = NULL;
+ // Calling deleteThis method here to finish Downloader
+ // may result to squid crash.
+ // This method called by handleReply method which maybe called
+ // by ClientHttpRequest::doCallouts. The doCallouts after this object deleted
+ // may operate on non valid objects.
+ // Schedule a fake call here just to force squid to delete this object
+ CallJobHere(33, 7, CbcPointer<Downloader>(this), Downloader, downloadFinished);
+}
+
+bool
+Downloader::isOpen() const
+{
+ return cbdataReferenceValid(this) && // XXX: checking "this" in a method
+ callback != NULL;
+}
--- /dev/null
- virtual ClientSocketContext *parseOneRequest();
- virtual void processParsedRequest(ClientSocketContext *context);
+#ifndef SQUID_DOWNLOADER_H
+#define SQUID_DOWNLOADER_H
+
+#include "client_side.h"
+#include "cbdata.h"
+
+class Downloader: public ConnStateData
+{
+ CBDATA_CLASS(Downloader);
+ // XXX CBDATA_CLASS expands to nonvirtual toCbdata, AsyncJob::toCbdata
+ // is pure virtual. breaks build on clang if override is used
+
+public:
+
+ /// Callback data to use with Downloader callbacks;
+ class CbDialer {
+ public:
+ CbDialer(): status(Http::scNone) {}
+ virtual ~CbDialer() {}
+ SBuf object;
+ Http::StatusCode status;
+ };
+
+ explicit Downloader(SBuf &url, const MasterXaction::Pointer &xact, AsyncCall::Pointer &aCallback, unsigned int level = 0);
+ virtual ~Downloader();
+
+ /// Fake call used internally by Downloader.
+ void downloadFinished();
+
+ /// The nested level of Downloader object (downloads inside downloads)
+ unsigned int nestedLevel() const {return level_;}
+
+ /* ConnStateData API */
+ virtual bool isOpen() const;
+
+ /* AsyncJob API */
+ virtual void callException(const std::exception &e);
+ virtual bool doneAll() const;
+
+ /*Bodypipe API*/
+ virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer);
+ virtual void noteBodyConsumerAborted(BodyPipe::Pointer);
+
+protected:
+ /* ConnStateData API */
++ virtual Http::Stream *parseOneRequest();
++ virtual void processParsedRequest(Http::Stream *context);
+ virtual time_t idleTimeout() const;
+ virtual void writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call);
+ virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData);
+
+ /* AsyncJob API */
+ virtual void start();
+
+private:
+ /// Schedules for execution the "callback" with parameters the status
+ /// and object
+ void callBack();
+
+ static const size_t MaxObjectSize = 1*1024*1024; ///< The maximum allowed object size.
+
+ SBuf url_; ///< The url to download
+ AsyncCall::Pointer callback; ///< callback to call when download finishes
+ Http::StatusCode status; ///< The download status code
+ SBuf object; //object data
+ unsigned int level_; ///< Holds the nested downloads level
+};
+
+#endif
request->flags.accelerated = http->flags.accel;
request->flags.sslBumped=conn->switchedToHttps();
- request->flags.ignoreCc = conn->port->ignore_cc;
- // TODO: decouple http->flags.accel from request->flags.sslBumped
- request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
- !conn->port->allow_direct : 0;
- request->sources |= isFtp ? HttpMsg::srcFtp :
- ((request->flags.sslBumped || conn->port->transport.protocol == AnyP::PROTO_HTTPS) ? HttpMsg::srcHttps : HttpMsg::srcHttp);
+ if (!conn->connectionless()) {
+ request->flags.ignoreCc = conn->port->ignore_cc;
+ // TODO: decouple http->flags.accel from request->flags.sslBumped
+ request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
- !conn->port->allow_direct : 0;
++ !conn->port->allow_direct : 0;
++ request->sources |= isFtp ? HttpMsg::srcFtp :
++ ((request->flags.sslBumped || conn->port->transport.protocol == AnyP::PROTO_HTTPS) ? HttpMsg::srcHttps : HttpMsg::srcHttp);
+ }
#if USE_AUTH
if (request->flags.sslBumped) {
if (conn->getAuth() != NULL)
void
ConnStateData::splice()
{
- //Normally we can splice here, because we just got client hello message
- auto ssl = fd_table[clientConnection->fd].ssl;
+ // normally we can splice here, because we just got client hello message
+ auto ssl = fd_table[clientConnection->fd].ssl.get();
+
+ //retrieve received TLS client information
+ clientConnection->tlsNegotiations()->fillWith(ssl);
+
BIO *b = SSL_get_rbio(ssl);
Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
- MemBuf const &rbuf = bio->rBufData();
- debugs(83,5, "Bio for " << clientConnection << " read " << rbuf.contentSize() << " helo bytes");
+ SBuf const &rbuf = bio->rBufData();
+ debugs(83,5, "Bio for " << clientConnection << " read " << rbuf.length() << " helo bytes");
// Do splice:
fd_table[clientConnection->fd].read_method = &default_read_method;
fd_table[clientConnection->fd].write_method = &default_write_method;
// reset the current protocol to HTTP/1.1 (was "HTTPS" for the bumping process)
transferProtocol = Http::ProtocolVersion();
// inBuf still has the "CONNECT ..." request data, reset it to SSL hello message
- inBuf.append(rbuf.content(), rbuf.contentSize());
+ inBuf.append(rbuf);
- ClientSocketContext::Pointer context = pipeline.front();
+ Http::StreamPointer context = pipeline.front();
ClientHttpRequest *http = context->http;
tunnelStart(http);
}
#include "squid.h"
#include "acl/FilledChecklist.h"
- #include "base/AsyncCbdataCalls.h"
- #include "CachePeer.h"
- #include "client_side.h"
#include "comm/Loops.h"
+#include "Downloader.h"
#include "errorpage.h"
#include "fde.h"
- #include "globals.h"
- #include "helper/ResultCode.h"
++#include "http/Stream.h"
#include "HttpRequest.h"
- #include "neighbors.h"
#include "SquidConfig.h"
+#include "ssl/bio.h"
#include "ssl/cert_validate_message.h"
#include "ssl/Config.h"
- #include "ssl/ErrorDetail.h"
#include "ssl/helper.h"
#include "ssl/PeerConnector.h"
- #include "ssl/ServerBump.h"
- #include "ssl/support.h"
CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector);
- CBDATA_NAMESPACED_CLASS_INIT(Ssl, BlindPeerConnector);
- CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeekingPeerConnector);
- Ssl::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerConn, AsyncCall::Pointer &aCallback, const time_t timeout) :
+ Ssl::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerConn, AsyncCall::Pointer &aCallback, const AccessLogEntryPointer &alp, const time_t timeout) :
AsyncJob("Ssl::PeerConnector"),
serverConn(aServerConn),
- certErrors(NULL),
+ al(alp),
callback(aCallback),
negotiationTimeout(timeout),
startTime(squid_curtime),
- useCertValidator_(false),
- useCertValidator_(true)
++ useCertValidator_(true),
+ certsDownloads(0)
{
// if this throws, the caller's cb dialer is not our CbDialer
Must(dynamic_cast<CbDialer*>(callback->getDialer()));
void
Ssl::PeerConnector::noteWantRead()
{
- setReadTimeout();
const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
++ Security::SessionPtr ssl = fd_table[fd].ssl.get();
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ if (srvBio->holdRead()) {
+ if (srvBio->gotHello()) {
+ if (checkForMissingCertificates())
+ return; // Wait to download certificates before proceed.
+
+ srvBio->holdRead(false);
+ // Schedule a negotiateSSl to allow openSSL parse received data
+ Ssl::PeerConnector::NegotiateSsl(fd, this);
+ return;
+ } else if (srvBio->gotHelloFailed()) {
+ srvBio->holdRead(false);
+ debugs(83, DBG_IMPORTANT, "Error parsing SSL Server Hello Message on FD " << fd);
+ // Schedule a negotiateSSl to allow openSSL parse received data
+ Ssl::PeerConnector::NegotiateSsl(fd, this);
+ return;
+ }
+ }
+
+ setReadTimeout();
Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
}
return buf.content();
}
- SSL *ssl = fd_table[fd].ssl;
+/// CallDialer to allow use Downloader objects within PeerConnector class.
+class PeerConnectorCertDownloaderDialer: public CallDialer, public Downloader::CbDialer
+{
+public:
+ typedef void (Ssl::PeerConnector::*Method)(SBuf &object, int status);
+
+ PeerConnectorCertDownloaderDialer(Method method, Ssl::PeerConnector *pc):
+ method_(method),
+ peerConnector_(pc) {}
+
+ /* CallDialer API */
+ virtual bool canDial(AsyncCall &call) { return peerConnector_.valid(); }
+ void dial(AsyncCall &call) { ((&(*peerConnector_))->*method_)(object, status); }
+ virtual void print(std::ostream &os) const {
+ os << '(' << peerConnector_.get() << ", Http Status:" << status << ')';
+ }
+
+ Method method_; ///< The Ssl::PeerConnector method to dial
+ CbcPointer<Ssl::PeerConnector> peerConnector_; ///< The Ssl::PeerConnector object
+};
+
+void
+Ssl::PeerConnector::startCertDownloading(SBuf &url)
+{
+ AsyncCall::Pointer certCallback = asyncCall(81, 4,
+ "Ssl::PeerConnector::certDownloadingDone",
+ PeerConnectorCertDownloaderDialer(&Ssl::PeerConnector::certDownloadingDone, this));
+
+ const Downloader *csd = dynamic_cast<const Downloader*>(request->clientConnectionManager.valid());
+ MasterXaction *xaction = new MasterXaction;
+ Downloader *dl = new Downloader(url, xaction, certCallback, csd ? csd->nestedLevel() + 1 : 1);
+ AsyncJob::Start(dl);
+}
+
+void
+Ssl::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus)
+{
+ certsDownloads++;
+ debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length());
+
+ // Get ServerBio from SSL object
+ const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
++ Security::SessionPtr ssl = fd_table[fd].ssl.get();
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+
+ // Parse Certificate. Assume that it is in DER format.
+ const unsigned char *raw = (const unsigned char*)obj.rawContent();
+ if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) {
+ char buffer[1024];
+ debugs(81, 5, "Retrieved certificate: " << X509_NAME_oneline(X509_get_subject_name(cert), buffer, 1024));
+ const Ssl::X509_STACK_Pointer &certsList = srvBio->serverCertificates();
+ if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert, certsList)) {
+ urlsOfMissingCerts.push(SBuf(issuerUri));
+ }
+ Ssl::SSL_add_untrusted_cert(ssl, cert);
+ }
+
+ // Check if has uri to download from and if yes add it to urlsOfMissingCerts
+ if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) {
+ startCertDownloading(urlsOfMissingCerts.front());
+ urlsOfMissingCerts.pop();
+ return;
+ }
+
+ srvBio->holdRead(false);
+ Ssl::PeerConnector::NegotiateSsl(serverConnection()->fd, this);
+}
+
+bool
+Ssl::PeerConnector::checkForMissingCertificates ()
+{
+ // Check for nested SSL certificates downloads. For example when the
+ // certificate located in an SSL site which requires to download a
+ // a missing certificate (... from an SSL site which requires to ...)
+ const Downloader *csd = dynamic_cast<const Downloader*>(request->clientConnectionManager.valid());
+ if (csd && csd->nestedLevel() >= MaxNestedDownloads)
+ return false;
+
+ const int fd = serverConnection()->fd;
- Security::ContextPtr
- Ssl::BlindPeerConnector::getSslContext()
- {
- if (const CachePeer *peer = serverConnection()->getPeer()) {
- assert(peer->secure.encryptTransport);
- Security::ContextPtr sslContext(peer->sslContext);
- return sslContext;
- }
- return ::Config.ssl_client.sslContext;
- }
-
- SSL *
- Ssl::BlindPeerConnector::initializeSsl()
- {
- SSL *ssl = Ssl::PeerConnector::initializeSsl();
- if (!ssl)
- return NULL;
-
- if (const CachePeer *peer = serverConnection()->getPeer()) {
- assert(peer);
-
- // NP: domain may be a raw-IP but it is now always set
- assert(!peer->secure.sslDomain.isEmpty());
-
- // const loss is okay here, ssl_ex_index_server is only read and not assigned a destructor
- SBuf *host = new SBuf(peer->secure.sslDomain);
- SSL_set_ex_data(ssl, ssl_ex_index_server, host);
-
- if (peer->sslSession)
- SSL_set_session(ssl, peer->sslSession);
- } else {
- SBuf *hostName = new SBuf(request->url.host());
- SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostName);
- }
-
- return ssl;
- }
-
- void
- Ssl::BlindPeerConnector::noteNegotiationDone(ErrorState *error)
- {
- if (error) {
- // XXX: forward.cc calls peerConnectSucceeded() after an OK TCP connect but
- // we call peerConnectFailed() if SSL failed afterwards. Is that OK?
- // It is not clear whether we should call peerConnectSucceeded/Failed()
- // based on TCP results, SSL results, or both. And the code is probably not
- // consistent in this aspect across tunnelling and forwarding modules.
- if (CachePeer *p = serverConnection()->getPeer())
- peerConnectFailed(p);
- return;
- }
-
- const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
- if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) {
- if (serverConnection()->getPeer()->sslSession)
- SSL_SESSION_free(serverConnection()->getPeer()->sslSession);
-
- serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
- }
- }
-
- Security::ContextPtr
- Ssl::PeekingPeerConnector::getSslContext()
- {
- // XXX: locate a per-server context in Security:: instead
- return ::Config.ssl_client.sslContext;
- }
-
- SSL *
- Ssl::PeekingPeerConnector::initializeSsl()
- {
- SSL *ssl = Ssl::PeerConnector::initializeSsl();
- if (!ssl)
- return NULL;
-
- if (ConnStateData *csd = request->clientConnectionManager.valid()) {
-
- // client connection is required in the case we need to splice
- // or terminate client and server connections
- assert(clientConn != NULL);
- SBuf *hostName = NULL;
- Ssl::ClientBio *cltBio = NULL;
-
- //Enable Status_request tls extension, required to bump some clients
- SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
-
- // In server-first bumping mode, clientSsl is NULL.
- if (SSL *clientSsl = fd_table[clientConn->fd].ssl) {
- BIO *b = SSL_get_rbio(clientSsl);
- cltBio = static_cast<Ssl::ClientBio *>(b->ptr);
- const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
- if (!features.serverName.isEmpty())
- hostName = new SBuf(features.serverName);
- }
-
- if (!hostName) {
- // While we are peeking at the certificate, we may not know the server
- // name that the client will request (after interception or CONNECT)
- // unless it was the CONNECT request with a user-typed address.
- const bool isConnectRequest = !csd->port->flags.isIntercepted();
- if (!request->flags.sslPeek || isConnectRequest)
- hostName = new SBuf(request->url.host());
- }
-
- if (hostName)
- SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostName);
-
- Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2);
- if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
- assert(cltBio);
- const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
- if (features.sslVersion != -1) {
- features.applyToSSL(ssl, csd->sslBumpMode);
- // Should we allow it for all protocols?
- if (features.sslVersion >= 3) {
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
- // Inherite client features, like SSL version, SNI and other
- srvBio->setClientFeatures(features);
- srvBio->recordInput(true);
- srvBio->mode(csd->sslBumpMode);
- }
- }
- } else {
- // Set client SSL options
- SSL_set_options(ssl, ::Security::ProxyOutgoingConfig.parsedOptions);
-
- // Use SNI TLS extension only when we connect directly
- // to the origin server and we know the server host name.
- const char *sniServer = NULL;
- const bool redirected = request->flags.redirected && ::Config.onoff.redir_rewrites_host;
- if (!hostName || redirected)
- sniServer = !request->url.hostIsNumeric() ? request->url.host() : NULL;
- else
- sniServer = hostName->c_str();
-
- if (sniServer)
- Ssl::setClientSNI(ssl, sniServer);
- }
-
- // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
- X509 *peeked_cert;
- if (csd->serverBump() &&
- (peeked_cert = csd->serverBump()->serverCert.get())) {
- CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509);
- SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert);
- }
- }
-
- return ssl;
- }
-
- void
- Ssl::PeekingPeerConnector::noteNegotiationDone(ErrorState *error)
- {
- SSL *ssl = fd_table[serverConnection()->fd].ssl;
-
- // Check the list error with
- if (!request->clientConnectionManager.valid() || ! ssl)
- return;
-
- // remember the server certificate from the ErrorDetail object
- if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
- // remember validation errors, if any
- if (certErrors) {
- if (serverBump->sslErrors)
- cbdataReferenceDone(serverBump->sslErrors);
- serverBump->sslErrors = cbdataReference(certErrors);
- }
-
- if (!serverBump->serverCert.get()) {
- // remember the server certificate from the ErrorDetail object
- if (error && error->detail && error->detail->peerCert())
- serverBump->serverCert.resetAndLock(error->detail->peerCert());
- else {
- handleServerCertificate();
- }
- }
-
- if (error) {
- // For intercepted connections, set the host name to the server
- // certificate CN. Otherwise, we just hope that CONNECT is using
- // a user-entered address (a host name or a user-entered IP).
- const bool isConnectRequest = !request->clientConnectionManager->port->flags.isIntercepted();
- if (request->flags.sslPeek && !isConnectRequest) {
- if (X509 *srvX509 = serverBump->serverCert.get()) {
- if (const char *name = Ssl::CommonHostName(srvX509)) {
- request->url.host(name);
- debugs(83, 3, "reset request host: " << name);
- }
- }
- }
- }
- }
-
- if (!error) {
- serverCertificateVerified();
- if (splice)
- switchToTunnel(request.getRaw(), clientConn, serverConn);
- }
- }
-
- void
- Ssl::PeekingPeerConnector::noteWantWrite()
- {
- const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
-
- if ((srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
- debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
- checkForPeekAndSplice();
- return;
- }
-
- Ssl::PeerConnector::noteWantWrite();
- }
-
- void
- Ssl::PeekingPeerConnector::noteSslNegotiationError(const int result, const int ssl_error, const int ssl_lib_error)
- {
- const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
-
- // In Peek mode, the ClientHello message sent to the server. If the
- // server resuming a previous (spliced) SSL session with the client,
- // then probably we are here because local SSL object does not know
- // anything about the session being resumed.
- //
- if (srvBio->bumpMode() == Ssl::bumpPeek && (resumingSession = srvBio->resumingSession())) {
- // we currently splice all resumed sessions unconditionally
- if (const bool spliceResumed = true) {
- bypassCertValidator();
- checkForPeekAndSpliceMatched(Ssl::bumpSplice);
- return;
- } // else fall through to find a matching ssl_bump action (with limited info)
- }
-
- // If we are in peek-and-splice mode and still we did not write to
- // server yet, try to see if we should splice.
- // In this case the connection can be saved.
- // If the checklist decision is do not splice a new error will
- // occur in the next SSL_connect call, and we will fail again.
- // Abort on certificate validation errors to avoid splicing and
- // thus hiding them.
- // Abort if no certificate found probably because of malformed or
- // unsupported server Hello message (TODO: make configurable).
- if (!SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) &&
- (srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
- Security::CertPointer serverCert(SSL_get_peer_certificate(ssl));
- if (serverCert.get()) {
- debugs(81, 3, "Error (" << ERR_error_string(ssl_lib_error, NULL) << ") but, hold write on SSL connection on FD " << fd);
- checkForPeekAndSplice();
- return;
- }
- }
-
- // else call parent noteNegotiationError to produce an error page
- Ssl::PeerConnector::noteSslNegotiationError(result, ssl_error, ssl_lib_error);
- }
-
- void
- Ssl::PeekingPeerConnector::handleServerCertificate()
- {
- if (serverCertificateHandled)
- return;
-
- if (ConnStateData *csd = request->clientConnectionManager.valid()) {
- const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
- Security::CertPointer serverCert(SSL_get_peer_certificate(ssl));
- if (!serverCert.get())
- return;
-
- serverCertificateHandled = true;
-
- // remember the server certificate for later use
- if (Ssl::ServerBump *serverBump = csd->serverBump()) {
- serverBump->serverCert.reset(serverCert.release());
- }
- }
- }
-
- void
- Ssl::PeekingPeerConnector::serverCertificateVerified()
- {
- if (ConnStateData *csd = request->clientConnectionManager.valid()) {
- Security::CertPointer serverCert;
- if(Ssl::ServerBump *serverBump = csd->serverBump())
- serverCert.resetAndLock(serverBump->serverCert.get());
- else {
- const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
- serverCert.reset(SSL_get_peer_certificate(ssl));
- }
- if (serverCert.get()) {
- csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
- debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
- " bumped: " << *serverConnection());
- }
- }
- }
-
++ Security::SessionPtr ssl = fd_table[fd].ssl.get();
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ const Ssl::X509_STACK_Pointer &certs = srvBio->serverCertificates();
+
+ if (certs.get() && sk_X509_num(certs.get())) {
+ debugs(83, 5, "SSL server sent " << sk_X509_num(certs.get()) << " certificates");
+ Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs);
+ if (urlsOfMissingCerts.size()) {
+ startCertDownloading(urlsOfMissingCerts.front());
+ urlsOfMissingCerts.pop();
+ return true;
+ }
+ }
+
+ return false;
+}
+
#include "CommCalls.h"
#include "security/EncryptorAnswer.h"
#include "ssl/support.h"
+
#include <iosfwd>
+#include <queue>
+ #if USE_OPENSSL
+
class HttpRequest;
class ErrorState;
+ class AccessLogEntry;
+ typedef RefCount<AccessLogEntry> AccessLogEntryPointer;
namespace Ssl
{
/// A wrapper function for negotiateSsl for use with Comm::SetSelect
static void NegotiateSsl(int fd, void *data);
- AsyncCall::Pointer callback; ///< we call this with the results
+
+ /// The maximum allowed missing certificates downloads
+ static const unsigned int MaxCertsDownloads = 10;
+ /// The maximum allowed nested certificates downloads
+ static const unsigned int MaxNestedDownloads = 3;
+
AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
time_t negotiationTimeout; ///< the SSL connection timeout to use
time_t startTime; ///< when the peer connector negotiation started
bool useCertValidator_; ///< whether the certificate validator should bypassed
+ /// The list of URLs where missing certificates should be downloaded
+ std::queue<SBuf> urlsOfMissingCerts;
+ unsigned int certsDownloads; ///< The number of downloaded missing certificates
};
- /// A simple PeerConnector for SSL/TLS cache_peers. No SslBump capabilities.
- class BlindPeerConnector: public PeerConnector {
- CBDATA_CLASS(BlindPeerConnector);
- public:
- BlindPeerConnector(HttpRequestPointer &aRequest,
- const Comm::ConnectionPointer &aServerConn,
- AsyncCall::Pointer &aCallback, const time_t timeout = 0) :
- AsyncJob("Ssl::BlindPeerConnector"),
- PeerConnector(aServerConn, aCallback, timeout)
- {
- request = aRequest;
- }
-
- /* PeerConnector API */
-
- /// Calls parent initializeSSL, configure the created SSL object to try reuse SSL session
- /// and sets the hostname to use for certificates validation
- virtual SSL *initializeSsl();
-
- /// Return the configured Security::ContextPtr object
- virtual Security::ContextPtr getSslContext();
-
- /// On error calls peerConnectFailed function, on success store the used SSL session
- /// for later use
- virtual void noteNegotiationDone(ErrorState *error);
- };
-
- /// A PeerConnector for HTTP origin servers. Capable of SslBumping.
- class PeekingPeerConnector: public PeerConnector {
- CBDATA_CLASS(PeekingPeerConnector);
- public:
- PeekingPeerConnector(HttpRequestPointer &aRequest,
- const Comm::ConnectionPointer &aServerConn,
- const Comm::ConnectionPointer &aClientConn,
- AsyncCall::Pointer &aCallback, const time_t timeout = 0) :
- AsyncJob("Ssl::PeekingPeerConnector"),
- PeerConnector(aServerConn, aCallback, timeout),
- clientConn(aClientConn),
- splice(false),
- resumingSession(false),
- serverCertificateHandled(false)
- {
- request = aRequest;
- }
-
- /* PeerConnector API */
- virtual SSL *initializeSsl();
- virtual Security::ContextPtr getSslContext();
- virtual void noteWantWrite();
- virtual void noteSslNegotiationError(const int result, const int ssl_error, const int ssl_lib_error);
- virtual void noteNegotiationDone(ErrorState *error);
-
- /// Updates associated client connection manager members
- /// if the server certificate was received from the server.
- void handleServerCertificate();
-
- /// Initiates the ssl_bump acl check in step3 SSL bump step to decide
- /// about bumping, splicing or terminating the connection.
- void checkForPeekAndSplice();
-
- /// Callback function for ssl_bump acl check in step3 SSL bump step.
- void checkForPeekAndSpliceDone(allow_t answer);
-
- /// Handles the final bumping decision.
- void checkForPeekAndSpliceMatched(const Ssl::BumpMode finalMode);
-
- /// Guesses the final bumping decision when no ssl_bump rules match.
- Ssl::BumpMode checkForPeekAndSpliceGuess() const;
-
- /// Runs after the server certificate verified to update client
- /// connection manager members
- void serverCertificateVerified();
-
- /// A wrapper function for checkForPeekAndSpliceDone for use with acl
- static void cbCheckForPeekAndSpliceDone(allow_t answer, void *data);
-
- private:
- Comm::ConnectionPointer clientConn; ///< TCP connection to the client
- AsyncCall::Pointer callback; ///< we call this with the results
- AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
- bool splice; ///< whether we are going to splice or not
- bool resumingSession; ///< whether it is an SSL resuming session connection
- bool serverCertificateHandled; ///< whether handleServerCertificate() succeeded
- };
-
} // namespace Ssl
- #endif /* SQUID_PEER_CONNECTOR_H */
+ #endif /* USE_OPENSSL */
+ #endif /* SQUID_SRC_SSL_PEERCONNECTOR_H */
}
}
- Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1), helloMsgSize(0), unknownCiphers(false), doHeartBeats(true), tlsTicketsExtension(false), hasTlsTicket(false), tlsStatusRequest(false), initialized_(false)
+ Ssl::Bio::sslFeatures::sslFeatures():
+ sslHelloVersion(-1),
+ sslVersion(-1),
+ compressMethod(-1),
+ helloMsgSize(0),
+ unknownCiphers(false),
+ doHeartBeats(true),
+ tlsTicketsExtension(false),
+ hasTlsTicket(false),
+ tlsStatusRequest(false),
- hasCcsOrNst(false),
+ initialized_(false)
{
memset(client_random, 0, SSL3_RANDOM_SIZE);
}
/// \retval >0 if the hello size is retrieved
/// \retval 0 if the contents of the buffer are not enough
/// \retval <0 if the contents of buf are not SSLv3 or TLS hello message
- int parseMsgHead(const MemBuf &);
- /// Parses msg buffer and return true if one of the Change Cipher Spec
- /// or New Session Ticket messages found
- bool checkForCcsOrNst(const unsigned char *msg, size_t size);
+ int parseMsgHead(const SBuf &);
public:
+ int sslHelloVersion; ///< The SSL hello message version
int sslVersion; ///< The requested/used SSL version
int compressMethod; ///< The requested/used compressed method
int helloMsgSize; ///< the hello message size
/// Tells ssl connection to use BIO and monitor state via stateChanged()
static void Link(SSL *ssl, BIO *bio);
- /// Prepare the rbuf buffer to accept hello data
- void prepReadBuf();
-
/// Reads data from socket and record them to a buffer
- int readAndBuffer(char *buf, int size, BIO *table, const char *description);
+ int readAndBuffer(BIO *table, const char *description);
- const MemBuf &rBufData() {return rbuf;}
+ /// Return the TLS features requested by TLS client
+ const Bio::sslFeatures &receivedHelloFeatures() const {return receivedHelloFeatures_;}
+
+ const SBuf &rBufData() {return rbuf;}
protected:
const int fd_; ///< the SSL socket we are reading and writing
- MemBuf rbuf; ///< Used to buffer input data.
+ SBuf rbuf; ///< Used to buffer input data.
+ /// The features retrieved from client or Server TLS hello message
+ Bio::sslFeatures receivedHelloFeatures_;
};
/// BIO node to handle socket IO for squid client side
/// Sets the random number to use in client SSL HELLO message
void setClientFeatures(const sslFeatures &features);
+ /// Parses server Hello message if it is recorded and extracts
+ /// server-supported features.
+ void extractHelloFeatures();
+
bool resumingSession();
+
+ /// Reads Server hello message+certificates+ServerHelloDone message sent
+ /// by server and buffer it to rbuf member
+ int readAndBufferServerHelloMsg(BIO *table, const char *description);
+
/// The write hold state
bool holdWrite() const {return holdWrite_;}
/// Enables or disables the write hold state
#include <cerrno>
- static void setSessionCallbacks(Security::ContextPtr ctx);
- Ipc::MemMap *SslSessionCache = NULL;
- const char *SslSessionCacheName = "ssl_session_cache";
+// TODO: Move ssl_ex_index_* global variables from global.cc here.
+int ssl_ex_index_ssl_untrusted_chain = -1;
+
+ Ipc::MemMap *Ssl::SessionCache = NULL;
+ const char *Ssl::SessionCacheName = "ssl_session_cache";
static Ssl::CertsIndexedList SquidUntrustedCerts;