From: Christos Tsantilas Date: Fri, 18 Mar 2016 17:42:31 +0000 (+0200) Subject: merge from trunk r14590 X-Git-Tag: SQUID_4_0_13~5^2~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=54fb1cbf85fff8de784dd1fb2aa6f3c274664590;p=thirdparty%2Fsquid.git merge from trunk r14590 --- 54fb1cbf85fff8de784dd1fb2aa6f3c274664590 diff --cc src/Downloader.cc index 5584dd84f7,0000000000..167abc9968 mode 100644,000000..100644 --- a/src/Downloader.cc +++ b/src/Downloader.cc @@@ -1,223 -1,0 +1,224 @@@ +#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(); - if (ClientSocketContext *context = parseOneRequest()) { ++ 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 * ++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; + - ClientSocketContext *const context = new ClientSocketContext(NULL, http); ++ 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 - Downloader::processParsedRequest(ClientSocketContext *context) ++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) +{ - ClientSocketContext::Pointer context = pipeline.front(); ++ 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(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(this), Downloader, downloadFinished); +} + +bool +Downloader::isOpen() const +{ + return cbdataReferenceValid(this) && // XXX: checking "this" in a method + callback != NULL; +} diff --cc src/Downloader.h index 152d2d8e0b,0000000000..be9293fd83 mode 100644,000000..100644 --- a/src/Downloader.h +++ b/src/Downloader.h @@@ -1,69 -1,0 +1,69 @@@ +#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 ClientSocketContext *parseOneRequest(); - virtual void processParsedRequest(ClientSocketContext *context); ++ 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 diff --cc src/client_side.cc index b5b7d52e8c,9df9c99ba7..df79f9a5c8 --- a/src/client_side.cc +++ b/src/client_side.cc @@@ -2348,12 -1640,12 +1646,14 @@@ clientProcessRequest(ConnStateData *con 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) @@@ -3955,12 -3251,16 +3263,16 @@@ void httpsSslBumpStep2AccessCheckDone(a 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(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; @@@ -3976,8 -3279,8 +3288,8 @@@ // 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); } diff --cc src/ssl/PeerConnector.cc index 28e105054f,695abc8bd8..5d633f921e --- a/src/ssl/PeerConnector.cc +++ b/src/ssl/PeerConnector.cc @@@ -10,40 -10,26 +10,30 @@@ #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(callback->getDialer())); @@@ -453,29 -338,8 +342,29 @@@ Ssl::PeerConnector::handleNegotiateErro 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(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); } @@@ -608,408 -467,3 +492,101 @@@ Ssl::PeerConnector::status() cons return buf.content(); } +/// 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 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(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(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(request->clientConnectionManager.valid()); + if (csd && csd->nestedLevel() >= MaxNestedDownloads) + return false; + + 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(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; +} + - 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(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(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(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(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()); - } - } - } - diff --cc src/ssl/PeerConnector.h index 34d2c12c1e,7a8f481d71..ec461dcde8 --- a/src/ssl/PeerConnector.h +++ b/src/ssl/PeerConnector.h @@@ -15,11 -15,15 +15,16 @@@ #include "CommCalls.h" #include "security/EncryptorAnswer.h" #include "ssl/support.h" + #include +#include + #if USE_OPENSSL + class HttpRequest; class ErrorState; + class AccessLogEntry; + typedef RefCount AccessLogEntryPointer; namespace Ssl { @@@ -182,108 -171,14 +184,23 @@@ private /// A wrapper function for negotiateSsl for use with Comm::SetSelect static void NegotiateSsl(int fd, void *data); + + /// 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 callback; ///< we call this with the results 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 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 */ diff --cc src/ssl/bio.cc index 3f82cc01b4,55e49fc07c..91d5afd5da --- a/src/ssl/bio.cc +++ b/src/ssl/bio.cc @@@ -863,7 -645,18 +870,17 @@@ squid_ssl_info(const SSL *ssl, int wher } } - 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); } diff --cc src/ssl/bio.h index 3fc79f70b9,c8f537a159..0268874a2e --- a/src/ssl/bio.h +++ b/src/ssl/bio.h @@@ -265,8 -56,12 +266,9 @@@ public /// \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 @@@ -310,13 -111,21 +312,18 @@@ /// 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); + /// Return the TLS features requested by TLS client + const Bio::sslFeatures &receivedHelloFeatures() const {return receivedHelloFeatures_;} + - const MemBuf &rBufData() {return rbuf;} + 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 @@@ -394,12 -199,11 +397,16 @@@ public /// 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 diff --cc src/ssl/support.cc index edcadbcbdc,611c412d33..564da9cfe7 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@@ -33,12 -33,8 +33,11 @@@ #include +// TODO: Move ssl_ex_index_* global variables from global.cc here. +int ssl_ex_index_ssl_untrusted_chain = -1; + - static void setSessionCallbacks(Security::ContextPtr ctx); - Ipc::MemMap *SslSessionCache = NULL; - const char *SslSessionCacheName = "ssl_session_cache"; + Ipc::MemMap *Ssl::SessionCache = NULL; + const char *Ssl::SessionCacheName = "ssl_session_cache"; static Ssl::CertsIndexedList SquidUntrustedCerts;